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