1 /*
   2  * Copyright (c) 1998, 2019, 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.security.Key;
  30 import java.security.PrivateKey;
  31 import java.security.Provider;
  32 import java.security.KeyFactory;
  33 import java.security.MessageDigest;
  34 import java.security.GeneralSecurityException;
  35 import java.security.NoSuchAlgorithmException;
  36 import java.security.UnrecoverableKeyException;
  37 import java.security.AlgorithmParameters;
  38 import java.security.spec.InvalidParameterSpecException;
  39 import java.security.spec.PKCS8EncodedKeySpec;
  40 import java.util.Arrays;
  41 
  42 import javax.crypto.Cipher;
  43 import javax.crypto.CipherSpi;
  44 import javax.crypto.SecretKey;
  45 import javax.crypto.SealedObject;
  46 import javax.crypto.spec.*;
  47 import javax.security.auth.DestroyFailedException;
  48 
  49 import sun.security.x509.AlgorithmId;
  50 import sun.security.util.ObjectIdentifier;
  51 import sun.security.util.SecurityProperties;
  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 MIN_ITERATION_COUNT = 10000;
  80     private static final int DEFAULT_ITERATION_COUNT = 200000;
  81     private static final int SALT_LEN = 20; // the salt length
  82     private static final int DIGEST_LEN = 20;
  83     private static final int ITERATION_COUNT;
  84 
  85     // the password used for protecting/recovering keys passed through this
  86     // key protector
  87     private char[] password;
  88 
  89     /**
  90      * {@systemProperty jdk.jceks.iterationCount} property indicating the
  91      * number of iterations for password-based encryption (PBE) in JCEKS
  92      * keystores. Values in the range 10000 to 5000000 are considered valid.
  93      * If the value is out of this range, or is not a number, or is
  94      * unspecified; a default of 200000 is used.
  95      */
  96     static {
  97         int iterationCount = DEFAULT_ITERATION_COUNT;
  98         String ic = SecurityProperties.privilegedGetOverridable(
  99                 "jdk.jceks.iterationCount");
 100         if (ic != null && !ic.isEmpty()) {
 101             try {
 102                 iterationCount = Integer.parseInt(ic);
 103                 if (iterationCount < MIN_ITERATION_COUNT ||
 104                         iterationCount > MAX_ITERATION_COUNT) {
 105                     iterationCount = DEFAULT_ITERATION_COUNT;
 106                 }
 107             } catch (NumberFormatException e) {}
 108         }
 109         ITERATION_COUNT = iterationCount;
 110     }
 111 
 112     KeyProtector(char[] password) {
 113         if (password == null) {
 114            throw new IllegalArgumentException("password can't be null");
 115         }
 116         this.password = password;
 117     }
 118 
 119     /**
 120      * Protects the given cleartext private key, using the password provided at
 121      * construction time.
 122      */
 123     byte[] protect(PrivateKey key)
 124         throws Exception
 125     {
 126         // create a random salt (8 bytes)
 127         byte[] salt = new byte[8];
 128         SunJCE.getRandom().nextBytes(salt);
 129 
 130         // create PBE parameters from salt and iteration count
 131         PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
 132 
 133         // create PBE key from password
 134         PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
 135         SecretKey sKey = null;
 136         PBEWithMD5AndTripleDESCipher cipher;
 137         try {
 138             sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
 139             // encrypt private key
 140             cipher = new PBEWithMD5AndTripleDESCipher();
 141             cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null);
 142         } finally {
 143             pbeKeySpec.clearPassword();
 144             if (sKey != null) sKey.destroy();
 145         }
 146         byte[] plain = key.getEncoded();
 147         byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length);
 148         Arrays.fill(plain, (byte) 0x00);
 149 
 150         // wrap encrypted private key in EncryptedPrivateKeyInfo
 151         // (as defined in PKCS#8)
 152         AlgorithmParameters pbeParams =
 153             AlgorithmParameters.getInstance("PBE", SunJCE.getInstance());
 154         pbeParams.init(pbeSpec);
 155 
 156         AlgorithmId encrAlg = new AlgorithmId
 157             (new ObjectIdentifier(PBE_WITH_MD5_AND_DES3_CBC_OID), pbeParams);
 158         return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded();
 159     }
 160 
 161     /*
 162      * Recovers the cleartext version of the given key (in protected format),
 163      * using the password provided at construction time.
 164      */
 165     Key recover(EncryptedPrivateKeyInfo encrInfo)
 166         throws UnrecoverableKeyException, NoSuchAlgorithmException
 167     {
 168         byte[] plain = null;
 169         SecretKey sKey = null;
 170         try {
 171             String encrAlg = encrInfo.getAlgorithm().getOID().toString();
 172             if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID)
 173                 && !encrAlg.equals(KEY_PROTECTOR_OID)) {
 174                 throw new UnrecoverableKeyException("Unsupported encryption "
 175                                                     + "algorithm");
 176             }
 177 
 178             if (encrAlg.equals(KEY_PROTECTOR_OID)) {
 179                 // JDK 1.2 style recovery
 180                 plain = recover(encrInfo.getEncryptedData());
 181             } else {
 182                 byte[] encodedParams =
 183                     encrInfo.getAlgorithm().getEncodedParams();
 184 
 185                 // parse the PBE parameters into the corresponding spec
 186                 AlgorithmParameters pbeParams =
 187                     AlgorithmParameters.getInstance("PBE");
 188                 pbeParams.init(encodedParams);
 189                 PBEParameterSpec pbeSpec =
 190                         pbeParams.getParameterSpec(PBEParameterSpec.class);
 191                 if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) {
 192                     throw new IOException("PBE iteration count too large");
 193                 }
 194 
 195                 // create PBE key from password
 196                 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
 197                 sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
 198                 pbeKeySpec.clearPassword();
 199 
 200                 // decrypt private key
 201                 PBEWithMD5AndTripleDESCipher cipher;
 202                 cipher = new PBEWithMD5AndTripleDESCipher();
 203                 cipher.engineInit(Cipher.DECRYPT_MODE, sKey, pbeSpec, null);
 204                 plain=cipher.engineDoFinal(encrInfo.getEncryptedData(), 0,
 205                                            encrInfo.getEncryptedData().length);
 206             }
 207 
 208             // determine the private-key algorithm, and parse private key
 209             // using the appropriate key factory
 210             String oidName = new AlgorithmId
 211                 (new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName();
 212             KeyFactory kFac = KeyFactory.getInstance(oidName);
 213             return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain));
 214         } catch (NoSuchAlgorithmException ex) {
 215             // Note: this catch needed to be here because of the
 216             // later catch of GeneralSecurityException
 217             throw ex;
 218         } catch (IOException ioe) {
 219             throw new UnrecoverableKeyException(ioe.getMessage());
 220         } catch (GeneralSecurityException gse) {
 221             throw new UnrecoverableKeyException(gse.getMessage());
 222         } finally {
 223             if (plain != null) Arrays.fill(plain, (byte) 0x00);
 224             if (sKey != null) {
 225                 try {
 226                     sKey.destroy();
 227                 } catch (DestroyFailedException e) {
 228                     //shouldn't happen
 229                 }
 230             }
 231         }
 232     }
 233 
 234     /*
 235      * Recovers the cleartext version of the given key (in protected format),
 236      * using the password provided at construction time. This method implements
 237      * the recovery algorithm used by Sun's keystore implementation in
 238      * JDK 1.2.
 239      */
 240     private byte[] recover(byte[] protectedKey)
 241         throws UnrecoverableKeyException, NoSuchAlgorithmException
 242     {
 243         int i, j;
 244         byte[] digest;
 245         int numRounds;
 246         int xorOffset; // offset in xorKey where next digest will be stored
 247         int encrKeyLen; // the length of the encrpyted key
 248 
 249         MessageDigest md = MessageDigest.getInstance("SHA");
 250 
 251         // Get the salt associated with this key (the first SALT_LEN bytes of
 252         // <code>protectedKey</code>)
 253         byte[] salt = new byte[SALT_LEN];
 254         System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);
 255 
 256         // Determine the number of digest rounds
 257         encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;
 258         numRounds = encrKeyLen / DIGEST_LEN;
 259         if ((encrKeyLen % DIGEST_LEN) != 0)
 260             numRounds++;
 261 
 262         // Get the encrypted key portion and store it in "encrKey"
 263         byte[] encrKey = new byte[encrKeyLen];
 264         System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen);
 265 
 266         // Set up the byte array which will be XORed with "encrKey"
 267         byte[] xorKey = new byte[encrKey.length];
 268 
 269         // Convert password to byte array, so that it can be digested
 270         byte[] passwdBytes = new byte[password.length * 2];
 271         for (i=0, j=0; i<password.length; i++) {
 272             passwdBytes[j++] = (byte)(password[i] >> 8);
 273             passwdBytes[j++] = (byte)password[i];
 274         }
 275 
 276         // Compute the digests, and store them in "xorKey"
 277         for (i = 0, xorOffset = 0, digest = salt;
 278              i < numRounds;
 279              i++, xorOffset += DIGEST_LEN) {
 280             md.update(passwdBytes);
 281             md.update(digest);
 282             digest = md.digest();
 283             md.reset();
 284             // Copy the digest into "xorKey"
 285             if (i < numRounds - 1) {
 286                 System.arraycopy(digest, 0, xorKey, xorOffset,
 287                                  digest.length);
 288             } else {
 289                 System.arraycopy(digest, 0, xorKey, xorOffset,
 290                                  xorKey.length - xorOffset);
 291             }
 292         }
 293 
 294         // XOR "encrKey" with "xorKey", and store the result in "plainKey"
 295         byte[] plainKey = new byte[encrKey.length];
 296         for (i = 0; i < plainKey.length; i++) {
 297             plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]);
 298         }
 299 
 300         // Check the integrity of the recovered key by concatenating it with
 301         // the password, digesting the concatenation, and comparing the
 302         // result of the digest operation with the digest provided at the end
 303         // of <code>protectedKey</code>. If the two digest values are
 304         // different, throw an exception.
 305         md.update(passwdBytes);
 306         Arrays.fill(passwdBytes, (byte)0x00);
 307         passwdBytes = null;
 308         md.update(plainKey);
 309         digest = md.digest();
 310         md.reset();
 311         for (i = 0; i < digest.length; i++) {
 312             if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) {
 313                 throw new UnrecoverableKeyException("Cannot recover key");
 314             }
 315         }
 316         return plainKey;
 317     }
 318 
 319     /**
 320      * Seals the given cleartext key, using the password provided at
 321      * construction time
 322      */
 323     SealedObject seal(Key key)
 324         throws Exception
 325     {
 326         // create a random salt (8 bytes)
 327         byte[] salt = new byte[8];
 328         SunJCE.getRandom().nextBytes(salt);
 329 
 330         // create PBE parameters from salt and iteration count
 331         PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
 332 
 333         // create PBE key from password
 334         PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
 335         SecretKey sKey = null;
 336         Cipher cipher;
 337         try {
 338             sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
 339             pbeKeySpec.clearPassword();
 340 
 341             // seal key
 342             PBEWithMD5AndTripleDESCipher cipherSpi;
 343             cipherSpi = new PBEWithMD5AndTripleDESCipher();
 344             cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(),
 345                                                "PBEWithMD5AndTripleDES");
 346             cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec);
 347         } finally {
 348             if (sKey != null) sKey.destroy();
 349         }
 350         return new SealedObjectForKeyProtector(key, cipher);
 351     }
 352 
 353     /**
 354      * Unseals the sealed key.
 355      *
 356      * @param maxLength Maximum possible length of so.
 357      *                  If bigger, must be illegal.
 358      */
 359     Key unseal(SealedObject so, int maxLength)
 360         throws NoSuchAlgorithmException, UnrecoverableKeyException {
 361         SecretKey sKey = null;
 362         try {
 363             // create PBE key from password
 364             PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
 365             sKey = new PBEKey(pbeKeySpec,
 366                     "PBEWithMD5AndTripleDES", false);
 367             pbeKeySpec.clearPassword();
 368 
 369             SealedObjectForKeyProtector soForKeyProtector = null;
 370             if (!(so instanceof SealedObjectForKeyProtector)) {
 371                 soForKeyProtector = new SealedObjectForKeyProtector(so);
 372             } else {
 373                 soForKeyProtector = (SealedObjectForKeyProtector)so;
 374             }
 375             AlgorithmParameters params = soForKeyProtector.getParameters();
 376             if (params == null) {
 377                 throw new UnrecoverableKeyException("Cannot get " +
 378                                                     "algorithm parameters");
 379             }
 380             PBEParameterSpec pbeSpec;
 381             try {
 382                 pbeSpec = params.getParameterSpec(PBEParameterSpec.class);
 383             } catch (InvalidParameterSpecException ipse) {
 384                 throw new IOException("Invalid PBE algorithm parameters");
 385             }
 386             if (pbeSpec.getIterationCount() > MAX_ITERATION_COUNT) {
 387                 throw new IOException("PBE iteration count too large");
 388             }
 389             PBEWithMD5AndTripleDESCipher cipherSpi;
 390             cipherSpi = new PBEWithMD5AndTripleDESCipher();
 391             Cipher cipher = new CipherForKeyProtector(cipherSpi,
 392                                                       SunJCE.getInstance(),
 393                                                       "PBEWithMD5AndTripleDES");
 394             cipher.init(Cipher.DECRYPT_MODE, sKey, params);
 395             return soForKeyProtector.getKey(cipher, maxLength);
 396         } catch (NoSuchAlgorithmException ex) {
 397             // Note: this catch needed to be here because of the
 398             // later catch of GeneralSecurityException
 399             throw ex;
 400         } catch (IOException ioe) {
 401             throw new UnrecoverableKeyException(ioe.getMessage());
 402         } catch (ClassNotFoundException cnfe) {
 403             throw new UnrecoverableKeyException(cnfe.getMessage());
 404         } catch (GeneralSecurityException gse) {
 405             throw new UnrecoverableKeyException(gse.getMessage());
 406         } finally {
 407             if (sKey != null) {
 408                 try {
 409                     sKey.destroy();
 410                 } catch (DestroyFailedException e) {
 411                     //shouldn't happen
 412                 }
 413             }
 414         }
 415     }
 416 }
 417 
 418 
 419 final class CipherForKeyProtector extends javax.crypto.Cipher {
 420     /**
 421      * Creates a Cipher object.
 422      *
 423      * @param cipherSpi the delegate
 424      * @param provider the provider
 425      * @param transformation the transformation
 426      */
 427     protected CipherForKeyProtector(CipherSpi cipherSpi,
 428                                     Provider provider,
 429                                     String transformation) {
 430         super(cipherSpi, provider, transformation);
 431     }
 432 }