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