Save This Page
Home » openjdk-7 » com.sun.crypto » provider » [javadoc | source]
    1   /*
    2    * Copyright (c) 1998, 2007, 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.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   }

Save This Page
Home » openjdk-7 » com.sun.crypto » provider » [javadoc | source]