Home | History | Annotate | Download | only in engines
      1 package org.bouncycastle.crypto.engines;
      2 
      3 import java.security.SecureRandom;
      4 
      5 import org.bouncycastle.crypto.CipherParameters;
      6 import org.bouncycastle.crypto.Digest;
      7 import org.bouncycastle.crypto.InvalidCipherTextException;
      8 import org.bouncycastle.crypto.Wrapper;
      9 import org.bouncycastle.crypto.modes.CBCBlockCipher;
     10 import org.bouncycastle.crypto.params.KeyParameter;
     11 import org.bouncycastle.crypto.params.ParametersWithIV;
     12 import org.bouncycastle.crypto.params.ParametersWithRandom;
     13 // Android-changed: Use Android digests
     14 // import org.bouncycastle.crypto.util.DigestFactory;
     15 import org.bouncycastle.crypto.digests.AndroidDigestFactory;
     16 import org.bouncycastle.util.Arrays;
     17 
     18 /**
     19  * Wrap keys according to
     20  * <A HREF="https://www.ietf.org/rfc/rfc3217.txt">
     21  * RFC 3217</A>.
     22  * <p>
     23  * Note:
     24  * <ul>
     25  * <li>if you are using this to wrap triple-des keys you need to set the
     26  * parity bits on the key and, if it's a two-key triple-des key, pad it
     27  * yourself.
     28  * </ul>
     29  */
     30 public class DESedeWrapEngine
     31     implements Wrapper
     32 {
     33    /** Field engine */
     34    private CBCBlockCipher engine;
     35 
     36    /** Field param */
     37    private KeyParameter param;
     38 
     39    /** Field paramPlusIV */
     40    private ParametersWithIV paramPlusIV;
     41 
     42    /** Field iv */
     43    private byte[] iv;
     44 
     45    /** Field forWrapping */
     46    private boolean forWrapping;
     47 
     48    /** Field IV2           */
     49    private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2,
     50                                        (byte) 0x2c, (byte) 0x79, (byte) 0xe8,
     51                                        (byte) 0x21, (byte) 0x05 };
     52 
     53     //
     54     // checksum digest
     55     //
     56     // Android-changed: Use Android digests
     57     // Digest  sha1 = DigestFactory.createSHA1();
     58     Digest  sha1 = AndroidDigestFactory.getSHA1();
     59     byte[]  digest = new byte[20];
     60 
     61    /**
     62     * Method init
     63     *
     64     * @param forWrapping true if for wrapping, false otherwise.
     65     * @param param necessary parameters, may include KeyParameter, ParametersWithRandom, and ParametersWithIV
     66     */
     67     public void init(boolean forWrapping, CipherParameters param)
     68     {
     69 
     70         this.forWrapping = forWrapping;
     71         this.engine = new CBCBlockCipher(new DESedeEngine());
     72 
     73         SecureRandom sr;
     74         if (param instanceof ParametersWithRandom)
     75         {
     76             ParametersWithRandom pr = (ParametersWithRandom) param;
     77             param = pr.getParameters();
     78             sr = pr.getRandom();
     79         }
     80         else
     81         {
     82             sr = new SecureRandom();
     83         }
     84 
     85         if (param instanceof KeyParameter)
     86         {
     87             this.param = (KeyParameter)param;
     88 
     89             if (this.forWrapping)
     90             {
     91 
     92                 // Hm, we have no IV but we want to wrap ?!?
     93                 // well, then we have to create our own IV.
     94                 this.iv = new byte[8];
     95                 sr.nextBytes(iv);
     96 
     97                 this.paramPlusIV = new ParametersWithIV(this.param, this.iv);
     98             }
     99         }
    100         else if (param instanceof ParametersWithIV)
    101         {
    102             this.paramPlusIV = (ParametersWithIV)param;
    103             this.iv = this.paramPlusIV.getIV();
    104             this.param = (KeyParameter)this.paramPlusIV.getParameters();
    105 
    106             if (this.forWrapping)
    107             {
    108                 if ((this.iv == null) || (this.iv.length != 8))
    109                 {
    110                     throw new IllegalArgumentException("IV is not 8 octets");
    111                 }
    112             }
    113             else
    114             {
    115                 throw new IllegalArgumentException(
    116                         "You should not supply an IV for unwrapping");
    117             }
    118         }
    119     }
    120 
    121    /**
    122     * Method getAlgorithmName
    123     *
    124     * @return the algorithm name "DESede".
    125     */
    126    public String getAlgorithmName()
    127    {
    128       return "DESede";
    129    }
    130 
    131    /**
    132     * Method wrap
    133     *
    134     * @param in byte array containing the encoded key.
    135     * @param inOff off set into in that the data starts at.
    136     * @param inLen  length of the data.
    137     * @return the wrapped bytes.
    138     */
    139    public byte[] wrap(byte[] in, int inOff, int inLen)
    140    {
    141       if (!forWrapping)
    142       {
    143          throw new IllegalStateException("Not initialized for wrapping");
    144       }
    145 
    146       byte keyToBeWrapped[] = new byte[inLen];
    147 
    148       System.arraycopy(in, inOff, keyToBeWrapped, 0, inLen);
    149 
    150       // Compute the CMS Key Checksum, (section 5.6.1), call this CKS.
    151       byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped);
    152 
    153       // Let WKCKS = WK || CKS where || is concatenation.
    154       byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length];
    155 
    156       System.arraycopy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.length);
    157       System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length, CKS.length);
    158 
    159       // Encrypt WKCKS in CBC mode using KEK as the key and IV as the
    160       // initialization vector. Call the results TEMP1.
    161 
    162       int blockSize = engine.getBlockSize();
    163 
    164       if (WKCKS.length % blockSize != 0)
    165       {
    166          throw new IllegalStateException("Not multiple of block length");
    167       }
    168 
    169       engine.init(true, paramPlusIV);
    170 
    171       byte TEMP1[] = new byte[WKCKS.length];
    172 
    173       for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize)
    174       {
    175          engine.processBlock(WKCKS, currentBytePos, TEMP1, currentBytePos);
    176       }
    177 
    178       // Let TEMP2 = IV || TEMP1.
    179       byte[] TEMP2 = new byte[this.iv.length + TEMP1.length];
    180 
    181       System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length);
    182       System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length);
    183 
    184       // Reverse the order of the octets in TEMP2 and call the result TEMP3.
    185       byte[] TEMP3 = reverse(TEMP2);
    186 
    187       // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector
    188       // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the desired
    189       // result. It is 40 octets long if a 168 bit key is being wrapped.
    190       ParametersWithIV param2 = new ParametersWithIV(this.param, IV2);
    191 
    192       this.engine.init(true, param2);
    193 
    194       for (int currentBytePos = 0; currentBytePos != TEMP3.length; currentBytePos += blockSize)
    195       {
    196          engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos);
    197       }
    198 
    199       return TEMP3;
    200    }
    201 
    202    /**
    203     * Method unwrap
    204     *
    205     * @param in byte array containing the wrapped key.
    206     * @param inOff off set into in that the data starts at.
    207     * @param inLen  length of the data.
    208     * @return the unwrapped bytes.
    209     * @throws InvalidCipherTextException
    210     */
    211     public byte[] unwrap(byte[] in, int inOff, int inLen)
    212            throws InvalidCipherTextException
    213     {
    214         if (forWrapping)
    215         {
    216             throw new IllegalStateException("Not set for unwrapping");
    217         }
    218 
    219         if (in == null)
    220         {
    221             throw new InvalidCipherTextException("Null pointer as ciphertext");
    222         }
    223 
    224         final int blockSize = engine.getBlockSize();
    225         if (inLen % blockSize != 0)
    226         {
    227             throw new InvalidCipherTextException("Ciphertext not multiple of " + blockSize);
    228         }
    229 
    230       /*
    231       // Check if the length of the cipher text is reasonable given the key
    232       // type. It must be 40 bytes for a 168 bit key and either 32, 40, or
    233       // 48 bytes for a 128, 192, or 256 bit key. If the length is not supported
    234       // or inconsistent with the algorithm for which the key is intended,
    235       // return error.
    236       //
    237       // we do not accept 168 bit keys. it has to be 192 bit.
    238       int lengthA = (estimatedKeyLengthInBit / 8) + 16;
    239       int lengthB = estimatedKeyLengthInBit % 8;
    240 
    241       if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) {
    242          throw new XMLSecurityException("empty");
    243       }
    244       */
    245 
    246       // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK
    247       // and an initialization vector (IV) of 0x4adda22c79e82105. Call the output TEMP3.
    248       ParametersWithIV param2 = new ParametersWithIV(this.param, IV2);
    249 
    250       this.engine.init(false, param2);
    251 
    252       byte TEMP3[] = new byte[inLen];
    253 
    254       for (int currentBytePos = 0; currentBytePos != inLen; currentBytePos += blockSize)
    255       {
    256          engine.processBlock(in, inOff + currentBytePos, TEMP3, currentBytePos);
    257       }
    258 
    259       // Reverse the order of the octets in TEMP3 and call the result TEMP2.
    260       byte[] TEMP2 = reverse(TEMP3);
    261 
    262       // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets.
    263       this.iv = new byte[8];
    264 
    265       byte[] TEMP1 = new byte[TEMP2.length - 8];
    266 
    267       System.arraycopy(TEMP2, 0, this.iv, 0, 8);
    268       System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8);
    269 
    270       // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV
    271       // found in the previous step. Call the result WKCKS.
    272       this.paramPlusIV = new ParametersWithIV(this.param, this.iv);
    273 
    274       this.engine.init(false, this.paramPlusIV);
    275 
    276       byte[] WKCKS = new byte[TEMP1.length];
    277 
    278       for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize)
    279       {
    280          engine.processBlock(TEMP1, currentBytePos, WKCKS, currentBytePos);
    281       }
    282 
    283       // Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are
    284       // those octets before the CKS.
    285       byte[] result = new byte[WKCKS.length - 8];
    286       byte[] CKStoBeVerified = new byte[8];
    287 
    288       System.arraycopy(WKCKS, 0, result, 0, WKCKS.length - 8);
    289       System.arraycopy(WKCKS, WKCKS.length - 8, CKStoBeVerified, 0, 8);
    290 
    291       // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and compare
    292       // with the CKS extracted in the above step. If they are not equal, return error.
    293       if (!checkCMSKeyChecksum(result, CKStoBeVerified))
    294       {
    295          throw new InvalidCipherTextException(
    296             "Checksum inside ciphertext is corrupted");
    297       }
    298 
    299       // WK is the wrapped key, now extracted for use in data decryption.
    300       return result;
    301    }
    302 
    303     /**
    304      * Some key wrap algorithms make use of the Key Checksum defined
    305      * in CMS [CMS-Algorithms]. This is used to provide an integrity
    306      * check value for the key being wrapped. The algorithm is
    307      *
    308      * - Compute the 20 octet SHA-1 hash on the key being wrapped.
    309      * - Use the first 8 octets of this hash as the checksum value.
    310      *
    311      * For details see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum.
    312      *
    313      * @param key the key to check,
    314      * @return the CMS checksum.
    315      * @throws RuntimeException
    316      */
    317     private byte[] calculateCMSKeyChecksum(
    318         byte[] key)
    319     {
    320         byte[]  result = new byte[8];
    321 
    322         sha1.update(key, 0, key.length);
    323         sha1.doFinal(digest, 0);
    324 
    325         System.arraycopy(digest, 0, result, 0, 8);
    326 
    327         return result;
    328     }
    329 
    330     /**
    331      * For details see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
    332      *
    333      * @param key key to be validated.
    334      * @param checksum the checksum.
    335      * @return true if okay, false otherwise.
    336      */
    337     private boolean checkCMSKeyChecksum(
    338         byte[] key,
    339         byte[] checksum)
    340     {
    341         return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum);
    342     }
    343 
    344     private static byte[] reverse(byte[] bs)
    345     {
    346         byte[] result = new byte[bs.length];
    347         for (int i = 0; i < bs.length; i++)
    348         {
    349            result[i] = bs[bs.length - (i + 1)];
    350         }
    351         return result;
    352     }
    353 }
    354