8006591: Protect keystore entries using stronger PBE algorithms

Reviewed-by: mullan
This commit is contained in:
Vinnie Ryan 2013-01-23 21:25:49 +00:00
parent 60e625f275
commit 7dcd0b38a8
3 changed files with 362 additions and 15 deletions
jdk
src/share/classes
java/security
sun/security/pkcs12
test/java/security/KeyStore

@ -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.io.*;
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 @@ public class KeyStore {
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 class KeyStore {
*/
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;
}
/**

@ -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.SecretKeyFactory;
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 final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
{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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
new ObjectIdentifier(pbeWithSHAAnd40BitRC2CBC);
pbeWithSHAAnd3KeyTripleDESCBC_OID =
new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC);
pbes2_OID = new ObjectIdentifier(pbes2);
} catch (IOException ioe) {
// should not happen
}
@ -288,6 +306,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
if (entry.chain == null) {
return null;
} else {
if (debug != null) {
debug.println("Retrieved a certificate at alias '" + alias +
"'");
}
return entry.chain[0];
}
} else {
@ -392,6 +429,28 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
public synchronized void engineSetKeyEntry(String alias, Key key,
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();
@ -401,8 +460,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* 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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
stream.flush();
}
/*
* Generate Hash.
*/
@ -1143,6 +1292,11 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
(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 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
} 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);
}
}
}
}

@ -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;
}
}