View Javadoc
1   /*
2    * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.crypto.provider;
27  
28  import java.security.*;
29  import java.security.spec.*;
30  import javax.crypto.*;
31  import javax.crypto.spec.*;
32  
33  /**
34   * This class implements the CMS DESede KeyWrap algorithm as defined
35   * in <a href=http://www.w3.org/TR/xmlenc-core/#sec-Alg-SymmetricKeyWrap>
36   * "XML Encryption Syntax and Processing" section 5.6.2
37   * "CMS Triple DES Key Wrap".
38   * Note: only <code>CBC</code> mode and <code>NoPadding</code> padding
39   * scheme can be used for this algorithm.
40   *
41   * @author Valerie Peng
42   *
43   *
44   * @see DESedeCipher
45   */
46  public final class DESedeWrapCipher extends CipherSpi {
47  
48      private static final byte[] IV2 = {
49          (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, (byte) 0x2c,
50          (byte) 0x79, (byte) 0xe8, (byte) 0x21, (byte) 0x05
51      };
52  
53      private static final int CHECKSUM_LEN = 8;
54      private static final int IV_LEN = 8;
55  
56      /*
57       * internal cipher object which does the real work.
58       */
59      private FeedbackCipher cipher;
60  
61      /*
62       * iv for (re-)initializing the internal cipher object.
63       */
64      private byte[] iv = null;
65  
66      /*
67       * key for re-initializing the internal cipher object.
68       */
69      private Key cipherKey = null;
70  
71      /*
72       * are we encrypting or decrypting?
73       */
74      private boolean decrypting = false;
75  
76      /**
77       * Creates an instance of CMS DESede KeyWrap cipher with default
78       * mode, i.e. "CBC" and padding scheme, i.e. "NoPadding".
79       */
80      public DESedeWrapCipher() {
81          cipher = new CipherBlockChaining(new DESedeCrypt());
82      }
83  
84      /**
85       * Sets the mode of this cipher. Only "CBC" mode is accepted for this
86       * cipher.
87       *
88       * @param mode the cipher mode.
89       *
90       * @exception NoSuchAlgorithmException if the requested cipher mode
91       * is not "CBC".
92       */
93      protected void engineSetMode(String mode)
94          throws NoSuchAlgorithmException {
95          if (!mode.equalsIgnoreCase("CBC")) {
96              throw new NoSuchAlgorithmException(mode + " cannot be used");
97          }
98      }
99  
100     /**
101      * Sets the padding mechanism of this cipher. Only "NoPadding" schmem
102      * is accepted for this cipher.
103      *
104      * @param padding the padding mechanism.
105      *
106      * @exception NoSuchPaddingException if the requested padding mechanism
107      * is not "NoPadding".
108      */
109     protected void engineSetPadding(String padding)
110         throws NoSuchPaddingException {
111         if (!padding.equalsIgnoreCase("NoPadding")) {
112             throw new NoSuchPaddingException(padding + " cannot be used");
113         }
114     }
115 
116     /**
117      * Returns the block size (in bytes), i.e. 8 bytes.
118      *
119      * @return the block size (in bytes), i.e. 8 bytes.
120      */
121     protected int engineGetBlockSize() {
122         return DESConstants.DES_BLOCK_SIZE;
123     }
124 
125     /**
126      * Returns the length in bytes that an output buffer would need to be
127      * given the input length <code>inputLen</code> (in bytes).
128      *
129      * <p>The actual output length of the next <code>update</code> or
130      * <code>doFinal</code> call may be smaller than the length returned
131      * by this method.
132      *
133      * @param inputLen the input length (in bytes).
134      *
135      * @return the required output buffer size (in bytes).
136      */
137     protected int engineGetOutputSize(int inputLen) {
138         // can only return an upper-limit if not initialized yet.
139         int result = 0;
140         if (decrypting) {
141             result = inputLen - 16; // CHECKSUM_LEN + IV_LEN;
142         } else {
143             result = inputLen + 16;
144         }
145         return (result < 0? 0:result);
146     }
147 
148     /**
149      * Returns the initialization vector (IV) in a new buffer.
150      *
151      * @return the initialization vector, or null if the underlying
152      * algorithm does not use an IV, or if the IV has not yet
153      * been set.
154      */
155     protected byte[] engineGetIV() {
156         return (iv == null) ? null : iv.clone();
157     }
158 
159     /**
160      * Initializes this cipher with a key and a source of randomness.
161      *
162      * <p>The cipher only supports the following two operation modes:<b>
163      * Cipher.WRAP_MODE, and <b>
164      * Cipher.UNWRAP_MODE.
165      * <p>For modes other than the above two, UnsupportedOperationException
166      * will be thrown.
167      * <p>If this cipher requires an initialization vector (IV), it will get
168      * it from <code>random</code>.
169      *
170      * @param opmode the operation mode of this cipher. Only
171      * <code>WRAP_MODE</code> or <code>UNWRAP_MODE</code>) are accepted.
172      * @param key the secret key.
173      * @param random the source of randomness.
174      *
175      * @exception InvalidKeyException if the given key is inappropriate
176      * or if parameters are required but not supplied.
177      */
178     protected void engineInit(int opmode, Key key, SecureRandom random)
179         throws InvalidKeyException {
180         try {
181             engineInit(opmode, key, (AlgorithmParameterSpec) null, random);
182         } catch (InvalidAlgorithmParameterException iape) {
183             // should never happen
184             InvalidKeyException ike =
185                 new InvalidKeyException("Parameters required");
186             ike.initCause(iape);
187             throw ike;
188         }
189     }
190 
191     /**
192      * Initializes this cipher with a key, a set of algorithm parameters,
193      * and a source of randomness.
194      *
195      * <p>The cipher only supports the following two operation modes:<b>
196      * Cipher.WRAP_MODE, and <b>
197      * Cipher.UNWRAP_MODE.
198      * <p>For modes other than the above two, UnsupportedOperationException
199      * will be thrown.
200      * <p>If this cipher requires an initialization vector (IV), it will get
201      * it from <code>random</code>.
202      *
203      * @param opmode the operation mode of this cipher. Only
204      * <code>WRAP_MODE</code> or <code>UNWRAP_MODE</code>) are accepted.
205      * @param key the secret key.
206      * @param params the algorithm parameters.
207      * @param random the source of randomness.
208      *
209      * @exception InvalidKeyException if the given key is inappropriate.
210      * @exception InvalidAlgorithmParameterException if the given algorithm
211      * parameters are inappropriate for this cipher.
212      */
213     protected void engineInit(int opmode, Key key,
214                               AlgorithmParameterSpec params,
215                               SecureRandom random)
216         throws InvalidKeyException, InvalidAlgorithmParameterException {
217         byte[] currIv = null;
218         if (opmode == Cipher.WRAP_MODE) {
219             decrypting = false;
220             if (params == null) {
221                 iv = new byte[IV_LEN];
222                 if (random == null) {
223                     random = SunJCE.getRandom();
224                 }
225                 random.nextBytes(iv);
226             }
227             else if (params instanceof IvParameterSpec) {
228                 iv = ((IvParameterSpec) params).getIV();
229             } else {
230                 throw new InvalidAlgorithmParameterException
231                     ("Wrong parameter type: IV expected");
232             }
233             currIv = iv;
234         } else if (opmode == Cipher.UNWRAP_MODE) {
235             if (params != null) {
236                 throw new InvalidAlgorithmParameterException
237                     ("No parameter accepted for unwrapping keys");
238             }
239             iv = null;
240             decrypting = true;
241             currIv = IV2;
242         } else {
243             throw new UnsupportedOperationException("This cipher can " +
244                 "only be used for key wrapping and unwrapping");
245         }
246         cipher.init(decrypting, key.getAlgorithm(), key.getEncoded(),
247                     currIv);
248         cipherKey = key;
249     }
250 
251     /**
252      * Initializes this cipher with a key, a set of algorithm parameters,
253      * and a source of randomness.
254      *
255      * <p>The cipher only supports the following two operation modes:<b>
256      * Cipher.WRAP_MODE, and <b>
257      * Cipher.UNWRAP_MODE.
258      * <p>For modes other than the above two, UnsupportedOperationException
259      * will be thrown.
260      * <p>If this cipher requires an initialization vector (IV), it will get
261      * it from <code>random</code>.
262      *
263      * @param opmode the operation mode of this cipher. Only
264      * <code>WRAP_MODE</code> or <code>UNWRAP_MODE</code>) are accepted.
265      * @param key the secret key.
266      * @param params the algorithm parameters.
267      * @param random the source of randomness.
268      *
269      * @exception InvalidKeyException if the given key is inappropriate.
270      * @exception InvalidAlgorithmParameterException if the given algorithm
271      * parameters are inappropriate for this cipher.
272      */
273     protected void engineInit(int opmode, Key key,
274                               AlgorithmParameters params,
275                               SecureRandom random)
276         throws InvalidKeyException, InvalidAlgorithmParameterException {
277         IvParameterSpec ivSpec = null;
278         if (params != null) {
279             try {
280                 DESedeParameters paramsEng = new DESedeParameters();
281                 paramsEng.engineInit(params.getEncoded());
282                 ivSpec = paramsEng.engineGetParameterSpec(IvParameterSpec.class);
283             } catch (Exception ex) {
284                 InvalidAlgorithmParameterException iape =
285                     new InvalidAlgorithmParameterException
286                         ("Wrong parameter type: IV expected");
287                 iape.initCause(ex);
288                 throw iape;
289             }
290         }
291         engineInit(opmode, key, ivSpec, random);
292     }
293 
294     /**
295      * This operation is not supported by this cipher.
296      * Since it's impossible to initialize this cipher given the
297      * current Cipher.engineInit(...) implementation,
298      * IllegalStateException will always be thrown upon invocation.
299      *
300      * @param in the input buffer.
301      * @param inOffset the offset in <code>in</code> where the input
302      * starts.
303      * @param inLen the input length.
304      *
305      * @return n/a.
306      *
307      * @exception IllegalStateException upon invocation of this method.
308      */
309     protected byte[] engineUpdate(byte[] in, int inOffset, int inLen) {
310         throw new IllegalStateException("Cipher has not been initialized");
311     }
312 
313     /**
314      * This operation is not supported by this cipher.
315      * Since it's impossible to initialize this cipher given the
316      * current Cipher.engineInit(...) implementation,
317      * IllegalStateException will always be thrown upon invocation.
318      *
319      * @param in the input buffer.
320      * @param inOffset the offset in <code>in</code> where the input
321      * starts.
322      * @param inLen the input length.
323      * @param out the buffer for the result.
324      * @param outOffset the offset in <code>out</code> where the result
325      * is stored.
326      *
327      * @return n/a.
328      *
329      * @exception IllegalStateException upon invocation of this method.
330      */
331     protected int engineUpdate(byte[] in, int inOffset, int inLen,
332                                byte[] out, int outOffset)
333         throws ShortBufferException {
334         throw new IllegalStateException("Cipher has not been initialized");
335     }
336 
337     /**
338      * This operation is not supported by this cipher.
339      * Since it's impossible to initialize this cipher given the
340      * current Cipher.engineInit(...) implementation,
341      * IllegalStateException will always be thrown upon invocation.
342      *
343      * @param in the input buffer.
344      * @param inOffset the offset in <code>in</code> where the input
345      * starts.
346      * @param inLen the input length.
347      *
348      * @return the new buffer with the result.
349      *
350      * @exception IllegalStateException upon invocation of this method.
351      */
352     protected byte[] engineDoFinal(byte[] in, int inOffset, int inLen)
353         throws IllegalBlockSizeException, BadPaddingException {
354         throw new IllegalStateException("Cipher has not been initialized");
355     }
356 
357     /**
358      * This operation is not supported by this cipher.
359      * Since it's impossible to initialize this cipher given the
360      * current Cipher.engineInit(...) implementation,
361      * IllegalStateException will always be thrown upon invocation.
362      *
363      * @param in the input buffer.
364      * @param inOffset the offset in <code>in</code> where the input
365      * starts.
366      * @param inLen the input length.
367      * @param out the buffer for the result.
368      * @param outOffset the ofset in <code>out</code> where the result
369      * is stored.
370      *
371      * @return the number of bytes stored in <code>out</code>.
372      *
373      * @exception IllegalStateException upon invocation of this method.
374      */
375     protected int engineDoFinal(byte[] input, int inputOffset, int inputLen,
376                                 byte[] output, int outputOffset)
377         throws IllegalBlockSizeException, ShortBufferException,
378                BadPaddingException {
379         throw new IllegalStateException("Cipher has not been initialized");
380     }
381 
382     /**
383      * Returns the parameters used with this cipher.
384      * Note that null maybe returned if this cipher does not use any
385      * parameters or when it has not be set, e.g. initialized with
386      * UNWRAP_MODE but wrapped key data has not been given.
387      *
388      * @return the parameters used with this cipher; can be null.
389      */
390     protected AlgorithmParameters engineGetParameters() {
391         AlgorithmParameters params = null;
392         if (iv != null) {
393             String algo = cipherKey.getAlgorithm();
394             try {
395                 params = AlgorithmParameters.getInstance(algo,
396                     SunJCE.getInstance());
397                 params.init(new IvParameterSpec(iv));
398             } catch (NoSuchAlgorithmException nsae) {
399                 // should never happen
400                 throw new RuntimeException("Cannot find " + algo +
401                     " AlgorithmParameters implementation in SunJCE provider");
402             } catch (InvalidParameterSpecException ipse) {
403                 // should never happen
404                 throw new RuntimeException("IvParameterSpec not supported");
405             }
406         }
407         return params;
408     }
409 
410     /**
411      * Returns the key size of the given key object in number of bits.
412      * This cipher always return the same key size as the DESede ciphers.
413      *
414      * @param key the key object.
415      *
416      * @return the "effective" key size of the given key object.
417      *
418      * @exception InvalidKeyException if <code>key</code> is invalid.
419      */
420     protected int engineGetKeySize(Key key) throws InvalidKeyException {
421         byte[] encoded = key.getEncoded();
422         if (encoded.length != 24) {
423             throw new InvalidKeyException("Invalid key length: " +
424                 encoded.length + " bytes");
425         }
426         // Return the effective key length
427         return 112;
428     }
429 
430     /**
431      * Wrap a key.
432      *
433      * @param key the key to be wrapped.
434      *
435      * @return the wrapped key.
436      *
437      * @exception IllegalBlockSizeException if this cipher is a block
438      * cipher, no padding has been requested, and the length of the
439      * encoding of the key to be wrapped is not a
440      * multiple of the block size.
441      *
442      * @exception InvalidKeyException if it is impossible or unsafe to
443      * wrap the key with this cipher (e.g., a hardware protected key is
444      * being passed to a software only cipher).
445      */
446     protected byte[] engineWrap(Key key)
447         throws IllegalBlockSizeException, InvalidKeyException {
448         byte[] keyVal = key.getEncoded();
449         if ((keyVal == null) || (keyVal.length == 0)) {
450             throw new InvalidKeyException("Cannot get an encoding of " +
451                                           "the key to be wrapped");
452         }
453 
454         byte[] cks = getChecksum(keyVal);
455         byte[] in = new byte[keyVal.length + CHECKSUM_LEN];
456         System.arraycopy(keyVal, 0, in, 0, keyVal.length);
457         System.arraycopy(cks, 0, in, keyVal.length, CHECKSUM_LEN);
458 
459         byte[] out = new byte[iv.length + in.length];
460         System.arraycopy(iv, 0, out, 0, iv.length);
461 
462         cipher.encrypt(in, 0, in.length, out, iv.length);
463 
464         // reverse the array content
465         for (int i = 0; i < out.length/2; i++) {
466             byte temp = out[i];
467             out[i] = out[out.length-1-i];
468             out[out.length-1-i] = temp;
469         }
470         try {
471             cipher.init(false, cipherKey.getAlgorithm(),
472                         cipherKey.getEncoded(), IV2);
473         } catch (InvalidKeyException ike) {
474             // should never happen
475             throw new RuntimeException("Internal cipher key is corrupted");
476         }
477         byte[] out2 = new byte[out.length];
478         cipher.encrypt(out, 0, out.length, out2, 0);
479 
480         // restore cipher state to prior to this call
481         try {
482             cipher.init(decrypting, cipherKey.getAlgorithm(),
483                         cipherKey.getEncoded(), iv);
484         } catch (InvalidKeyException ike) {
485             // should never happen
486             throw new RuntimeException("Internal cipher key is corrupted");
487         }
488         return out2;
489     }
490 
491     /**
492      * Unwrap a previously wrapped key.
493      *
494      * @param wrappedKey the key to be unwrapped.
495      *
496      * @param wrappedKeyAlgorithm the algorithm the wrapped key is for.
497      *
498      * @param wrappedKeyType the type of the wrapped key.
499      * This is one of <code>Cipher.SECRET_KEY</code>,
500      * <code>Cipher.PRIVATE_KEY</code>, or <code>Cipher.PUBLIC_KEY</code>.
501      *
502      * @return the unwrapped key.
503      *
504      * @exception NoSuchAlgorithmException if no installed providers
505      * can create keys of type <code>wrappedKeyType</code> for the
506      * <code>wrappedKeyAlgorithm</code>.
507      *
508      * @exception InvalidKeyException if <code>wrappedKey</code> does not
509      * represent a wrapped key of type <code>wrappedKeyType</code> for
510      * the <code>wrappedKeyAlgorithm</code>.
511      */
512     protected Key engineUnwrap(byte[] wrappedKey,
513                                String wrappedKeyAlgorithm,
514                                int wrappedKeyType)
515         throws InvalidKeyException, NoSuchAlgorithmException {
516         if (wrappedKey.length == 0) {
517             throw new InvalidKeyException("The wrapped key is empty");
518         }
519         byte[] buffer = new byte[wrappedKey.length];
520         cipher.decrypt(wrappedKey, 0, wrappedKey.length, buffer, 0);
521 
522         // reverse array content
523         for (int i = 0; i < buffer.length/2; i++) {
524             byte temp = buffer[i];
525             buffer[i] = buffer[buffer.length-1-i];
526             buffer[buffer.length-1-i] = temp;
527         }
528         iv = new byte[IV_LEN];
529         System.arraycopy(buffer, 0, iv, 0, iv.length);
530         cipher.init(true, cipherKey.getAlgorithm(), cipherKey.getEncoded(),
531                     iv);
532         byte[] buffer2 = new byte[buffer.length - iv.length];
533         cipher.decrypt(buffer, iv.length, buffer2.length,
534                        buffer2, 0);
535         int keyValLen = buffer2.length - CHECKSUM_LEN;
536         byte[] cks = getChecksum(buffer2, 0, keyValLen);
537         int offset = keyValLen;
538         for (int i = 0; i < CHECKSUM_LEN; i++) {
539             if (buffer2[offset + i] != cks[i]) {
540                 throw new InvalidKeyException("Checksum comparison failed");
541             }
542         }
543         // restore cipher state to prior to this call
544         cipher.init(decrypting, cipherKey.getAlgorithm(),
545                     cipherKey.getEncoded(), IV2);
546         byte[] out = new byte[keyValLen];
547         System.arraycopy(buffer2, 0, out, 0, keyValLen);
548         return ConstructKeys.constructKey(out, wrappedKeyAlgorithm,
549                                           wrappedKeyType);
550     }
551 
552     private static final byte[] getChecksum(byte[] in) {
553         return getChecksum(in, 0, in.length);
554     }
555     private static final byte[] getChecksum(byte[] in, int offset, int len) {
556         MessageDigest md = null;
557         try {
558             md = MessageDigest.getInstance("SHA1");
559         } catch (NoSuchAlgorithmException nsae) {
560             throw new RuntimeException("SHA1 message digest not available");
561         }
562         md.update(in, offset, len);
563         byte[] cks = new byte[CHECKSUM_LEN];
564         System.arraycopy(md.digest(), 0, cks, 0, cks.length);
565         return cks;
566     }
567 }