    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}