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