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