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