WinZipAesExtraField::getSaltSize()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 1
b 0
f 1
1
<?php
2
3
namespace PhpZip\Model\Extra\Fields;
4
5
use PhpZip\Constants\ZipCompressionMethod;
6
use PhpZip\Constants\ZipEncryptionMethod;
7
use PhpZip\Exception\InvalidArgumentException;
8
use PhpZip\Exception\ZipException;
9
use PhpZip\Exception\ZipUnsupportMethodException;
10
use PhpZip\Model\Extra\ZipExtraField;
11
use PhpZip\Model\ZipEntry;
12
13
/**
14
 * WinZip AES Extra Field.
15
 *
16
 * @see http://www.winzip.com/win/en/aes_tips.htm AES Coding Tips for Developers
17
 */
18
class WinZipAesExtraField implements ZipExtraField
19
{
20
    /** @var int Header id */
21
    const HEADER_ID = 0x9901;
22
23
    /**
24
     * @var int Data size (currently 7, but subject to possible increase
25
     *          in the future)
26
     */
27
    const DATA_SIZE = 7;
28
29
    /**
30
     * @var int The vendor ID field should always be set to the two ASCII
31
     *          characters "AE"
32
     */
33
    const VENDOR_ID = 0x4541; // 'A' | ('E' << 8)
34
35
    /**
36
     * @var int Entries of this type do include the standard ZIP CRC-32 value.
37
     *          For use with {@see WinZipAesExtraField::setVendorVersion()}.
38
     */
39
    const VERSION_AE1 = 1;
40
41
    /**
42
     * @var int Entries of this type do not include the standard ZIP CRC-32 value.
43
     *          For use with {@see WinZipAesExtraField::setVendorVersion().
44
     */
45
    const VERSION_AE2 = 2;
46
47
    /** @var int integer mode value indicating AES encryption 128-bit strength */
48
    const KEY_STRENGTH_128BIT = 0x01;
49
50
    /** @var int integer mode value indicating AES encryption 192-bit strength */
51
    const KEY_STRENGTH_192BIT = 0x02;
52
53
    /** @var int integer mode value indicating AES encryption 256-bit strength */
54
    const KEY_STRENGTH_256BIT = 0x03;
55
56
    /** @var int[] */
57
    private static $allowVendorVersions = [
58
        self::VERSION_AE1,
59
        self::VERSION_AE2,
60
    ];
61
62
    /** @var array<int, int> */
63
    private static $encryptionStrengths = [
64
        self::KEY_STRENGTH_128BIT => 128,
65
        self::KEY_STRENGTH_192BIT => 192,
66
        self::KEY_STRENGTH_256BIT => 256,
67
    ];
68
69
    /** @var array<int, int> */
70
    private static $MAP_KEY_STRENGTH_METHODS = [
71
        self::KEY_STRENGTH_128BIT => ZipEncryptionMethod::WINZIP_AES_128,
72
        self::KEY_STRENGTH_192BIT => ZipEncryptionMethod::WINZIP_AES_192,
73
        self::KEY_STRENGTH_256BIT => ZipEncryptionMethod::WINZIP_AES_256,
74
    ];
75
76
    /** @var int Integer version number specific to the zip vendor */
77
    private $vendorVersion = self::VERSION_AE1;
78
79
    /** @var int Integer mode value indicating AES encryption strength */
80
    private $keyStrength = self::KEY_STRENGTH_256BIT;
81
82
    /** @var int The actual compression method used to compress the file */
83
    private $compressionMethod;
84
85
    /**
86
     * @param int $vendorVersion     Integer version number specific to the zip vendor
87
     * @param int $keyStrength       Integer mode value indicating AES encryption strength
88
     * @param int $compressionMethod The actual compression method used to compress the file
89
     *
90
     * @throws ZipUnsupportMethodException
91
     */
92 22
    public function __construct($vendorVersion, $keyStrength, $compressionMethod)
93
    {
94 22
        $this->setVendorVersion($vendorVersion);
95 21
        $this->setKeyStrength($keyStrength);
96 19
        $this->setCompressionMethod($compressionMethod);
97 18
    }
98
99
    /**
100
     * @param ZipEntry $entry
101
     *
102
     * @throws ZipUnsupportMethodException
103
     *
104
     * @return WinZipAesExtraField
105
     */
106 8
    public static function create(ZipEntry $entry)
107
    {
108 8
        $keyStrength = array_search($entry->getEncryptionMethod(), self::$MAP_KEY_STRENGTH_METHODS, true);
109
110 8
        if ($keyStrength === false) {
111
            throw new InvalidArgumentException('Not support encryption method ' . $entry->getEncryptionMethod());
112
        }
113
114
        // WinZip 11 will continue to use AE-2, with no CRC, for very small files
115
        // of less than 20 bytes. It will also use AE-2 for files compressed in
116
        // BZIP2 format, because this format has internal integrity checks
117
        // equivalent to a CRC check built in.
118
        //
119
        // https://www.winzip.com/win/en/aes_info.html
120
        $vendorVersion = (
121 8
            $entry->getUncompressedSize() < 20 ||
122 8
            $entry->getCompressionMethod() === ZipCompressionMethod::BZIP2
123
        ) ?
124 6
            self::VERSION_AE2 :
125 8
            self::VERSION_AE1;
126
127 8
        $field = new self($vendorVersion, $keyStrength, $entry->getCompressionMethod());
0 ignored issues
show
Bug introduced by
It seems like $keyStrength can also be of type string; however, parameter $keyStrength of PhpZip\Model\Extra\Field...traField::__construct() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

127
        $field = new self($vendorVersion, /** @scrutinizer ignore-type */ $keyStrength, $entry->getCompressionMethod());
Loading history...
128
129 8
        $entry->getLocalExtraFields()->add($field);
130 8
        $entry->getCdExtraFields()->add($field);
131
132 8
        return $field;
133
    }
134
135
    /**
136
     * Returns the Header ID (type) of this Extra Field.
137
     * The Header ID is an unsigned short integer (two bytes)
138
     * which must be constant during the life cycle of this object.
139
     *
140
     * @return int
141
     */
142 15
    public function getHeaderId()
143
    {
144 15
        return self::HEADER_ID;
145
    }
146
147
    /**
148
     * Populate data from this array as if it was in local file data.
149
     *
150
     * @param string        $buffer the buffer to read data from
151
     * @param ZipEntry|null $entry
152
     *
153
     * @throws ZipException on error
154
     *
155
     * @return WinZipAesExtraField
156
     */
157 15
    public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
158
    {
159 15
        $size = \strlen($buffer);
160
161 15
        if ($size !== self::DATA_SIZE) {
162
            throw new ZipException(
163
                sprintf(
164
                    'WinZip AES Extra data invalid size: %d. Must be %d',
165
                    $size,
166
                    self::DATA_SIZE
167
                )
168
            );
169
        }
170
171 15
        $data = unpack('vvendorVersion/vvendorId/ckeyStrength/vcompressionMethod', $buffer);
172
173 15
        if ($data['vendorId'] !== self::VENDOR_ID) {
174
            throw new ZipException(
175
                sprintf(
176
                    'Vendor id invalid: %d. Must be %d',
177
                    $data['vendorId'],
178
                    self::VENDOR_ID
179
                )
180
            );
181
        }
182
183 15
        return new self(
184 15
            $data['vendorVersion'],
185 15
            $data['keyStrength'],
186 15
            $data['compressionMethod']
187
        );
188
    }
189
190
    /**
191
     * Populate data from this array as if it was in central directory data.
192
     *
193
     * @param string        $buffer the buffer to read data from
194
     * @param ZipEntry|null $entry
195
     *
196
     * @throws ZipException
197
     *
198
     * @return WinZipAesExtraField
199
     */
200 15
    public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
201
    {
202 15
        return self::unpackLocalFileData($buffer, $entry);
203
    }
204
205
    /**
206
     * The actual data to put into local file data - without Header-ID
207
     * or length specifier.
208
     *
209
     * @return string the data
210
     */
211 15
    public function packLocalFileData()
212
    {
213 15
        return pack(
214 15
            'vvcv',
215 15
            $this->vendorVersion,
216 15
            self::VENDOR_ID,
217 15
            $this->keyStrength,
218 15
            $this->compressionMethod
219
        );
220
    }
221
222
    /**
223
     * The actual data to put into central directory - without Header-ID or
224
     * length specifier.
225
     *
226
     * @return string the data
227
     */
228 15
    public function packCentralDirData()
229
    {
230 15
        return $this->packLocalFileData();
231
    }
232
233
    /**
234
     * Returns the vendor version.
235
     *
236
     * @return int
237
     *
238
     * @see WinZipAesExtraField::VERSION_AE2
239
     * @see WinZipAesExtraField::VERSION_AE1
240
     */
241 7
    public function getVendorVersion()
242
    {
243 7
        return $this->vendorVersion;
244
    }
245
246
    /**
247
     * Sets the vendor version.
248
     *
249
     * @param int $vendorVersion the vendor version
250
     *
251
     * @see    WinZipAesExtraField::VERSION_AE2
252
     * @see    WinZipAesExtraField::VERSION_AE1
253
     */
254 22
    public function setVendorVersion($vendorVersion)
255
    {
256 22
        $vendorVersion = (int) $vendorVersion;
257
258 22
        if (!\in_array($vendorVersion, self::$allowVendorVersions, true)) {
259 2
            throw new InvalidArgumentException(
260
                sprintf(
261 2
                    'Unsupport WinZip AES vendor version: %d',
262
                    $vendorVersion
263
                )
264
            );
265
        }
266 21
        $this->vendorVersion = $vendorVersion;
267 21
    }
268
269
    /**
270
     * Returns vendor id.
271
     *
272
     * @return int
273
     */
274 6
    public function getVendorId()
275
    {
276 6
        return self::VENDOR_ID;
277
    }
278
279
    /**
280
     * @return int
281
     */
282 16
    public function getKeyStrength()
283
    {
284 16
        return $this->keyStrength;
285
    }
286
287
    /**
288
     * Set key strength.
289
     *
290
     * @param int $keyStrength
291
     */
292 21
    public function setKeyStrength($keyStrength)
293
    {
294 21
        $keyStrength = (int) $keyStrength;
295
296 21
        if (!isset(self::$encryptionStrengths[$keyStrength])) {
297 2
            throw new InvalidArgumentException(
298
                sprintf(
299 2
                    'Key strength %d not support value. Allow values: %s',
300
                    $keyStrength,
301 2
                    implode(', ', array_keys(self::$encryptionStrengths))
302
                )
303
            );
304
        }
305 19
        $this->keyStrength = $keyStrength;
306 19
    }
307
308
    /**
309
     * @return int
310
     */
311 16
    public function getCompressionMethod()
312
    {
313 16
        return $this->compressionMethod;
314
    }
315
316
    /**
317
     * @param int $compressionMethod
318
     *
319
     * @throws ZipUnsupportMethodException
320
     */
321 19
    public function setCompressionMethod($compressionMethod)
322
    {
323 19
        $compressionMethod = (int) $compressionMethod;
324 19
        ZipCompressionMethod::checkSupport($compressionMethod);
325 18
        $this->compressionMethod = $compressionMethod;
326 18
    }
327
328
    /**
329
     * @return int
330
     */
331 16
    public function getEncryptionStrength()
332
    {
333 16
        return self::$encryptionStrengths[$this->keyStrength];
334
    }
335
336
    /**
337
     * @return int
338
     */
339 10
    public function getEncryptionMethod()
340
    {
341 10
        $keyStrength = $this->getKeyStrength();
342
343 10
        if (!isset(self::$MAP_KEY_STRENGTH_METHODS[$keyStrength])) {
344
            throw new InvalidArgumentException('Invalid encryption method');
345
        }
346
347 10
        return self::$MAP_KEY_STRENGTH_METHODS[$keyStrength];
348
    }
349
350
    /**
351
     * @return bool
352
     */
353
    public function isV1()
354
    {
355
        return $this->vendorVersion === self::VERSION_AE1;
356
    }
357
358
    /**
359
     * @return bool
360
     */
361 9
    public function isV2()
362
    {
363 9
        return $this->vendorVersion === self::VERSION_AE2;
364
    }
365
366
    /**
367
     * @return int
368
     */
369 16
    public function getSaltSize()
370
    {
371 16
        return (int) ($this->getEncryptionStrength() / 8 / 2);
372
    }
373
374
    /**
375
     * @return string
376
     */
377
    public function __toString()
378
    {
379
        return sprintf(
380
            '0x%04x WINZIP AES: VendorVersion=%d KeyStrength=0x%02x CompressionMethod=%s',
381
            __CLASS__,
382
            $this->vendorVersion,
383
            $this->keyStrength,
384
            $this->compressionMethod
385
        );
386
    }
387
}
388