1 /*
   2  * Copyright (c) 1998, 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.io.IOException;
  29 import java.io.Serializable;
  30 import java.security.Security;
  31 import java.security.Key;
  32 import java.security.PrivateKey;
  33 import java.security.Provider;
  34 import java.security.KeyFactory;
  35 import java.security.MessageDigest;
  36 import java.security.GeneralSecurityException;
  37 import java.security.NoSuchAlgorithmException;
  38 import java.security.NoSuchProviderException;
  39 import java.security.UnrecoverableKeyException;
  40 import java.security.AlgorithmParameters;
  41 import java.security.spec.PKCS8EncodedKeySpec;
  42 
  43 import javax.crypto.Cipher;
  44 import javax.crypto.CipherSpi;
  45 import javax.crypto.SecretKey;
  46 import javax.crypto.IllegalBlockSizeException;
  47 import javax.crypto.SealedObject;
  48 import javax.crypto.spec.*;
  49 import sun.security.x509.AlgorithmId;
  50 import sun.security.util.ObjectIdentifier;
  51 
  52 /**
  53  * This class implements a protection mechanism for private keys. In JCE, we
  54  * use a stronger protection mechanism than in the JDK, because we can use
  55  * the <code>Cipher</code> class.
  56  * Private keys are protected using the JCE mechanism, and are recovered using
  57  * either the JDK or JCE mechanism, depending on how the key has been
  58  * protected. This allows us to parse Sun's keystore implementation that ships
  59  * with JDK 1.2.
  60  *
  61  * @author Jan Luehe
  62  *
  63  *
  64  * @see JceKeyStore
  65  */
  66 
  67 final class KeyProtector {
  68 
  69     // defined by SunSoft (SKI project)
  70     private static final String PBE_WITH_MD5_AND_DES3_CBC_OID
  71             = "1.3.6.1.4.1.42.2.19.1";
  72 
  73     // JavaSoft proprietary key-protection algorithm (used to protect private
  74     // keys in the keystore implementation that comes with JDK 1.2)
  75     private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1";
  76 
  77     private static final int SALT_LEN = 20; // the salt length
  78     private static final int DIGEST_LEN = 20;
  79 
  80     // the password used for protecting/recovering keys passed through this
  81     // key protector
  82     private char[] password;
  83 
  84     KeyProtector(char[] password) {
  85         if (password == null) {
  86            throw new IllegalArgumentException("password can't be null");
  87         }
  88         this.password = password;
  89     }
  90 
  91     /**
  92      * Protects the given cleartext private key, using the password provided at
  93      * construction time.
  94      */
  95     byte[] protect(PrivateKey key)
  96         throws Exception
  97     {
  98         // create a random salt (8 bytes)
  99         byte[] salt = new byte[8];
 100         SunJCE.getRandom().nextBytes(salt);
 101 
 102         // create PBE parameters from salt and iteration count
 103         PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20);
 104 
 105         // create PBE key from password
 106         PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
 107         SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
 108         pbeKeySpec.clearPassword();
 109 
 110         // encrypt private key
 111         PBEWithMD5AndTripleDESCipher cipher;
 112         cipher = new PBEWithMD5AndTripleDESCipher();
 113         cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null);
 114         byte[] plain = key.getEncoded();
 115         byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length);
 116 
 117         // wrap encrypted private key in EncryptedPrivateKeyInfo
 118         // (as defined in PKCS#8)
 119         AlgorithmParameters pbeParams =
 120             AlgorithmParameters.getInstance("PBE", SunJCE.getInstance());
 121         pbeParams.init(pbeSpec);
 122 
 123         AlgorithmId encrAlg = new AlgorithmId
 124             (new ObjectIdentifier(PBE_WITH_MD5_AND_DES3_CBC_OID), pbeParams);
 125         return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded();
 126     }
 127 
 128     /*
 129      * Recovers the cleartext version of the given key (in protected format),
 130      * using the password provided at construction time.
 131      */
 132     Key recover(EncryptedPrivateKeyInfo encrInfo)
 133         throws UnrecoverableKeyException, NoSuchAlgorithmException
 134     {
 135         byte[] plain;
 136 
 137         try {
 138             String encrAlg = encrInfo.getAlgorithm().getOID().toString();
 139             if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID)
 140                 && !encrAlg.equals(KEY_PROTECTOR_OID)) {
 141                 throw new UnrecoverableKeyException("Unsupported encryption "
 142                                                     + "algorithm");
 143             }
 144 
 145             if (encrAlg.equals(KEY_PROTECTOR_OID)) {
 146                 // JDK 1.2 style recovery
 147                 plain = recover(encrInfo.getEncryptedData());
 148             } else {
 149                 byte[] encodedParams =
 150                     encrInfo.getAlgorithm().getEncodedParams();
 151 
 152                 // parse the PBE parameters into the corresponding spec
 153                 AlgorithmParameters pbeParams =
 154                     AlgorithmParameters.getInstance("PBE");
 155                 pbeParams.init(encodedParams);
 156                 PBEParameterSpec pbeSpec =
 157                         pbeParams.getParameterSpec(PBEParameterSpec.class);
 158 
 159                 // create PBE key from password
 160                 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
 161                 SecretKey sKey =
 162                     new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
 163                 pbeKeySpec.clearPassword();
 164 
 165                 // decrypt private key
 166                 PBEWithMD5AndTripleDESCipher cipher;
 167                 cipher = new PBEWithMD5AndTripleDESCipher();
 168                 cipher.engineInit(Cipher.DECRYPT_MODE, sKey, pbeSpec, null);
 169                 plain=cipher.engineDoFinal(encrInfo.getEncryptedData(), 0,
 170                                            encrInfo.getEncryptedData().length);
 171             }
 172 
 173             // determine the private-key algorithm, and parse private key
 174             // using the appropriate key factory
 175             String oidName = new AlgorithmId
 176                 (new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName();
 177             KeyFactory kFac = KeyFactory.getInstance(oidName);
 178             return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain));
 179 
 180         } catch (NoSuchAlgorithmException ex) {
 181             // Note: this catch needed to be here because of the
 182             // later catch of GeneralSecurityException
 183             throw ex;
 184         } catch (IOException ioe) {
 185             throw new UnrecoverableKeyException(ioe.getMessage());
 186         } catch (GeneralSecurityException gse) {
 187             throw new UnrecoverableKeyException(gse.getMessage());
 188         }
 189     }
 190 
 191     /*
 192      * Recovers the cleartext version of the given key (in protected format),
 193      * using the password provided at construction time. This method implements
 194      * the recovery algorithm used by Sun's keystore implementation in
 195      * JDK 1.2.
 196      */
 197     private byte[] recover(byte[] protectedKey)
 198         throws UnrecoverableKeyException, NoSuchAlgorithmException
 199     {
 200         int i, j;
 201         byte[] digest;
 202         int numRounds;
 203         int xorOffset; // offset in xorKey where next digest will be stored
 204         int encrKeyLen; // the length of the encrpyted key
 205 
 206         MessageDigest md = MessageDigest.getInstance("SHA");
 207 
 208         // Get the salt associated with this key (the first SALT_LEN bytes of
 209         // <code>protectedKey</code>)
 210         byte[] salt = new byte[SALT_LEN];
 211         System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);
 212 
 213         // Determine the number of digest rounds
 214         encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;
 215         numRounds = encrKeyLen / DIGEST_LEN;
 216         if ((encrKeyLen % DIGEST_LEN) != 0)
 217             numRounds++;
 218 
 219         // Get the encrypted key portion and store it in "encrKey"
 220         byte[] encrKey = new byte[encrKeyLen];
 221         System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen);
 222 
 223         // Set up the byte array which will be XORed with "encrKey"
 224         byte[] xorKey = new byte[encrKey.length];
 225 
 226         // Convert password to byte array, so that it can be digested
 227         byte[] passwdBytes = new byte[password.length * 2];
 228         for (i=0, j=0; i<password.length; i++) {
 229             passwdBytes[j++] = (byte)(password[i] >> 8);
 230             passwdBytes[j++] = (byte)password[i];
 231         }
 232 
 233         // Compute the digests, and store them in "xorKey"
 234         for (i = 0, xorOffset = 0, digest = salt;
 235              i < numRounds;
 236              i++, xorOffset += DIGEST_LEN) {
 237             md.update(passwdBytes);
 238             md.update(digest);
 239             digest = md.digest();
 240             md.reset();
 241             // Copy the digest into "xorKey"
 242             if (i < numRounds - 1) {
 243                 System.arraycopy(digest, 0, xorKey, xorOffset,
 244                                  digest.length);
 245             } else {
 246                 System.arraycopy(digest, 0, xorKey, xorOffset,
 247                                  xorKey.length - xorOffset);
 248             }
 249         }
 250 
 251         // XOR "encrKey" with "xorKey", and store the result in "plainKey"
 252         byte[] plainKey = new byte[encrKey.length];
 253         for (i = 0; i < plainKey.length; i++) {
 254             plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]);
 255         }
 256 
 257         // Check the integrity of the recovered key by concatenating it with
 258         // the password, digesting the concatenation, and comparing the
 259         // result of the digest operation with the digest provided at the end
 260         // of <code>protectedKey</code>. If the two digest values are
 261         // different, throw an exception.
 262         md.update(passwdBytes);
 263         java.util.Arrays.fill(passwdBytes, (byte)0x00);
 264         passwdBytes = null;
 265         md.update(plainKey);
 266         digest = md.digest();
 267         md.reset();
 268         for (i = 0; i < digest.length; i++) {
 269             if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) {
 270                 throw new UnrecoverableKeyException("Cannot recover key");
 271             }
 272         }
 273         return plainKey;
 274     }
 275 
 276     /**
 277      * Seals the given cleartext key, using the password provided at
 278      * construction time
 279      */
 280     SealedObject seal(Key key)
 281         throws Exception
 282     {
 283         // create a random salt (8 bytes)
 284         byte[] salt = new byte[8];
 285         SunJCE.getRandom().nextBytes(salt);
 286 
 287         // create PBE parameters from salt and iteration count
 288         PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20);
 289 
 290         // create PBE key from password
 291         PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
 292         SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
 293         pbeKeySpec.clearPassword();
 294 
 295         // seal key
 296         Cipher cipher;
 297 
 298         PBEWithMD5AndTripleDESCipher cipherSpi;
 299         cipherSpi = new PBEWithMD5AndTripleDESCipher();
 300         cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(),
 301                                            "PBEWithMD5AndTripleDES");
 302         cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec);
 303         return new SealedObjectForKeyProtector(key, cipher);
 304     }
 305 
 306     /**
 307      * Unseals the sealed key.
 308      */
 309     Key unseal(SealedObject so)
 310         throws NoSuchAlgorithmException, UnrecoverableKeyException
 311     {
 312         try {
 313             // create PBE key from password
 314             PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
 315             SecretKey skey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
 316             pbeKeySpec.clearPassword();
 317 
 318             SealedObjectForKeyProtector soForKeyProtector = null;
 319             if (!(so instanceof SealedObjectForKeyProtector)) {
 320                 soForKeyProtector = new SealedObjectForKeyProtector(so);
 321             } else {
 322                 soForKeyProtector = (SealedObjectForKeyProtector)so;
 323             }
 324             AlgorithmParameters params = soForKeyProtector.getParameters();
 325             if (params == null) {
 326                 throw new UnrecoverableKeyException("Cannot get " +
 327                                                     "algorithm parameters");
 328             }
 329             PBEWithMD5AndTripleDESCipher cipherSpi;
 330             cipherSpi = new PBEWithMD5AndTripleDESCipher();
 331             Cipher cipher = new CipherForKeyProtector(cipherSpi,
 332                                                       SunJCE.getInstance(),
 333                                                       "PBEWithMD5AndTripleDES");
 334             cipher.init(Cipher.DECRYPT_MODE, skey, params);
 335             return (Key)soForKeyProtector.getObject(cipher);
 336         } catch (NoSuchAlgorithmException ex) {
 337             // Note: this catch needed to be here because of the
 338             // later catch of GeneralSecurityException
 339             throw ex;
 340         } catch (IOException ioe) {
 341             throw new UnrecoverableKeyException(ioe.getMessage());
 342         } catch (ClassNotFoundException cnfe) {
 343             throw new UnrecoverableKeyException(cnfe.getMessage());
 344         } catch (GeneralSecurityException gse) {
 345             throw new UnrecoverableKeyException(gse.getMessage());
 346         }
 347     }
 348 }
 349 
 350 
 351 final class CipherForKeyProtector extends javax.crypto.Cipher {
 352     /**
 353      * Creates a Cipher object.
 354      *
 355      * @param cipherSpi the delegate
 356      * @param provider the provider
 357      * @param transformation the transformation
 358      */
 359     protected CipherForKeyProtector(CipherSpi cipherSpi,
 360                                     Provider provider,
 361                                     String transformation) {
 362         super(cipherSpi, provider, transformation);
 363     }
 364 }