# HG changeset patch # User vinnie # Date 1358976349 0 # Node ID eb3d7b36b4c4dbdbb5f57bdbc4a916e94835309c # Parent e652ed68b1c42a90c0f360627400faf08e4e7c27 8006591: Protect keystore entries using stronger PBE algorithms Reviewed-by: mullan diff -r e652ed68b1c4 -r eb3d7b36b4c4 jdk/src/share/classes/java/security/KeyStore.java --- a/jdk/src/share/classes/java/security/KeyStore.java Wed Jan 23 10:31:10 2013 -0800 +++ b/jdk/src/share/classes/java/security/KeyStore.java Wed Jan 23 21:25:49 2013 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.cert.CertificateException; +import java.security.spec.AlgorithmParameterSpec; import java.util.*; import javax.crypto.SecretKey; @@ -239,6 +240,8 @@ ProtectionParameter, javax.security.auth.Destroyable { private final char[] password; + private final String protectionAlgorithm; + private final AlgorithmParameterSpec protectionParameters; private volatile boolean destroyed = false; /** @@ -251,6 +254,72 @@ */ public PasswordProtection(char[] password) { this.password = (password == null) ? null : password.clone(); + this.protectionAlgorithm = null; + this.protectionParameters = null; + } + + /** + * Creates a password parameter and specifies the protection algorithm + * and associated parameters to use when encrypting a keystore entry. + *

+ * The specified {@code password} is cloned before it is stored in the + * new {@code PasswordProtection} object. + * + * @param password the password, which may be {@code null} + * @param protectionAlgorithm the encryption algorithm name, for + * example, {@code PBEWithHmacSHA256AndAES_256}. + * See the Cipher section in the + * Java Cryptography Architecture Standard Algorithm Name + * Documentation + * for information about standard encryption algorithm names. + * @param protectionParameters the encryption algorithm parameter + * specification, which may be {@code null} + * @exception NullPointerException if {@code protectionAlgorithm} is + * {@code null} + * + * @since 1.8 + */ + public PasswordProtection(char[] password, String protectionAlgorithm, + AlgorithmParameterSpec protectionParameters) { + if (protectionAlgorithm == null) { + throw new NullPointerException("invalid null input"); + } + this.password = (password == null) ? null : password.clone(); + this.protectionAlgorithm = protectionAlgorithm; + this.protectionParameters = protectionParameters; + } + + /** + * Gets the name of the protection algorithm. + * If none was set then the keystore provider will use its default + * protection algorithm. The name of the default protection algorithm + * for a given keystore type is set using the + * {@code 'keystore..keyProtectionAlgorithm'} security property. + * For example, the + * {@code keystore.PKCS12.keyProtectionAlgorithm} property stores the + * name of the default key protection algorithm used for PKCS12 + * keystores. If the security property is not set, an + * implementation-specific algorithm will be used. + * + * @return the algorithm name, or {@code null} if none was set + * + * @since 1.8 + */ + public String getProtectionAlgorithm() { + return protectionAlgorithm; + } + + /** + * Gets the parameters supplied for the protection algorithm. + * + * @return the algorithm parameter specification, or {@code null}, + * if none was set + * + * @since 1.8 + */ + public AlgorithmParameterSpec getProtectionParameters() { + return protectionParameters; } /** diff -r e652ed68b1c4 -r eb3d7b36b4c4 jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java --- a/jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java Wed Jan 23 10:31:10 2013 -0800 +++ b/jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java Wed Jan 23 21:25:49 2013 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,19 +26,25 @@ package sun.security.pkcs12; import java.io.*; +import java.security.AccessController; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Key; +import java.security.KeyStore; import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.PrivilegedAction; import java.security.KeyStoreSpi; import java.security.KeyStoreException; +import java.security.UnrecoverableEntryException; import java.security.UnrecoverableKeyException; +import java.security.Security; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.cert.CertificateException; +import java.security.spec.AlgorithmParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; @@ -49,8 +55,10 @@ import javax.crypto.SecretKey; import javax.crypto.Cipher; import javax.crypto.Mac; +import javax.security.auth.DestroyFailedException; import javax.security.auth.x500.X500Principal; +import sun.security.util.Debug; import sun.security.util.DerInputStream; import sun.security.util.DerOutputStream; import sun.security.util.DerValue; @@ -119,6 +127,13 @@ public static final int VERSION_3 = 3; + private static final String[] KEY_PROTECTION_ALGORITHM = { + "keystore.pkcs12.keyProtectionAlgorithm", + "keystore.PKCS12.keyProtectionAlgorithm" + }; + + private static final Debug debug = Debug.getInstance("pkcs12"); + private static final int keyBag[] = {1, 2, 840, 113549, 1, 12, 10, 1, 2}; private static final int certBag[] = {1, 2, 840, 113549, 1, 12, 10, 1, 3}; @@ -131,6 +146,7 @@ {1, 2, 840, 113549, 1, 12, 1, 6}; private static final int pbeWithSHAAnd3KeyTripleDESCBC[] = {1, 2, 840, 113549, 1, 12, 1, 3}; + private static final int pbes2[] = {1, 2, 840, 113549, 1, 5, 13}; private static ObjectIdentifier PKCS8ShroudedKeyBag_OID; private static ObjectIdentifier CertBag_OID; @@ -139,6 +155,7 @@ private static ObjectIdentifier PKCS9CertType_OID; private static ObjectIdentifier pbeWithSHAAnd40BitRC2CBC_OID; private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID; + private static ObjectIdentifier pbes2_OID; private int counter = 0; private static final int iterationCount = 1024; @@ -163,6 +180,7 @@ new ObjectIdentifier(pbeWithSHAAnd40BitRC2CBC); pbeWithSHAAnd3KeyTripleDESCBC_OID = new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC); + pbes2_OID = new ObjectIdentifier(pbes2); } catch (IOException ioe) { // should not happen } @@ -288,6 +306,12 @@ KeyFactory kfac = KeyFactory.getInstance(algName); key = kfac.generatePrivate(kspec); + + if (debug != null) { + debug.println("Retrieved a protected private key at alias '" + + alias + "'"); + } + } catch (Exception e) { UnrecoverableKeyException uke = new UnrecoverableKeyException("Get Key failed: " + @@ -315,6 +339,13 @@ if (entry.chain == null) { return null; } else { + + if (debug != null) { + debug.println("Retrieved a " + + entry.chain.length + + "-certificate chain at alias '" + alias + "'"); + } + return entry.chain.clone(); } } else { @@ -343,6 +374,12 @@ if (entry.chain == null) { return null; } else { + + if (debug != null) { + debug.println("Retrieved a certificate at alias '" + alias + + "'"); + } + return entry.chain[0]; } } else { @@ -393,6 +430,28 @@ char[] password, Certificate[] chain) throws KeyStoreException { + KeyStore.PasswordProtection passwordProtection = + new KeyStore.PasswordProtection(password); + + try { + setKeyEntry(alias, key, passwordProtection, chain); + + } finally { + try { + passwordProtection.destroy(); + } catch (DestroyFailedException dfe) { + // ignore + } + } + } + + /* + * Sets a key entry + */ + private void setKeyEntry(String alias, Key key, + KeyStore.PasswordProtection passwordProtection, Certificate[] chain) + throws KeyStoreException + { try { KeyEntry entry = new KeyEntry(); entry.date = new Date(); @@ -401,8 +460,14 @@ if ((key.getFormat().equals("PKCS#8")) || (key.getFormat().equals("PKCS8"))) { // Encrypt the private key + + if (debug != null) { + debug.println("Setting a protected private key at " + + "alias '" + alias + "'"); + } + entry.protectedPrivKey = - encryptPrivateKey(key.getEncoded(), password); + encryptPrivateKey(key.getEncoded(), passwordProtection); } else { throw new KeyStoreException("Private key is not encoded" + "as PKCS#8"); @@ -418,6 +483,11 @@ throw new KeyStoreException("Certificate chain is " + "not validate"); entry.chain = chain.clone(); + + if (debug != null) { + debug.println("Setting a " + chain.length + + "-certificate chain at alias '" + alias + "'"); + } } // set the keyId to current date @@ -472,6 +542,10 @@ KeyEntry entry = new KeyEntry(); entry.date = new Date(); + if (debug != null) { + debug.println("Setting a protected key at alias '" + alias + "'"); + } + try { // set the keyId to current date entry.keyId = ("Time " + (entry.date).getTime()).getBytes("UTF8"); @@ -484,6 +558,11 @@ entry.protectedPrivKey = key.clone(); if (chain != null) { entry.chain = chain.clone(); + + if (debug != null) { + debug.println("Setting a " + entry.chain.length + + "-certificate chain at alias '" + alias + "'"); + } } // add the entry @@ -576,31 +655,76 @@ * Encrypt private key using Password-based encryption (PBE) * as defined in PKCS#5. * - * NOTE: Currently pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is + * NOTE: By default, pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is * used to derive the key and IV. * * @return encrypted private key encoded as EncryptedPrivateKeyInfo */ - private byte[] encryptPrivateKey(byte[] data, char[] password) + private byte[] encryptPrivateKey(byte[] data, + KeyStore.PasswordProtection passwordProtection) throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException { byte[] key = null; try { - // create AlgorithmParameters - AlgorithmParameters algParams = - getAlgorithmParameters("PBEWithSHA1AndDESede"); + String algorithm; + AlgorithmParameters algParams; + AlgorithmId algid; + + // Initialize PBE algorithm and parameters + algorithm = passwordProtection.getProtectionAlgorithm(); + if (algorithm != null) { + AlgorithmParameterSpec algParamSpec = + passwordProtection.getProtectionParameters(); + if (algParamSpec != null) { + algParams = AlgorithmParameters.getInstance(algorithm); + algParams.init(algParamSpec); + } else { + algParams = getAlgorithmParameters(algorithm); + } + ObjectIdentifier pbeOID = mapPBEAlgorithmToOID(algorithm); + if (pbeOID != null) { + algid = new AlgorithmId(pbeOID, algParams); + } else { + throw new IOException("PBE algorithm '" + algorithm + + " 'is not supported for key entry protection"); + } + } else { + // Check default key protection algorithm for PKCS12 keystores + algorithm = AccessController.doPrivileged( + new PrivilegedAction() { + public String run() { + String prop = + Security.getProperty( + KEY_PROTECTION_ALGORITHM[0]); + if (prop == null) { + prop = Security.getProperty( + KEY_PROTECTION_ALGORITHM[1]); + } + return prop; + } + }); + if (algorithm == null) { + algorithm = "PBEWithSHA1AndDESede"; + } + algParams = getAlgorithmParameters(algorithm); + algid = new AlgorithmId(pbeWithSHAAnd3KeyTripleDESCBC_OID, + algParams); + } // Use JCE - SecretKey skey = getPBEKey(password); - Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede"); + SecretKey skey = getPBEKey(passwordProtection.getPassword()); + Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); byte[] encryptedKey = cipher.doFinal(data); + if (debug != null) { + debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + + ")"); + } + // wrap encrypted private key in EncryptedPrivateKeyInfo // as defined in PKCS#8 - AlgorithmId algid = - new AlgorithmId(pbeWithSHAAnd3KeyTripleDESCBC_OID, algParams); EncryptedPrivateKeyInfo encrInfo = new EncryptedPrivateKeyInfo(algid, encryptedKey); key = encrInfo.getEncoded(); @@ -615,6 +739,18 @@ return key; } + /* + * Map a PBE algorithm name onto its object identifier + */ + private ObjectIdentifier mapPBEAlgorithmToOID(String algorithm) { + // Check for PBES2 algorithms + if (algorithm.toLowerCase().startsWith("pbewithhmacsha")) { + return pbes2_OID; + } + + return null; + } + /** * Assigns the given certificate to the given alias. * @@ -649,6 +785,10 @@ public synchronized void engineDeleteEntry(String alias) throws KeyStoreException { + if (debug != null) { + debug.println("Removing entry at alias '" + alias + "'"); + } + entries.remove(alias.toLowerCase(Locale.ENGLISH)); } @@ -778,11 +918,21 @@ DerOutputStream authSafeContentInfo = new DerOutputStream(); // -- create safeContent Data ContentInfo + if (debug != null) { + debug.println("Storing " + privateKeyCount + + " protected key(s) in a PKCS#7 data content-type"); + } + byte[] safeContentData = createSafeContent(); ContentInfo dataContentInfo = new ContentInfo(safeContentData); dataContentInfo.encode(authSafeContentInfo); // -- create EncryptedContentInfo + if (debug != null) { + debug.println("Storing certificate(s) in a PKCS#7 encryptedData " + + "content-type"); + } + byte[] encrData = createEncryptedData(password); ContentInfo encrContentInfo = new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID, @@ -812,7 +962,6 @@ stream.flush(); } - /* * Generate Hash. */ @@ -1143,6 +1292,11 @@ cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); encryptedData = cipher.doFinal(data); + if (debug != null) { + debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + + ")"); + } + } catch (Exception e) { throw new IOException("Failed to encrypt" + " safe contents entry: " + e, e); @@ -1240,11 +1394,21 @@ contentType = safeContents.getContentType(); safeContentsData = null; if (contentType.equals((Object)ContentInfo.DATA_OID)) { + + if (debug != null) { + debug.println("Loading PKCS#7 data content-type"); + } + safeContentsData = safeContents.getData(); } else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) { if (password == null) { continue; } + + if (debug != null) { + debug.println("Loading PKCS#7 encryptedData content-type"); + } + DerInputStream edi = safeContents.getContent().toDerInputStream(); int edVersion = edi.getInteger(); @@ -1312,6 +1476,11 @@ m.update(authSafeData); byte[] macResult = m.doFinal(); + if (debug != null) { + debug.println("Checking keystore integrity " + + "(MAC algorithm: " + m.getAlgorithm() + ")"); + } + if (!Arrays.equals(macData.getDigest(), macResult)) { throw new SecurityException("Failed PKCS12" + " integrity checking"); @@ -1417,7 +1586,10 @@ (new ByteArrayInputStream(certValue.getOctetString())); bagItem = cert; } else { - // log error message for "unsupported PKCS12 bag type" + + if (debug != null) { + debug.println("Unsupported PKCS12 bag type: " + bagId); + } } DerValue[] attrSet; @@ -1453,7 +1625,11 @@ } else if (attrId.equals((Object)PKCS9LocalKeyId_OID)) { keyId = valSet[0].getOctetString(); } else { - // log error message for "unknown attr" + + if (debug != null) { + debug.println("Unsupported PKCS12 bag attribute: " + + attrId); + } } } } diff -r e652ed68b1c4 -r eb3d7b36b4c4 jdk/test/java/security/KeyStore/PBETest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/security/KeyStore/PBETest.java Wed Jan 23 21:25:49 2013 +0000 @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8006591 + * @summary Protect keystore entries using stronger PBE algorithms + */ + +import java.io.*; +import java.security.*; +import javax.crypto.spec.*; + +// Retrieve a keystore entry, protected by the default encryption algorithm. +// Set the keystore entry, protected by a stronger encryption algorithm. + +public class PBETest { + private final static String DIR = System.getProperty("test.src", "."); + private static final String PBE_ALGO = "PBEWithHmacSHA1AndAES_128"; + private static final char[] PASSWORD = "passphrase".toCharArray(); + private static final String KEYSTORE_TYPE = "JKS"; + private static final String KEYSTORE = DIR + "/keystore.jks"; + private static final String NEW_KEYSTORE_TYPE = "PKCS12"; + private static final String NEW_KEYSTORE = PBE_ALGO + ".p12"; + private static final String ALIAS = "vajra"; + + private static final byte[] IV = { + 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, + 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20 + }; + private static final byte[] SALT = { + 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08 + }; + private static final int ITERATION_COUNT = 1024; + + public static void main(String[] args) throws Exception { + + new File(NEW_KEYSTORE).delete(); + + try { + KeyStore keystore = load(KEYSTORE_TYPE, KEYSTORE, PASSWORD); + KeyStore.Entry entry = + keystore.getEntry(ALIAS, + new KeyStore.PasswordProtection(PASSWORD)); + System.out.println("Retrieved entry named '" + ALIAS + "'"); + + // Set entry + KeyStore keystore2 = load(NEW_KEYSTORE_TYPE, null, null); + keystore2.setEntry(ALIAS, entry, + new KeyStore.PasswordProtection(PASSWORD, PBE_ALGO, + new PBEParameterSpec(SALT, ITERATION_COUNT, + new IvParameterSpec(IV)))); + System.out.println("Encrypted entry using: " + PBE_ALGO); + + System.out.println("Storing keystore to: " + NEW_KEYSTORE); + keystore2.store(new FileOutputStream(NEW_KEYSTORE), PASSWORD); + + keystore2 = load(NEW_KEYSTORE_TYPE, NEW_KEYSTORE, PASSWORD); + entry = keystore2.getEntry(ALIAS, + new KeyStore.PasswordProtection(PASSWORD)); + System.out.println("Retrieved entry named '" + ALIAS + "'"); + + } finally { + new File(NEW_KEYSTORE).delete(); + System.out.println("Deleted keystore: " + NEW_KEYSTORE); + } + } + + private static KeyStore load(String type, String path, char[] password) + throws Exception { + + FileInputStream stream = null; + if (path != null) { + stream = new FileInputStream(path); + } + KeyStore keystore = KeyStore.getInstance(type); + System.out.println("Loading keystore from: " + path); + keystore.load(stream, password); + + return keystore; + } +}