View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers.zip;
20  
21  import java.util.Arrays;
22  import java.util.zip.ZipException;
23  
24  /**
25   * Strong Encryption Header (0x0017).
26   *
27   * <p>
28   * Certificate-based encryption:
29   * </p>
30   *
31   * <pre>
32   * Value     Size     Description
33   * -----     ----     -----------
34   * 0x0017    2 bytes  Tag for this "extra" block type
35   * TSize     2 bytes  Size of data that follows
36   * Format    2 bytes  Format definition for this record
37   * AlgID     2 bytes  Encryption algorithm identifier
38   * Bitlen    2 bytes  Bit length of encryption key (32-448 bits)
39   * Flags     2 bytes  Processing flags
40   * RCount    4 bytes  Number of recipients.
41   * HashAlg   2 bytes  Hash algorithm identifier
42   * HSize     2 bytes  Hash size
43   * SRList    (var)    Simple list of recipients hashed public keys
44   *
45   * Flags -   This defines the processing flags.
46   * </pre>
47   *
48   * <ul>
49   * <li>0x0007 - reserved for future use
50   * <li>0x000F - reserved for future use
51   * <li>0x0100 - Indicates non-OAEP key wrapping was used. If this this field is set, the version needed to extract must be at least 61. This means OAEP key
52   * wrapping is not used when generating a Master Session Key using ErdData.
53   * <li>0x4000 - ErdData must be decrypted using 3DES-168, otherwise use the same algorithm used for encrypting the file contents.
54   * <li>0x8000 - reserved for future use
55   * </ul>
56   *
57   * <pre>
58   * RCount - This defines the number intended recipients whose
59   *          public keys were used for encryption.  This identifies
60   *          the number of elements in the SRList.
61   *
62   *          see also: reserved1
63   *
64   * HashAlg - This defines the hash algorithm used to calculate
65   *           the public key hash of each public key used
66   *           for encryption. This field currently supports
67   *           only the following value for SHA-1
68   *
69   *           0x8004 - SHA1
70   *
71   * HSize -   This defines the size of a hashed public key.
72   *
73   * SRList -  This is a variable length list of the hashed
74   *           public keys for each intended recipient.  Each
75   *           element in this list is HSize.  The total size of
76   *           SRList is determined using RCount * HSize.
77   * </pre>
78   *
79   * <p>
80   * Password-based Extra Field 0x0017 in central header only.
81   * </p>
82   *
83   * <pre>
84   * Value     Size     Description
85   * -----     ----     -----------
86   * 0x0017    2 bytes  Tag for this "extra" block type
87   * TSize     2 bytes  Size of data that follows
88   * Format    2 bytes  Format definition for this record
89   * AlgID     2 bytes  Encryption algorithm identifier
90   * Bitlen    2 bytes  Bit length of encryption key (32-448 bits)
91   * Flags     2 bytes  Processing flags
92   * (more?)
93   * </pre>
94   *
95   * <p>
96   * <b>Format</b> - the data format identifier for this record. The only value allowed at this time is the integer value 2.
97   * </p>
98   *
99   * <p>
100  * Password-based Extra Field 0x0017 preceding compressed file data.
101  * </p>
102  *
103  * <pre>
104  * Value     Size     Description
105  * -----     ----     -----------
106  * 0x0017    2 bytes  Tag for this "extra" block type
107  * IVSize    2 bytes  Size of initialization vector (IV)
108  * IVData    IVSize   Initialization vector for this file
109  * Size      4 bytes  Size of remaining decryption header data
110  * Format    2 bytes  Format definition for this record
111  * AlgID     2 bytes  Encryption algorithm identifier
112  * Bitlen    2 bytes  Bit length of encryption key (32-448 bits)
113  * Flags     2 bytes  Processing flags
114  * ErdSize   2 bytes  Size of Encrypted Random Data
115  * ErdData   ErdSize  Encrypted Random Data
116  * Reserved1 4 bytes  Reserved certificate processing data
117  * Reserved2 (var)    Reserved for certificate processing data
118  * VSize     2 bytes  Size of password validation data
119  * VData     VSize-4  Password validation data
120  * VCRC32    4 bytes  Standard ZIP CRC32 of password validation data
121  *
122  * IVData - The size of the IV should match the algorithm block size.
123  *          The IVData can be completely random data.  If the size of
124  *          the randomly generated data does not match the block size
125  *          it should be complemented with zero's or truncated as
126  *          necessary.  If IVSize is 0, then IV = CRC32 + Uncompressed
127  *          File Size (as a 64 bit little-endian, unsigned integer value).
128  *
129  * Format -  the data format identifier for this record.  The only
130  *           value allowed at this time is the integer value 2.
131  *
132  * ErdData - Encrypted random data is used to store random data that
133  *           is used to generate a file session key for encrypting
134  *           each file.  SHA1 is used to calculate hash data used to
135  *           derive keys.  File session keys are derived from a master
136  *           session key generated from the user-supplied password.
137  *           If the Flags field in the decryption header contains
138  *           the value 0x4000, then the ErdData field must be
139  *           decrypted using 3DES. If the value 0x4000 is not set,
140  *           then the ErdData field must be decrypted using AlgId.
141  *
142  * Reserved1 - Reserved for certificate processing, if value is
143  *           zero, then Reserved2 data is absent.  See the explanation
144  *           under the Certificate Processing Method for details on
145  *           this data structure.
146  *
147  * Reserved2 - If present, the size of the Reserved2 data structure
148  *           is located by skipping the first 4 bytes of this field
149  *           and using the next 2 bytes as the remaining size.  See
150  *           the explanation under the Certificate Processing Method
151  *           for details on this data structure.
152  *
153  * VSize - This size value will always include the 4 bytes of the
154  *         VCRC32 data and will be greater than 4 bytes.
155  *
156  * VData - Random data for password validation.  This data is VSize
157  *         in length and VSize must be a multiple of the encryption
158  *         block size.  VCRC32 is a checksum value of VData.
159  *         VData and VCRC32 are stored encrypted and start the
160  *         stream of encrypted data for a file.
161  * </pre>
162  *
163  * <p>
164  * Reserved1 - Certificate Decryption Header Reserved1 Data:
165  * </p>
166  *
167  * <pre>
168  * Value     Size     Description
169  * -----     ----     -----------
170  * RCount    4 bytes  Number of recipients.
171  * </pre>
172  *
173  * <p>
174  * RCount - This defines the number intended recipients whose public keys were used for encryption. This defines the number of elements in the REList field
175  * defined below.
176  * </p>
177  *
178  * <p>
179  * Reserved2 - Certificate Decryption Header Reserved2 Data Structures:
180  * </p>
181  *
182  * <pre>
183  * Value     Size     Description
184  * -----     ----     -----------
185  * HashAlg   2 bytes  Hash algorithm identifier
186  * HSize     2 bytes  Hash size
187  * REList    (var)    List of recipient data elements
188  *
189  * HashAlg - This defines the hash algorithm used to calculate
190  *           the public key hash of each public key used
191  *           for encryption. This field currently supports
192  *           only the following value for SHA-1
193  *
194  *               0x8004 - SHA1
195  *
196  * HSize -   This defines the size of a hashed public key
197  *           defined in REHData.
198  *
199  * REList -  This is a variable length of list of recipient data.
200  *           Each element in this list consists of a Recipient
201  *           Element data structure as follows:
202  * </pre>
203  *
204  * <p>
205  * Recipient Element (REList) Data Structure:
206  * </p>
207  *
208  * <pre>
209  * Value     Size     Description
210  * -----     ----     -----------
211  * RESize    2 bytes  Size of REHData + REKData
212  * REHData   HSize    Hash of recipients public key
213  * REKData   (var)    Simple key blob
214  *
215  *
216  * RESize -  This defines the size of an individual REList
217  *           element.  This value is the combined size of the
218  *           REHData field + REKData field.  REHData is defined by
219  *           HSize.  REKData is variable and can be calculated
220  *           for each REList element using RESize and HSize.
221  *
222  * REHData - Hashed public key for this recipient.
223  *
224  * REKData - Simple Key Blob.  The format of this data structure
225  *           is identical to that defined in the Microsoft
226  *           CryptoAPI and generated using the CryptExportKey()
227  *           function.  The version of the Simple Key Blob
228  *           supported at this time is 0x02 as defined by
229  *           Microsoft.
230  *
231  *           For more details see https://msdn.microsoft.com/en-us/library/aa920051.aspx
232  * </pre>
233  *
234  * <p>
235  * <b>Flags</b> - Processing flags needed for decryption
236  * </p>
237  *
238  * <ul>
239  * <li>0x0001 - Password is required to decrypt</li>
240  * <li>0x0002 - Certificates only</li>
241  * <li>0x0003 - Password or certificate required to decrypt</li>
242  * <li>0x0007 - reserved for future use
243  * <li>0x000F - reserved for future use
244  * <li>0x0100 - indicates non-OAEP key wrapping was used. If this field is set the version needed to extract must be at least 61. This means OAEP key wrapping
245  * is not used when generating a Master Session Key using ErdData.
246  * <li>0x4000 - ErdData must be decrypted using 3DES-168, otherwise use the same algorithm used for encrypting the file contents.
247  * <li>0x8000 - reserved for future use.
248  * </ul>
249  *
250  * <p>
251  * <b>See the section describing the Strong Encryption Specification for details. Refer to the section in this document entitled "Incorporating PKWARE
252  * Proprietary Technology into Your Product" for more information.</b>
253  * </p>
254  *
255  * @NotThreadSafe
256  * @since 1.11
257  */
258 public class X0017_StrongEncryptionHeader extends PKWareExtraHeader {
259 
260     static final ZipShort HEADER_ID = new ZipShort(0x0017);
261 
262     private int format; // TODO written but not read
263 
264     private EncryptionAlgorithm algId;
265     private int bitlen; // TODO written but not read
266     private int flags; // TODO written but not read
267     private long rcount;
268     private HashAlgorithm hashAlg;
269     private int hashSize;
270 
271     /** Encryption data/ */
272     private byte[] ivData;
273 
274     private byte[] erdData;
275 
276     /** Encryption key. */
277     private byte[] recipientKeyHash;
278 
279     private byte[] keyBlob;
280 
281     /** Password verification data. */
282     private byte[] vData;
283 
284     private byte[] vCRC32;
285 
286     public X0017_StrongEncryptionHeader() {
287         super(HEADER_ID);
288     }
289 
290     private void assertDynamicLengthFits(final String what, final int dynamicLength, final int prefixLength, final int length) throws ZipException {
291         if (prefixLength + dynamicLength > length) {
292             throw new ZipException("Invalid X0017_StrongEncryptionHeader: " + what + " " + dynamicLength + " doesn't fit into " + length
293                     + " bytes of data at position " + prefixLength);
294         }
295     }
296 
297     /**
298      * Gets encryption algorithm.
299      *
300      * @return the encryption algorithm
301      */
302     public EncryptionAlgorithm getEncryptionAlgorithm() {
303         return algId;
304     }
305 
306     /**
307      * Gets hash algorithm.
308      *
309      * @return the hash algorithm
310      */
311     public HashAlgorithm getHashAlgorithm() {
312         return hashAlg;
313     }
314 
315     /**
316      * Gets record count.
317      *
318      * @return the record count
319      */
320     public long getRecordCount() {
321         return rcount;
322     }
323 
324     /**
325      * Parse central directory format.
326      *
327      * @param data   the buffer to read data from
328      * @param offset offset into buffer to read data
329      * @param length the length of data
330      * @throws ZipException if an error occurs
331      */
332     public void parseCentralDirectoryFormat(final byte[] data, final int offset, final int length) throws ZipException {
333         assertMinimalLength(12, length);
334         // TODO: double check we really do not want to call super here
335         this.format = ZipShort.getValue(data, offset);
336         this.algId = EncryptionAlgorithm.getAlgorithmByCode(ZipShort.getValue(data, offset + 2));
337         this.bitlen = ZipShort.getValue(data, offset + 4);
338         this.flags = ZipShort.getValue(data, offset + 6);
339         this.rcount = ZipLong.getValue(data, offset + 8);
340 
341         if (rcount > 0) {
342             assertMinimalLength(16, length);
343             this.hashAlg = HashAlgorithm.getAlgorithmByCode(ZipShort.getValue(data, offset + 12));
344             this.hashSize = ZipShort.getValue(data, offset + 14);
345         }
346     }
347 
348     /**
349      * Parse file header format.
350      *
351      * <p>
352      * (Password only?)
353      * </p>
354      *
355      * @param data   the buffer to read data from
356      * @param offset offset into buffer to read data
357      * @param length the length of data
358      * @throws ZipException if an error occurs
359      */
360     public void parseFileFormat(final byte[] data, final int offset, final int length) throws ZipException {
361         assertMinimalLength(4, length);
362         final int ivSize = ZipShort.getValue(data, offset);
363         assertDynamicLengthFits("ivSize", ivSize, 4, length);
364         assertMinimalLength(offset + 4, ivSize);
365         // TODO: what is at offset + 2?
366         this.ivData = Arrays.copyOfRange(data, offset + 4, ivSize);
367 
368         assertMinimalLength(16 + ivSize, length); // up to and including erdSize
369         // TODO: what is at offset + 4 + ivSize?
370         this.format = ZipShort.getValue(data, offset + ivSize + 6);
371         this.algId = EncryptionAlgorithm.getAlgorithmByCode(ZipShort.getValue(data, offset + ivSize + 8));
372         this.bitlen = ZipShort.getValue(data, offset + ivSize + 10);
373         this.flags = ZipShort.getValue(data, offset + ivSize + 12);
374 
375         final int erdSize = ZipShort.getValue(data, offset + ivSize + 14);
376         assertDynamicLengthFits("erdSize", erdSize, ivSize + 16, length);
377         assertMinimalLength(offset + ivSize + 16, erdSize);
378         this.erdData = Arrays.copyOfRange(data, offset + ivSize + 16, erdSize);
379 
380         assertMinimalLength(16 + 4 + ivSize + erdSize, length);
381         this.rcount = ZipLong.getValue(data, offset + ivSize + 16 + erdSize);
382         if (rcount == 0) {
383             assertMinimalLength(ivSize + 20 + erdSize + 2, length);
384             final int vSize = ZipShort.getValue(data, offset + ivSize + 20 + erdSize);
385             assertDynamicLengthFits("vSize", vSize, ivSize + 22 + erdSize, length);
386             if (vSize < 4) {
387                 throw new ZipException("Invalid X0017_StrongEncryptionHeader: vSize " + vSize + " is too small to hold CRC");
388             }
389             assertMinimalLength(offset + ivSize + 22 + erdSize, vSize - 4);
390             this.vData = Arrays.copyOfRange(data, offset + ivSize + 22 + erdSize, vSize - 4);
391             assertMinimalLength(offset + ivSize + 22 + erdSize + vSize - 4, 4);
392             this.vCRC32 = Arrays.copyOfRange(data, offset + ivSize + 22 + erdSize + vSize - 4, 4);
393         } else {
394             assertMinimalLength(ivSize + 20 + erdSize + 6, length); // up to and including resize
395             this.hashAlg = HashAlgorithm.getAlgorithmByCode(ZipShort.getValue(data, offset + ivSize + 20 + erdSize));
396             this.hashSize = ZipShort.getValue(data, offset + ivSize + 22 + erdSize);
397             final int resize = ZipShort.getValue(data, offset + ivSize + 24 + erdSize);
398 
399             if (resize < this.hashSize) {
400                 throw new ZipException("Invalid X0017_StrongEncryptionHeader: resize " + resize + " is too small to hold hashSize" + this.hashSize);
401             }
402             // TODO: this looks suspicious, 26 rather than 24 would be "after" resize
403             assertDynamicLengthFits("resize", resize, ivSize + 24 + erdSize, length);
404             //
405             this.recipientKeyHash = Arrays.copyOfRange(data, offset + ivSize + 24 + erdSize, this.hashSize);
406             this.keyBlob = Arrays.copyOfRange(data, offset + ivSize + 24 + erdSize + this.hashSize, resize - this.hashSize);
407 
408             assertMinimalLength(ivSize + 26 + erdSize + resize + 2, length);
409             final int vSize = ZipShort.getValue(data, offset + ivSize + 26 + erdSize + resize);
410             if (vSize < 4) {
411                 throw new ZipException("Invalid X0017_StrongEncryptionHeader: vSize " + vSize + " is too small to hold CRC");
412             }
413             // TODO: these offsets look even more suspicious, the constant should likely be 28 rather than 22
414             assertDynamicLengthFits("vSize", vSize, ivSize + 22 + erdSize + resize, length);
415             //
416             this.vData = Arrays.copyOfRange(data, offset + ivSize + 22 + erdSize + resize, vSize - 4);
417             this.vCRC32 = Arrays.copyOfRange(data, offset + ivSize + 22 + erdSize + resize + vSize - 4, 4);
418         }
419 
420         // validate values?
421     }
422 
423     @Override
424     public void parseFromCentralDirectoryData(final byte[] data, final int offset, final int length) throws ZipException {
425         super.parseFromCentralDirectoryData(data, offset, length);
426         parseCentralDirectoryFormat(data, offset, length);
427     }
428 
429     @Override
430     public void parseFromLocalFileData(final byte[] data, final int offset, final int length) throws ZipException {
431         super.parseFromLocalFileData(data, offset, length);
432         parseFileFormat(data, offset, length);
433     }
434 }