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 }