8006591: Protect keystore entries using stronger PBE algorithms
Reviewed-by: mullan
--- 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.
+ * <p>
+ * 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 <a href=
+ * "{@docRoot}/../technotes/guides/security/StandardNames.html#Cipher">
+ * Java Cryptography Architecture Standard Algorithm Name
+ * Documentation</a>
+ * 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.<type>.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;
}
/**
--- 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<String>() {
+ 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);
+ }
}
}
}
--- /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;
+ }
+}