diff --git a/jdk/src/share/classes/java/security/KeyStore.java b/jdk/src/share/classes/java/security/KeyStore.java
index c27f7f435df..96565684b0e 100644
--- a/jdk/src/share/classes/java/security/KeyStore.java
+++ b/jdk/src/share/classes/java/security/KeyStore.java
@@ -26,6 +26,7 @@
package java.security;
import java.io.*;
+import java.net.URI;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
@@ -405,7 +406,44 @@ public class KeyStore {
*
* @since 1.5
*/
- public static interface Entry { }
+ public static interface Entry {
+
+ /**
+ * Retrieves the attributes associated with an entry.
+ *
+ * The default implementation returns an empty {@code Set}.
+ *
+ * @return an unmodifiable {@code Set} of attributes, possibly empty
+ *
+ * @since 1.8
+ */
+ public default Set getAttributes() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * An attribute associated with a keystore entry.
+ * It comprises a name and one or more values.
+ *
+ * @since 1.8
+ */
+ public interface Attribute {
+ /**
+ * Returns the attribute's name.
+ *
+ * @return the attribute name
+ */
+ public String getName();
+
+ /**
+ * Returns the attribute's value.
+ * Multi-valued attributes encode their values as a single string.
+ *
+ * @return the attribute value
+ */
+ public String getValue();
+ }
+ }
/**
* A KeyStore
entry that holds a PrivateKey
@@ -417,6 +455,7 @@ public class KeyStore {
private final PrivateKey privKey;
private final Certificate[] chain;
+ private final Set attributes;
/**
* Constructs a PrivateKeyEntry
with a
@@ -443,7 +482,39 @@ public class KeyStore {
* in the end entity Certificate
(at index 0)
*/
public PrivateKeyEntry(PrivateKey privateKey, Certificate[] chain) {
- if (privateKey == null || chain == null) {
+ this(privateKey, chain, Collections.emptySet());
+ }
+
+ /**
+ * Constructs a {@code PrivateKeyEntry} with a {@code PrivateKey} and
+ * corresponding certificate chain and associated entry attributes.
+ *
+ * The specified {@code chain} and {@code attributes} are cloned
+ * before they are stored in the new {@code PrivateKeyEntry} object.
+ *
+ * @param privateKey the {@code PrivateKey}
+ * @param chain an array of {@code Certificate}s
+ * representing the certificate chain.
+ * The chain must be ordered and contain a
+ * {@code Certificate} at index 0
+ * corresponding to the private key.
+ * @param attributes the attributes
+ *
+ * @exception NullPointerException if {@code privateKey}, {@code chain}
+ * or {@code attributes} is {@code null}
+ * @exception IllegalArgumentException if the specified chain has a
+ * length of 0, if the specified chain does not contain
+ * {@code Certificate}s of the same type,
+ * or if the {@code PrivateKey} algorithm
+ * does not match the algorithm of the {@code PublicKey}
+ * in the end entity {@code Certificate} (at index 0)
+ *
+ * @since 1.8
+ */
+ public PrivateKeyEntry(PrivateKey privateKey, Certificate[] chain,
+ Set attributes) {
+
+ if (privateKey == null || chain == null || attributes == null) {
throw new NullPointerException("invalid null input");
}
if (chain.length == 0) {
@@ -478,6 +549,9 @@ public class KeyStore {
} else {
this.chain = clonedChain;
}
+
+ this.attributes =
+ Collections.unmodifiableSet(new HashSet<>(attributes));
}
/**
@@ -518,6 +592,19 @@ public class KeyStore {
return chain[0];
}
+ /**
+ * Retrieves the attributes associated with an entry.
+ *
+ *
+ * @return an unmodifiable {@code Set} of attributes, possibly empty
+ *
+ * @since 1.8
+ */
+ @Override
+ public Set getAttributes() {
+ return attributes;
+ }
+
/**
* Returns a string representation of this PrivateKeyEntry.
* @return a string representation of this PrivateKeyEntry.
@@ -543,6 +630,7 @@ public class KeyStore {
public static final class SecretKeyEntry implements Entry {
private final SecretKey sKey;
+ private final Set attributes;
/**
* Constructs a SecretKeyEntry
with a
@@ -558,6 +646,32 @@ public class KeyStore {
throw new NullPointerException("invalid null input");
}
this.sKey = secretKey;
+ this.attributes = Collections.emptySet();
+ }
+
+ /**
+ * Constructs a {@code SecretKeyEntry} with a {@code SecretKey} and
+ * associated entry attributes.
+ *
+ * The specified {@code attributes} is cloned before it is stored
+ * in the new {@code SecretKeyEntry} object.
+ *
+ * @param secretKey the {@code SecretKey}
+ * @param attributes the attributes
+ *
+ * @exception NullPointerException if {@code secretKey} or
+ * {@code attributes} is {@code null}
+ *
+ * @since 1.8
+ */
+ public SecretKeyEntry(SecretKey secretKey, Set attributes) {
+
+ if (secretKey == null || attributes == null) {
+ throw new NullPointerException("invalid null input");
+ }
+ this.sKey = secretKey;
+ this.attributes =
+ Collections.unmodifiableSet(new HashSet<>(attributes));
}
/**
@@ -569,6 +683,19 @@ public class KeyStore {
return sKey;
}
+ /**
+ * Retrieves the attributes associated with an entry.
+ *
+ *
+ * @return an unmodifiable {@code Set} of attributes, possibly empty
+ *
+ * @since 1.8
+ */
+ @Override
+ public Set getAttributes() {
+ return attributes;
+ }
+
/**
* Returns a string representation of this SecretKeyEntry.
* @return a string representation of this SecretKeyEntry.
@@ -587,6 +714,7 @@ public class KeyStore {
public static final class TrustedCertificateEntry implements Entry {
private final Certificate cert;
+ private final Set attributes;
/**
* Constructs a TrustedCertificateEntry
with a
@@ -602,6 +730,32 @@ public class KeyStore {
throw new NullPointerException("invalid null input");
}
this.cert = trustedCert;
+ this.attributes = Collections.emptySet();
+ }
+
+ /**
+ * Constructs a {@code TrustedCertificateEntry} with a
+ * trusted {@code Certificate} and associated entry attributes.
+ *
+ * The specified {@code attributes} is cloned before it is stored
+ * in the new {@code TrustedCertificateEntry} object.
+ *
+ * @param trustedCert the trusted {@code Certificate}
+ * @param attributes the attributes
+ *
+ * @exception NullPointerException if {@code trustedCert} or
+ * {@code attributes} is {@code null}
+ *
+ * @since 1.8
+ */
+ public TrustedCertificateEntry(Certificate trustedCert,
+ Set attributes) {
+ if (trustedCert == null || attributes == null) {
+ throw new NullPointerException("invalid null input");
+ }
+ this.cert = trustedCert;
+ this.attributes =
+ Collections.unmodifiableSet(new HashSet<>(attributes));
}
/**
@@ -613,6 +767,19 @@ public class KeyStore {
return cert;
}
+ /**
+ * Retrieves the attributes associated with an entry.
+ *
+ *
+ * @return an unmodifiable {@code Set} of attributes, possibly empty
+ *
+ * @since 1.8
+ */
+ @Override
+ public Set getAttributes() {
+ return attributes;
+ }
+
/**
* Returns a string representation of this TrustedCertificateEntry.
* @return a string representation of this TrustedCertificateEntry.
diff --git a/jdk/src/share/classes/java/security/PKCS12Attribute.java b/jdk/src/share/classes/java/security/PKCS12Attribute.java
new file mode 100644
index 00000000000..b13a4b1f18a
--- /dev/null
+++ b/jdk/src/share/classes/java/security/PKCS12Attribute.java
@@ -0,0 +1,285 @@
+/*
+ * 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package java.security;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+import sun.security.util.*;
+
+/**
+ * An attribute associated with a PKCS12 keystore entry.
+ * The attribute name is an ASN.1 Object Identifier and the attribute
+ * value is a set of ASN.1 types.
+ *
+ * @since 1.8
+ */
+public final class PKCS12Attribute implements KeyStore.Entry.Attribute {
+
+ private static final Pattern COLON_SEPARATED_HEX_PAIRS =
+ Pattern.compile("^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2})+$");
+ private String name;
+ private String value;
+ private byte[] encoded;
+ private int hashValue = -1;
+
+ /**
+ * Constructs a PKCS12 attribute from its name and value.
+ * The name is an ASN.1 Object Identifier represented as a list of
+ * dot-separated integers.
+ * A string value is represented as the string itself.
+ * A binary value is represented as a string of colon-separated
+ * pairs of hexadecimal digits.
+ * Multi-valued attributes are represented as a comma-separated
+ * list of values, enclosed in square brackets. See
+ * {@link Arrays.toString}.
+ *
+ * A string value will be DER-encoded as an ASN.1 UTF8String and a
+ * binary value will be DER-encoded as an ASN.1 Octet String.
+ *
+ * @param name the attribute's identifier
+ * @param value the attribute's value
+ *
+ * @exception NullPointerException if {@code name} or {@code value}
+ * is {@code null}
+ * @exception IllegalArgumentException if {@code name} or
+ * {@code value} is incorrectly formatted
+ */
+ public PKCS12Attribute(String name, String value) {
+ if (name == null || value == null) {
+ throw new NullPointerException();
+ }
+ // Validate name
+ ObjectIdentifier type;
+ try {
+ type = new ObjectIdentifier(name);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Incorrect format: name", e);
+ }
+ this.name = name;
+
+ // Validate value
+ int length = value.length();
+ String[] values;
+ if (value.charAt(0) == '[' && value.charAt(length - 1) == ']') {
+ values = value.substring(1, length - 1).split(", ");
+ } else {
+ values = new String[]{ value };
+ }
+ this.value = value;
+
+ try {
+ this.encoded = encode(type, values);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Incorrect format: value", e);
+ }
+ }
+
+ /**
+ * Constructs a PKCS12 attribute from its ASN.1 DER encoding.
+ * The DER encoding is specified by the following ASN.1 definition:
+ *
+ *
+ * Attribute ::= SEQUENCE {
+ * type AttributeType,
+ * values SET OF AttributeValue
+ * }
+ * AttributeType ::= OBJECT IDENTIFIER
+ * AttributeValue ::= ANY defined by type
+ *
+ *
+ *
+ * @param encoded the attribute's ASN.1 DER encoding. It is cloned
+ * to prevent subsequent modificaion.
+ *
+ * @exception NullPointerException if {@code encoded} is
+ * {@code null}
+ * @exception IllegalArgumentException if {@code encoded} is
+ * incorrectly formatted
+ */
+ public PKCS12Attribute(byte[] encoded) {
+ if (encoded == null) {
+ throw new NullPointerException();
+ }
+ this.encoded = encoded.clone();
+
+ try {
+ parse(encoded);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Incorrect format: encoded", e);
+ }
+ }
+
+ /**
+ * Returns the attribute's ASN.1 Object Identifier represented as a
+ * list of dot-separated integers.
+ *
+ * @return the attribute's identifier
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the attribute's ASN.1 DER-encoded value as a string.
+ * An ASN.1 DER-encoded value is returned in one of the following
+ * {@code String} formats:
+ *
+ * - the DER encoding of a basic ASN.1 type that has a natural
+ * string representation is returned as the string itself.
+ * Such types are currently limited to BOOLEAN, INTEGER,
+ * OBJECT IDENTIFIER, UTCTime, GeneralizedTime and the
+ * following six ASN.1 string types: UTF8String,
+ * PrintableString, T61String, IA5String, BMPString and
+ * GeneralString.
+ *
- the DER encoding of any other ASN.1 type is not decoded but
+ * returned as a binary string of colon-separated pairs of
+ * hexadecimal digits.
+ *
+ * Multi-valued attributes are represented as a comma-separated
+ * list of values, enclosed in square brackets. See
+ * {@link Arrays.toString}.
+ *
+ * @return the attribute value's string encoding
+ */
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the attribute's ASN.1 DER encoding.
+ *
+ * @return a clone of the attribute's DER encoding
+ */
+ public byte[] getEncoded() {
+ return encoded.clone();
+ }
+
+ /**
+ * Compares this {@code PKCS12Attribute} and a specified object for
+ * equality.
+ *
+ * @param obj the comparison object
+ *
+ * @return true if {@code obj} is a {@code PKCS12Attribute} and
+ * their DER encodings are equal.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof PKCS12Attribute)) {
+ return false;
+ }
+ return Arrays.equals(encoded, ((PKCS12Attribute) obj).getEncoded());
+ }
+
+ /**
+ * Returns the hashcode for this {@code PKCS12Attribute}.
+ * The hash code is computed from its DER encoding.
+ *
+ * @return the hash code
+ */
+ @Override
+ public int hashCode() {
+ if (hashValue == -1) {
+ Arrays.hashCode(encoded);
+ }
+ return hashValue;
+ }
+
+ /**
+ * Returns a string representation of this {@code PKCS12Attribute}.
+ *
+ * @return a name/value pair separated by an 'equals' symbol
+ */
+ @Override
+ public String toString() {
+ return (name + "=" + value);
+ }
+
+ private byte[] encode(ObjectIdentifier type, String[] values)
+ throws IOException {
+ DerOutputStream attribute = new DerOutputStream();
+ attribute.putOID(type);
+ DerOutputStream attrContent = new DerOutputStream();
+ for (String value : values) {
+ if (COLON_SEPARATED_HEX_PAIRS.matcher(value).matches()) {
+ byte[] bytes =
+ new BigInteger(value.replace(":", ""), 16).toByteArray();
+ if (bytes[0] == 0) {
+ bytes = Arrays.copyOfRange(bytes, 1, bytes.length);
+ }
+ attrContent.putOctetString(bytes);
+ } else {
+ attrContent.putUTF8String(value);
+ }
+ }
+ attribute.write(DerValue.tag_Set, attrContent);
+ DerOutputStream attributeValue = new DerOutputStream();
+ attributeValue.write(DerValue.tag_Sequence, attribute);
+
+ return attributeValue.toByteArray();
+ }
+
+ private void parse(byte[] encoded) throws IOException {
+ DerInputStream attributeValue = new DerInputStream(encoded);
+ DerValue[] attrSeq = attributeValue.getSequence(2);
+ ObjectIdentifier type = attrSeq[0].getOID();
+ DerInputStream attrContent =
+ new DerInputStream(attrSeq[1].toByteArray());
+ DerValue[] attrValueSet = attrContent.getSet(1);
+ String[] values = new String[attrValueSet.length];
+ String printableString;
+ for (int i = 0; i < attrValueSet.length; i++) {
+ if (attrValueSet[i].tag == DerValue.tag_OctetString) {
+ values[i] = Debug.toString(attrValueSet[i].getOctetString());
+ } else if ((printableString = attrValueSet[i].getAsString())
+ != null) {
+ values[i] = printableString;
+ } else if (attrValueSet[i].tag == DerValue.tag_ObjectId) {
+ values[i] = attrValueSet[i].getOID().toString();
+ } else if (attrValueSet[i].tag == DerValue.tag_GeneralizedTime) {
+ values[i] = attrValueSet[i].getGeneralizedTime().toString();
+ } else if (attrValueSet[i].tag == DerValue.tag_UtcTime) {
+ values[i] = attrValueSet[i].getUTCTime().toString();
+ } else if (attrValueSet[i].tag == DerValue.tag_Integer) {
+ values[i] = attrValueSet[i].getBigInteger().toString();
+ } else if (attrValueSet[i].tag == DerValue.tag_Boolean) {
+ values[i] = String.valueOf(attrValueSet[i].getBoolean());
+ } else {
+ values[i] = Debug.toString(attrValueSet[i].getDataBytes());
+ }
+ }
+
+ this.name = type.toString();
+ this.value = values.length == 1 ? values[0] : Arrays.toString(values);
+ }
+}
diff --git a/jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java b/jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
index 1c348017828..a8ce582fa2e 100644
--- a/jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
+++ b/jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
@@ -30,27 +30,30 @@ 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.KeyStore;
import java.security.KeyStoreSpi;
import java.security.KeyStoreException;
+import java.security.PKCS12Attribute;
+import java.security.PrivateKey;
+import java.security.PrivilegedAction;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
-import java.security.Security;
import java.security.SecureRandom;
+import java.security.Security;
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.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.security.AlgorithmParameters;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
@@ -107,11 +110,12 @@ import sun.security.pkcs.EncryptedPrivateKeyInfo;
* OpenSSL PKCS#12 code. All. All.
* ---------------------------------------------------------------------
*
- * NOTE: Currently PKCS12 KeyStore does not support TrustedCertEntries.
+ * NOTE: PKCS12 KeyStore supports PrivateKeyEntry and TrustedCertficateEntry.
* PKCS#12 is mainly used to deliver private keys with their associated
* certificate chain and aliases. In a PKCS12 keystore, entries are
* identified by the alias, and a localKeyId is required to match the
- * private key with the certificate.
+ * private key with the certificate. Trusted certificate entries are identified
+ * by the presence of an trustedKeyUsage attribute.
*
* @author Seema Malkani
* @author Jeff Nisewanger
@@ -136,6 +140,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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};
+ private static final int secretBag[] = {1, 2, 840, 113549, 1, 12, 10, 1, 5};
private static final int pkcs9Name[] = {1, 2, 840, 113549, 1, 9, 20};
private static final int pkcs9KeyId[] = {1, 2, 840, 113549, 1, 9, 21};
@@ -147,15 +152,26 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
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};
+ // TODO: temporary Oracle OID
+ /*
+ * { joint-iso-itu-t(2) country(16) us(840) organization(1) oracle(113894)
+ * jdk(746875) crypto(1) id-at-trustedKeyUsage(1) }
+ */
+ private static final int TrustedKeyUsage[] =
+ {2, 16, 840, 1, 113894, 746875, 1, 1};
+ private static final int AnyExtendedKeyUsage[] = {2, 5, 29, 37, 0};
private static ObjectIdentifier PKCS8ShroudedKeyBag_OID;
private static ObjectIdentifier CertBag_OID;
+ private static ObjectIdentifier SecretBag_OID;
private static ObjectIdentifier PKCS9FriendlyName_OID;
private static ObjectIdentifier PKCS9LocalKeyId_OID;
private static ObjectIdentifier PKCS9CertType_OID;
private static ObjectIdentifier pbeWithSHAAnd40BitRC2CBC_OID;
private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID;
private static ObjectIdentifier pbes2_OID;
+ private static ObjectIdentifier TrustedKeyUsage_OID;
+ private static ObjectIdentifier[] AnyUsage;
private int counter = 0;
private static final int iterationCount = 1024;
@@ -166,6 +182,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
// in pkcs12 with one private key entry and associated cert-chain
private int privateKeyCount = 0;
+ // secret key count
+ private int secretKeyCount = 0;
+
+ // certificate count
+ private int certificateCount = 0;
+
// the source of randomness
private SecureRandom random;
@@ -173,6 +195,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
try {
PKCS8ShroudedKeyBag_OID = new ObjectIdentifier(keyBag);
CertBag_OID = new ObjectIdentifier(certBag);
+ SecretBag_OID = new ObjectIdentifier(secretBag);
PKCS9FriendlyName_OID = new ObjectIdentifier(pkcs9Name);
PKCS9LocalKeyId_OID = new ObjectIdentifier(pkcs9KeyId);
PKCS9CertType_OID = new ObjectIdentifier(pkcs9certType);
@@ -181,38 +204,67 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
pbeWithSHAAnd3KeyTripleDESCBC_OID =
new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC);
pbes2_OID = new ObjectIdentifier(pbes2);
+ TrustedKeyUsage_OID = new ObjectIdentifier(TrustedKeyUsage);
+ AnyUsage = new ObjectIdentifier[]{
+ new ObjectIdentifier(AnyExtendedKeyUsage)};
} catch (IOException ioe) {
// should not happen
}
}
- // Private keys and their supporting certificate chains
- private static class KeyEntry {
+ // A keystore entry and associated attributes
+ private static class Entry {
Date date; // the creation date of this entry
+ String alias;
+ byte[] keyId;
+ Set attributes;
+ }
+
+ // A key entry
+ private static class KeyEntry extends Entry {
+ }
+
+ // A private key entry and its supporting certificate chain
+ private static class PrivateKeyEntry extends KeyEntry {
byte[] protectedPrivKey;
Certificate chain[];
- byte[] keyId;
- String alias;
};
- // A certificate with its PKCS #9 attributes
- private static class CertEntry {
+ // A secret key
+ private static class SecretKeyEntry extends KeyEntry {
+ byte[] protectedSecretKey;
+ };
+
+ // A certificate entry
+ private static class CertEntry extends Entry {
final X509Certificate cert;
- final byte[] keyId;
- final String alias;
+ ObjectIdentifier[] trustedKeyUsage;
+
CertEntry(X509Certificate cert, byte[] keyId, String alias) {
+ this(cert, keyId, alias, null, null);
+ }
+
+ CertEntry(X509Certificate cert, byte[] keyId, String alias,
+ ObjectIdentifier[] trustedKeyUsage,
+ Set extends KeyStore.Entry.Attribute> attributes) {
+ this.date = new Date();
this.cert = cert;
this.keyId = keyId;
this.alias = alias;
+ this.trustedKeyUsage = trustedKeyUsage;
+ this.attributes = new HashSet<>();
+ if (attributes != null) {
+ this.attributes.addAll(attributes);
+ }
}
}
/**
- * Private keys and certificates are stored in a hashtable.
- * Hash entries are keyed by alias names.
+ * Private keys and certificates are stored in a map.
+ * Map entries are keyed by alias names.
*/
- private Hashtable entries =
- new Hashtable();
+ private Map entries =
+ Collections.synchronizedMap(new LinkedHashMap());
private ArrayList keyList = new ArrayList();
private LinkedHashMap certsMap =
@@ -237,15 +289,22 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
public Key engineGetKey(String alias, char[] password)
throws NoSuchAlgorithmException, UnrecoverableKeyException
{
- KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
Key key = null;
- if (entry == null) {
+ if (entry == null || (!(entry instanceof KeyEntry))) {
return null;
}
- // get the encoded private key
- byte[] encrBytes = entry.protectedPrivKey;
+ // get the encoded private key or secret key
+ byte[] encrBytes = null;
+ if (entry instanceof PrivateKeyEntry) {
+ encrBytes = ((PrivateKeyEntry) entry).protectedPrivKey;
+ } else if (entry instanceof SecretKeyEntry) {
+ encrBytes = ((SecretKeyEntry) entry).protectedSecretKey;
+ } else {
+ throw new UnrecoverableKeyException("Error locating key");
+ }
byte[] encryptedKey;
AlgorithmParameters algParams;
@@ -271,14 +330,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
}
try {
- byte[] privateKeyInfo;
+ byte[] keyInfo;
while (true) {
try {
// Use JCE
SecretKey skey = getPBEKey(password);
Cipher cipher = Cipher.getInstance(algOid.toString());
cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
- privateKeyInfo = cipher.doFinal(encryptedKey);
+ keyInfo = cipher.doFinal(encryptedKey);
break;
} catch (Exception e) {
if (password.length == 0) {
@@ -291,27 +350,52 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
}
}
- PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(privateKeyInfo);
-
/*
* Parse the key algorithm and then use a JCA key factory
- * to create the private key.
+ * to re-create the key.
*/
- DerValue val = new DerValue(privateKeyInfo);
+ DerValue val = new DerValue(keyInfo);
DerInputStream in = val.toDerInputStream();
int i = in.getInteger();
DerValue[] value = in.getSequence(2);
AlgorithmId algId = new AlgorithmId(value[0].getOID());
- String algName = algId.getName();
+ String keyAlgo = algId.getName();
- KeyFactory kfac = KeyFactory.getInstance(algName);
- key = kfac.generatePrivate(kspec);
+ // decode private key
+ if (entry instanceof PrivateKeyEntry) {
+ KeyFactory kfac = KeyFactory.getInstance(keyAlgo);
+ PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(keyInfo);
+ key = kfac.generatePrivate(kspec);
- if (debug != null) {
- debug.println("Retrieved a protected private key at alias '" +
- alias + "'");
+ if (debug != null) {
+ debug.println("Retrieved a protected private key (" +
+ key.getClass().getName() + ") at alias '" + alias +
+ "'");
+ }
+
+ // decode secret key
+ } else {
+ SecretKeyFactory sKeyFactory =
+ SecretKeyFactory.getInstance(keyAlgo);
+ byte[] keyBytes = in.getOctetString();
+ SecretKeySpec secretKeySpec =
+ new SecretKeySpec(keyBytes, keyAlgo);
+
+ // Special handling required for PBE: needs a PBEKeySpec
+ if (keyAlgo.startsWith("PBE")) {
+ KeySpec pbeKeySpec =
+ sKeyFactory.getKeySpec(secretKeySpec, PBEKeySpec.class);
+ key = sKeyFactory.generateSecret(pbeKeySpec);
+ } else {
+ key = sKeyFactory.generateSecret(secretKeySpec);
+ }
+
+ if (debug != null) {
+ debug.println("Retrieved a protected secret key (" +
+ key.getClass().getName() + ") at alias '" + alias +
+ "'");
+ }
}
-
} catch (Exception e) {
UnrecoverableKeyException uke =
new UnrecoverableKeyException("Get Key failed: " +
@@ -334,19 +418,19 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* key entry without a certificate chain).
*/
public Certificate[] engineGetCertificateChain(String alias) {
- KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
- if (entry != null) {
- if (entry.chain == null) {
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ if (entry != null && entry instanceof PrivateKeyEntry) {
+ if (((PrivateKeyEntry) entry).chain == null) {
return null;
} else {
if (debug != null) {
debug.println("Retrieved a " +
- entry.chain.length +
+ ((PrivateKeyEntry) entry).chain.length +
"-certificate chain at alias '" + alias + "'");
}
- return entry.chain.clone();
+ return ((PrivateKeyEntry) entry).chain.clone();
}
} else {
return null;
@@ -369,9 +453,28 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* does not contain a certificate.
*/
public Certificate engineGetCertificate(String alias) {
- KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
- if (entry != null) {
- if (entry.chain == null) {
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ if (entry == null) {
+ return null;
+ }
+ if (entry instanceof CertEntry &&
+ ((CertEntry) entry).trustedKeyUsage != null) {
+
+ if (debug != null) {
+ if (Arrays.equals(AnyUsage,
+ ((CertEntry) entry).trustedKeyUsage)) {
+ debug.println("Retrieved a certificate at alias '" + alias +
+ "' (trusted for any purpose)");
+ } else {
+ debug.println("Retrieved a certificate at alias '" + alias +
+ "' (trusted for limited purposes)");
+ }
+ }
+
+ return ((CertEntry) entry).cert;
+
+ } else if (entry instanceof PrivateKeyEntry) {
+ if (((PrivateKeyEntry) entry).chain == null) {
return null;
} else {
@@ -380,8 +483,9 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
"'");
}
- return entry.chain[0];
+ return ((PrivateKeyEntry) entry).chain[0];
}
+
} else {
return null;
}
@@ -396,7 +500,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* not exist
*/
public Date engineGetCreationDate(String alias) {
- KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
if (entry != null) {
return new Date(entry.date.getTime());
} else {
@@ -434,7 +538,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
new KeyStore.PasswordProtection(password);
try {
- setKeyEntry(alias, key, passwordProtection, chain);
+ setKeyEntry(alias, key, passwordProtection, chain, null);
} finally {
try {
@@ -446,57 +550,94 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
}
/*
- * Sets a key entry
+ * Sets a key entry (with attributes, when present)
*/
private void setKeyEntry(String alias, Key key,
- KeyStore.PasswordProtection passwordProtection, Certificate[] chain)
+ KeyStore.PasswordProtection passwordProtection, Certificate[] chain,
+ Set attributes)
throws KeyStoreException
{
try {
- KeyEntry entry = new KeyEntry();
- entry.date = new Date();
+ Entry entry;
if (key instanceof PrivateKey) {
+ PrivateKeyEntry keyEntry = new PrivateKeyEntry();
+ keyEntry.date = new Date();
+
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 + "'");
- }
+ debug.println("Setting a protected private key (" +
+ key.getClass().getName() + ") at alias '" + alias +
+ "'");
+ }
- entry.protectedPrivKey =
+ // Encrypt the private key
+ keyEntry.protectedPrivKey =
encryptPrivateKey(key.getEncoded(), passwordProtection);
} else {
throw new KeyStoreException("Private key is not encoded" +
"as PKCS#8");
}
- } else {
- throw new KeyStoreException("Key is not a PrivateKey");
- }
- // clone the chain
- if (chain != null) {
- // validate cert-chain
- if ((chain.length > 1) && (!validateChain(chain)))
- throw new KeyStoreException("Certificate chain is " +
- "not validate");
- entry.chain = chain.clone();
+ // clone the chain
+ if (chain != null) {
+ // validate cert-chain
+ if ((chain.length > 1) && (!validateChain(chain)))
+ throw new KeyStoreException("Certificate chain is " +
+ "not valid");
+ keyEntry.chain = chain.clone();
+ certificateCount += chain.length;
+
+ if (debug != null) {
+ debug.println("Setting a " + chain.length +
+ "-certificate chain at alias '" + alias + "'");
+ }
+ }
+ privateKeyCount++;
+ entry = keyEntry;
+
+ } else if (key instanceof SecretKey) {
+ SecretKeyEntry keyEntry = new SecretKeyEntry();
+ keyEntry.date = new Date();
+
+ // Encode secret key in a PKCS#8
+ DerOutputStream pkcs8 = new DerOutputStream();
+ DerOutputStream secretKeyInfo = new DerOutputStream();
+ secretKeyInfo.putInteger(0);
+ AlgorithmId algId = AlgorithmId.get(key.getAlgorithm());
+ algId.encode(secretKeyInfo);
+ secretKeyInfo.putOctetString(key.getEncoded());
+ pkcs8.write(DerValue.tag_Sequence, secretKeyInfo);
+
+ // Encrypt the secret key (using same PBE as for private keys)
+ keyEntry.protectedSecretKey =
+ encryptPrivateKey(pkcs8.toByteArray(), passwordProtection);
if (debug != null) {
- debug.println("Setting a " + chain.length +
- "-certificate chain at alias '" + alias + "'");
+ debug.println("Setting a protected secret key (" +
+ key.getClass().getName() + ") at alias '" + alias +
+ "'");
}
+ secretKeyCount++;
+ entry = keyEntry;
+
+ } else {
+ throw new KeyStoreException("Unsupported Key type");
}
+ entry.attributes = new HashSet<>();
+ if (attributes != null) {
+ entry.attributes.addAll(attributes);
+ }
// set the keyId to current date
entry.keyId = ("Time " + (entry.date).getTime()).getBytes("UTF8");
// set the alias
entry.alias = alias.toLowerCase(Locale.ENGLISH);
-
// add the entry
entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
+
} catch (Exception nsae) {
throw new KeyStoreException("Key protection " +
" algorithm not found: " + nsae, nsae);
@@ -530,7 +671,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
Certificate[] chain)
throws KeyStoreException
{
- // key must be encoded as EncryptedPrivateKeyInfo
+ // Private key must be encoded as EncryptedPrivateKeyInfo
// as defined in PKCS#8
try {
new EncryptedPrivateKeyInfo(key);
@@ -539,11 +680,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
+ " as PKCS#8 EncryptedPrivateKeyInfo: " + ioe, ioe);
}
- KeyEntry entry = new KeyEntry();
+ PrivateKeyEntry entry = new PrivateKeyEntry();
entry.date = new Date();
if (debug != null) {
- debug.println("Setting a protected key at alias '" + alias + "'");
+ debug.println("Setting a protected private key at alias '" +
+ alias + "'");
}
try {
@@ -557,7 +699,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
entry.protectedPrivKey = key.clone();
if (chain != null) {
- entry.chain = chain.clone();
+ entry.chain = chain.clone();
+ certificateCount += chain.length;
if (debug != null) {
debug.println("Setting a " + entry.chain.length +
@@ -566,6 +709,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
}
// add the entry
+ privateKeyCount++;
entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
}
@@ -644,6 +788,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE");
skey = skFac.generateSecret(keySpec);
+ keySpec.clearPassword();
} catch (Exception e) {
throw new IOException("getSecretKey failed: " +
e.getMessage(), e);
@@ -695,7 +840,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
new PrivilegedAction() {
public String run() {
String prop =
- Security.getProperty(
+ Security.getProperty
KEY_PROTECTION_ALGORITHM[0]);
if (prop == null) {
prop = Security.getProperty(
@@ -762,17 +907,36 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* @param cert the certificate
*
* @exception KeyStoreException if the given alias already exists and does
- * identify a key entry, or on an attempt to create a
- * trusted cert entry which is currently not supported.
+ * not identify a trusted certificate entry, or this operation fails
+ * for some other reason.
*/
public synchronized void engineSetCertificateEntry(String alias,
Certificate cert) throws KeyStoreException
{
- KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
- if (entry != null) {
+ setCertEntry(alias, cert, null);
+ }
+
+ /*
+ * Sets a trusted cert entry (with attributes, when present)
+ */
+ private void setCertEntry(String alias, Certificate cert,
+ Set attributes) throws KeyStoreException {
+
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ if (entry != null && entry instanceof KeyEntry) {
throw new KeyStoreException("Cannot overwrite own certificate");
- } else
- throw new KeyStoreException("TrustedCertEntry not supported");
+ }
+
+ CertEntry certEntry =
+ new CertEntry((X509Certificate) cert, null, alias, AnyUsage,
+ attributes);
+ certificateCount++;
+ entries.put(alias, certEntry);
+
+ if (debug != null) {
+ debug.println("Setting a trusted certificate at alias '" + alias +
+ "'");
+ }
}
/**
@@ -789,6 +953,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
debug.println("Removing entry at alias '" + alias + "'");
}
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ if (entry instanceof PrivateKeyEntry) {
+ PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
+ if (keyEntry.chain != null) {
+ certificateCount -= keyEntry.chain.length;
+ }
+ privateKeyCount--;
+ } else if (entry instanceof CertEntry) {
+ certificateCount--;
+ } else if (entry instanceof SecretKeyEntry) {
+ secretKeyCount--;
+ }
entries.remove(alias.toLowerCase(Locale.ENGLISH));
}
@@ -798,7 +974,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* @return enumeration of the alias names
*/
public Enumeration engineAliases() {
- return entries.keys();
+ return Collections.enumeration(entries.keySet());
}
/**
@@ -829,8 +1005,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* key entry, false otherwise.
*/
public boolean engineIsKeyEntry(String alias) {
- KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
- if (entry != null) {
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ if (entry != null && entry instanceof KeyEntry) {
return true;
} else {
return false;
@@ -845,8 +1021,13 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* trusted certificate entry, false otherwise.
*/
public boolean engineIsCertificateEntry(String alias) {
- // TrustedCertEntry is not supported
- return false;
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ if (entry != null && entry instanceof CertEntry &&
+ ((CertEntry) entry).trustedKeyUsage != null) {
+ return true;
+ } else {
+ return false;
+ }
}
/**
@@ -868,11 +1049,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
public String engineGetCertificateAlias(Certificate cert) {
Certificate certElem = null;
- for (Enumeration e = entries.keys(); e.hasMoreElements(); ) {
+ for (Enumeration e = engineAliases(); e.hasMoreElements(); ) {
String alias = e.nextElement();
- KeyEntry entry = entries.get(alias);
- if (entry.chain != null) {
- certElem = entry.chain[0];
+ Entry entry = entries.get(alias);
+ if (entry instanceof PrivateKeyEntry) {
+ if (((PrivateKeyEntry) entry).chain != null) {
+ certElem = ((PrivateKeyEntry) entry).chain[0];
+ }
+ } else if (entry instanceof CertEntry &&
+ ((CertEntry) entry).trustedKeyUsage != null) {
+ certElem = ((CertEntry) entry).cert;
+ } else {
+ continue;
}
if (certElem.equals(cert)) {
return alias;
@@ -918,26 +1106,32 @@ 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");
- }
+ if (privateKeyCount > 0 || secretKeyCount > 0) {
- byte[] safeContentData = createSafeContent();
- ContentInfo dataContentInfo = new ContentInfo(safeContentData);
- dataContentInfo.encode(authSafeContentInfo);
+ 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");
- }
+ if (certificateCount > 0) {
- byte[] encrData = createEncryptedData(password);
- ContentInfo encrContentInfo =
+ if (debug != null) {
+ debug.println("Storing " + certificateCount +
+ " certificate(s) in a PKCS#7 encryptedData content-type");
+ }
+
+ byte[] encrData = createEncryptedData(password);
+ ContentInfo encrContentInfo =
new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID,
new DerValue(encrData));
- encrContentInfo.encode(authSafeContentInfo);
+ encrContentInfo.encode(authSafeContentInfo);
+ }
// wrap as SequenceOf ContentInfos
DerOutputStream cInfo = new DerOutputStream();
@@ -962,6 +1156,207 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
stream.flush();
}
+ /**
+ * Gets a KeyStore.Entry
for the specified alias
+ * with the specified protection parameter.
+ *
+ * @param alias get the KeyStore.Entry
for this alias
+ * @param protParam the ProtectionParameter
+ * used to protect the Entry
,
+ * which may be null
+ *
+ * @return the KeyStore.Entry
for the specified alias,
+ * or null
if there is no such entry
+ *
+ * @exception KeyStoreException if the operation failed
+ * @exception NoSuchAlgorithmException if the algorithm for recovering the
+ * entry cannot be found
+ * @exception UnrecoverableEntryException if the specified
+ * protParam
were insufficient or invalid
+ * @exception UnrecoverableKeyException if the entry is a
+ * PrivateKeyEntry
or SecretKeyEntry
+ * and the specified protParam
does not contain
+ * the information needed to recover the key (e.g. wrong password)
+ *
+ * @since 1.5
+ */
+ @Override
+ public KeyStore.Entry engineGetEntry(String alias,
+ KeyStore.ProtectionParameter protParam)
+ throws KeyStoreException, NoSuchAlgorithmException,
+ UnrecoverableEntryException {
+
+ if (!engineContainsAlias(alias)) {
+ return null;
+ }
+
+ Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
+ if (protParam == null) {
+ if (engineIsCertificateEntry(alias)) {
+ if (entry instanceof CertEntry &&
+ ((CertEntry) entry).trustedKeyUsage != null) {
+
+ if (debug != null) {
+ debug.println("Retrieved a trusted certificate at " +
+ "alias '" + alias + "'");
+ }
+
+ return new KeyStore.TrustedCertificateEntry(
+ ((CertEntry)entry).cert, getAttributes(entry));
+ }
+ } else {
+ throw new UnrecoverableKeyException
+ ("requested entry requires a password");
+ }
+ }
+
+ if (protParam instanceof KeyStore.PasswordProtection) {
+ if (engineIsCertificateEntry(alias)) {
+ throw new UnsupportedOperationException
+ ("trusted certificate entries are not password-protected");
+ } else if (engineIsKeyEntry(alias)) {
+ KeyStore.PasswordProtection pp =
+ (KeyStore.PasswordProtection)protParam;
+ char[] password = pp.getPassword();
+
+ Key key = engineGetKey(alias, password);
+ if (key instanceof PrivateKey) {
+ Certificate[] chain = engineGetCertificateChain(alias);
+
+ return new KeyStore.PrivateKeyEntry((PrivateKey)key, chain,
+ getAttributes(entry));
+
+ } else if (key instanceof SecretKey) {
+
+ return new KeyStore.SecretKeyEntry((SecretKey)key,
+ getAttributes(entry));
+ }
+ } else if (!engineIsKeyEntry(alias)) {
+ throw new UnsupportedOperationException
+ ("untrusted certificate entries are not " +
+ "password-protected");
+ }
+ }
+
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Saves a KeyStore.Entry
under the specified alias.
+ * The specified protection parameter is used to protect the
+ * Entry
.
+ *
+ * If an entry already exists for the specified alias,
+ * it is overridden.
+ *
+ * @param alias save the KeyStore.Entry
under this alias
+ * @param entry the Entry
to save
+ * @param protParam the ProtectionParameter
+ * used to protect the Entry
,
+ * which may be null
+ *
+ * @exception KeyStoreException if this operation fails
+ *
+ * @since 1.5
+ */
+ @Override
+ public synchronized void engineSetEntry(String alias, KeyStore.Entry entry,
+ KeyStore.ProtectionParameter protParam) throws KeyStoreException {
+
+ // get password
+ if (protParam != null &&
+ !(protParam instanceof KeyStore.PasswordProtection)) {
+ throw new KeyStoreException("unsupported protection parameter");
+ }
+ KeyStore.PasswordProtection pProtect = null;
+ if (protParam != null) {
+ pProtect = (KeyStore.PasswordProtection)protParam;
+ }
+
+ // set entry
+ if (entry instanceof KeyStore.TrustedCertificateEntry) {
+ if (protParam != null && pProtect.getPassword() != null) {
+ // pre-1.5 style setCertificateEntry did not allow password
+ throw new KeyStoreException
+ ("trusted certificate entries are not password-protected");
+ } else {
+ KeyStore.TrustedCertificateEntry tce =
+ (KeyStore.TrustedCertificateEntry)entry;
+ setCertEntry(alias, tce.getTrustedCertificate(),
+ tce.getAttributes());
+
+ return;
+ }
+ } else if (entry instanceof KeyStore.PrivateKeyEntry) {
+ if (pProtect == null || pProtect.getPassword() == null) {
+ // pre-1.5 style setKeyEntry required password
+ throw new KeyStoreException
+ ("non-null password required to create PrivateKeyEntry");
+ } else {
+ KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry)entry;
+ setKeyEntry(alias, pke.getPrivateKey(), pProtect,
+ pke.getCertificateChain(), pke.getAttributes());
+
+ return;
+ }
+ } else if (entry instanceof KeyStore.SecretKeyEntry) {
+ if (pProtect == null || pProtect.getPassword() == null) {
+ // pre-1.5 style setKeyEntry required password
+ throw new KeyStoreException
+ ("non-null password required to create SecretKeyEntry");
+ } else {
+ KeyStore.SecretKeyEntry ske = (KeyStore.SecretKeyEntry)entry;
+ setKeyEntry(alias, ske.getSecretKey(), pProtect,
+ (Certificate[])null, ske.getAttributes());
+
+ return;
+ }
+ }
+
+ throw new KeyStoreException
+ ("unsupported entry type: " + entry.getClass().getName());
+ }
+
+ /*
+ * Assemble the entry attributes
+ */
+ private Set getAttributes(Entry entry) {
+
+ if (entry.attributes == null) {
+ entry.attributes = new HashSet<>();
+ }
+
+ // friendlyName
+ entry.attributes.add(new PKCS12Attribute(
+ PKCS9FriendlyName_OID.toString(), entry.alias));
+
+ // localKeyID
+ byte[] keyIdValue = entry.keyId;
+ if (keyIdValue != null) {
+ entry.attributes.add(new PKCS12Attribute(
+ PKCS9LocalKeyId_OID.toString(), Debug.toString(keyIdValue)));
+ }
+
+ // trustedKeyUsage
+ if (entry instanceof CertEntry) {
+ ObjectIdentifier[] trustedKeyUsageValue =
+ ((CertEntry) entry).trustedKeyUsage;
+ if (trustedKeyUsageValue != null) {
+ if (trustedKeyUsageValue.length == 1) { // omit brackets
+ entry.attributes.add(new PKCS12Attribute(
+ TrustedKeyUsage_OID.toString(),
+ trustedKeyUsageValue[0].toString()));
+ } else { // multi-valued
+ entry.attributes.add(new PKCS12Attribute(
+ TrustedKeyUsage_OID.toString(),
+ Arrays.toString(trustedKeyUsageValue)));
+ }
+ }
+ }
+
+ return entry.attributes;
+ }
+
/*
* Generate Hash.
*/
@@ -1036,11 +1431,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
/*
- * Create PKCS#12 Attributes, friendlyName and localKeyId.
+ * Create PKCS#12 Attributes, friendlyName, localKeyId and trustedKeyUsage.
*
* Although attributes are optional, they could be required.
* For e.g. localKeyId attribute is required to match the
* private key with the associated end-entity certificate.
+ * The trustedKeyUsage attribute is used to denote a trusted certificate.
*
* PKCS8ShroudedKeyBags include unique localKeyID and friendlyName.
* CertBags may or may not include attributes depending on the type
@@ -1062,20 +1458,28 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* friendlyName unique same/ same/ unique
* unique unique/
* null
+ * trustedKeyUsage - - - true
*
* Note: OpenSSL adds friendlyName for end-entity cert only, and
* removes the localKeyID and friendlyName for CA certs.
* If the CertBag did not have a friendlyName, most vendors will
* add it, and assign it to the DN of the cert.
*/
- private byte[] getBagAttributes(String alias, byte[] keyId)
- throws IOException {
+ private byte[] getBagAttributes(String alias, byte[] keyId,
+ Set attributes) throws IOException {
+ return getBagAttributes(alias, keyId, null, attributes);
+ }
+
+ private byte[] getBagAttributes(String alias, byte[] keyId,
+ ObjectIdentifier[] trustedUsage,
+ Set attributes) throws IOException {
byte[] localKeyID = null;
byte[] friendlyName = null;
+ byte[] trustedKeyUsage = null;
- // return null if both attributes are null
- if ((alias == null) && (keyId == null)) {
+ // return null if all three attributes are null
+ if ((alias == null) && (keyId == null) && (trustedKeyUsage == null)) {
return null;
}
@@ -1106,6 +1510,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
localKeyID = bagAttrValue2.toByteArray();
}
+ // Encode the trustedKeyUsage oid.
+ if (trustedUsage != null) {
+ DerOutputStream bagAttr3 = new DerOutputStream();
+ bagAttr3.putOID(TrustedKeyUsage_OID);
+ DerOutputStream bagAttrContent3 = new DerOutputStream();
+ DerOutputStream bagAttrValue3 = new DerOutputStream();
+ for (ObjectIdentifier usage : trustedUsage) {
+ bagAttrContent3.putOID(usage);
+ }
+ bagAttr3.write(DerValue.tag_Set, bagAttrContent3);
+ bagAttrValue3.write(DerValue.tag_Sequence, bagAttr3);
+ trustedKeyUsage = bagAttrValue3.toByteArray();
+ }
+
DerOutputStream attrs = new DerOutputStream();
if (friendlyName != null) {
attrs.write(friendlyName);
@@ -1113,11 +1531,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
if (localKeyID != null) {
attrs.write(localKeyID);
}
+ if (trustedKeyUsage != null) {
+ attrs.write(trustedKeyUsage);
+ }
+
+ if (attributes != null) {
+ for (KeyStore.Entry.Attribute attribute : attributes) {
+ attrs.write(((PKCS12Attribute) attribute).getEncoded());
+ }
+ }
+
bagAttrs.write(DerValue.tag_Set, attrs);
return bagAttrs.toByteArray();
}
-
/*
* Create EncryptedData content type, that contains EncryptedContentInfo.
* Includes certificates in individual SafeBags of type CertBag.
@@ -1128,17 +1555,26 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
throws CertificateException, IOException
{
DerOutputStream out = new DerOutputStream();
- for (Enumeration e = entries.keys(); e.hasMoreElements(); ) {
+ for (Enumeration e = engineAliases(); e.hasMoreElements(); ) {
String alias = e.nextElement();
- KeyEntry entry = entries.get(alias);
+ Entry entry = entries.get(alias);
// certificate chain
- int chainLen;
- if (entry.chain == null) {
- chainLen = 0;
- } else {
- chainLen = entry.chain.length;
+ int chainLen = 1;
+ Certificate[] certs = null;
+
+ if (entry instanceof PrivateKeyEntry) {
+ PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
+ if (keyEntry.chain == null) {
+ chainLen = 0;
+ } else {
+ chainLen = keyEntry.chain.length;
+ }
+ certs = keyEntry.chain;
+
+ } else if (entry instanceof CertEntry) {
+ certs = new Certificate[]{((CertEntry) entry).cert};
}
for (int i = 0; i < chainLen; i++) {
@@ -1152,7 +1588,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
// write encoded certs in a context-specific tag
DerOutputStream certValue = new DerOutputStream();
- X509Certificate cert = (X509Certificate)entry.chain[i];
+ X509Certificate cert = (X509Certificate) certs[i];
certValue.putOctetString(cert.getEncoded());
certBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
true, (byte) 0), certValue);
@@ -1175,7 +1611,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
byte[] bagAttrs = null;
if (i == 0) {
// Only End-Entity Cert should have a localKeyId.
- bagAttrs = getBagAttributes(entry.alias, entry.keyId);
+ if (entry instanceof KeyEntry) {
+ KeyEntry keyEntry = (KeyEntry) entry;
+ bagAttrs =
+ getBagAttributes(keyEntry.alias, keyEntry.keyId,
+ keyEntry.attributes);
+ } else {
+ CertEntry certEntry = (CertEntry) entry;
+ bagAttrs =
+ getBagAttributes(certEntry.alias, certEntry.keyId,
+ certEntry.trustedKeyUsage,
+ certEntry.attributes);
+ }
} else {
// Trusted root CA certs and Intermediate CA certs do not
// need to have a localKeyId, and hence localKeyId is null
@@ -1184,7 +1631,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
// certificate chain to have unique or null localKeyID.
// However, IE/OpenSSL do not impose this restriction.
bagAttrs = getBagAttributes(
- cert.getSubjectX500Principal().getName(), null);
+ cert.getSubjectX500Principal().getName(), null,
+ entry.attributes);
}
if (bagAttrs != null) {
safeBag.write(bagAttrs);
@@ -1214,6 +1662,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
/*
* Create SafeContent Data content type.
+ * Includes encrypted secret key in a SafeBag of type SecretBag.
* Includes encrypted private key in a SafeBag of type PKCS8ShroudedKeyBag.
* Each PKCS8ShroudedKeyBag includes pkcs12 attributes
* (see comments in getBagAttributes)
@@ -1222,33 +1671,74 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
throws CertificateException, IOException {
DerOutputStream out = new DerOutputStream();
- for (Enumeration e = entries.keys(); e.hasMoreElements(); ) {
+ for (Enumeration e = engineAliases(); e.hasMoreElements(); ) {
String alias = e.nextElement();
- KeyEntry entry = entries.get(alias);
-
- // Create SafeBag of type pkcs8ShroudedKeyBag
- DerOutputStream safeBag = new DerOutputStream();
- safeBag.putOID(PKCS8ShroudedKeyBag_OID);
-
- // get the encrypted private key
- byte[] encrBytes = entry.protectedPrivKey;
- EncryptedPrivateKeyInfo encrInfo = null;
- try {
- encrInfo = new EncryptedPrivateKeyInfo(encrBytes);
- } catch (IOException ioe) {
- throw new IOException("Private key not stored as "
- + "PKCS#8 EncryptedPrivateKeyInfo" + ioe.getMessage());
+ Entry entry = entries.get(alias);
+ if (entry == null || (!(entry instanceof KeyEntry))) {
+ continue;
}
+ DerOutputStream safeBag = new DerOutputStream();
+ KeyEntry keyEntry = (KeyEntry) entry;
- // Wrap the EncryptedPrivateKeyInfo in a context-specific tag.
- DerOutputStream bagValue = new DerOutputStream();
- bagValue.write(encrInfo.getEncoded());
- safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+ // DER encode the private key
+ if (keyEntry instanceof PrivateKeyEntry) {
+ // Create SafeBag of type pkcs8ShroudedKeyBag
+ safeBag.putOID(PKCS8ShroudedKeyBag_OID);
+
+ // get the encrypted private key
+ byte[] encrBytes = ((PrivateKeyEntry)keyEntry).protectedPrivKey;
+ EncryptedPrivateKeyInfo encrInfo = null;
+ try {
+ encrInfo = new EncryptedPrivateKeyInfo(encrBytes);
+
+ } catch (IOException ioe) {
+ throw new IOException("Private key not stored as "
+ + "PKCS#8 EncryptedPrivateKeyInfo"
+ + ioe.getMessage());
+ }
+
+ // Wrap the EncryptedPrivateKeyInfo in a context-specific tag.
+ DerOutputStream bagValue = new DerOutputStream();
+ bagValue.write(encrInfo.getEncoded());
+ safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
true, (byte) 0), bagValue);
+ // DER encode the secret key
+ } else if (keyEntry instanceof SecretKeyEntry) {
+ // Create SafeBag of type SecretBag
+ safeBag.putOID(SecretBag_OID);
+
+ // Create a SecretBag
+ DerOutputStream secretBag = new DerOutputStream();
+ secretBag.putOID(PKCS8ShroudedKeyBag_OID);
+
+ // Write secret key in a context-specific tag
+ DerOutputStream secretKeyValue = new DerOutputStream();
+ secretKeyValue.putOctetString(
+ ((SecretKeyEntry) keyEntry).protectedSecretKey);
+ secretBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+ true, (byte) 0), secretKeyValue);
+
+ // Wrap SecretBag in a Sequence
+ DerOutputStream secretBagSeq = new DerOutputStream();
+ secretBagSeq.write(DerValue.tag_Sequence, secretBag);
+ byte[] secretBagValue = secretBagSeq.toByteArray();
+
+ // Wrap the secret bag in a context-specific tag.
+ DerOutputStream bagValue = new DerOutputStream();
+ bagValue.write(secretBagValue);
+
+ // Write SafeBag value
+ safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+ true, (byte) 0), bagValue);
+ } else {
+ continue; // skip this entry
+ }
+
// write SafeBag Attributes
- byte[] bagAttrs = getBagAttributes(alias, entry.keyId);
+ byte[] bagAttrs =
+ getBagAttributes(alias, entry.keyId, entry.attributes);
safeBag.write(bagAttrs);
// wrap as Sequence
@@ -1377,8 +1867,10 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
DerValue[] safeContentsArray = as.getSequence(2);
int count = safeContentsArray.length;
- // reset the count at the start
+ // reset the counters at the start
privateKeyCount = 0;
+ secretKeyCount = 0;
+ certificateCount = 0;
/*
* Spin over the ContentInfos.
@@ -1445,7 +1937,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
continue;
}
throw new IOException(
- "failed to decrypt safe contents entry: " + e, e);
+ "failed to decrypt safe contents entry: " + e, e);
}
}
} else {
@@ -1493,9 +1985,10 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
/*
* Match up private keys with certificate chains.
*/
- KeyEntry[] list = keyList.toArray(new KeyEntry[keyList.size()]);
+ PrivateKeyEntry[] list =
+ keyList.toArray(new PrivateKeyEntry[keyList.size()]);
for (int m = 0; m < list.length; m++) {
- KeyEntry entry = list[m];
+ PrivateKeyEntry entry = list[m];
if (entry.keyId != null) {
ArrayList chain =
new ArrayList();
@@ -1513,6 +2006,22 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
entry.chain = chain.toArray(new Certificate[chain.size()]);
}
}
+
+ if (debug != null) {
+ if (privateKeyCount > 0) {
+ debug.println("Loaded " + privateKeyCount +
+ " protected private key(s)");
+ }
+ if (secretKeyCount > 0) {
+ debug.println("Loaded " + secretKeyCount +
+ " protected secret key(s)");
+ }
+ if (certificateCount > 0) {
+ debug.println("Loaded " + certificateCount +
+ " certificate(s)");
+ }
+ }
+
certEntries.clear();
certsMap.clear();
keyList.clear();
@@ -1523,7 +2032,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* @param entry the KeyEntry to match
* @return a certificate, null if not found
*/
- private X509Certificate findMatchedCertificate(KeyEntry entry) {
+ private X509Certificate findMatchedCertificate(PrivateKeyEntry entry) {
CertEntry keyIdMatch = null;
CertEntry aliasMatch = null;
for (CertEntry ce: certEntries) {
@@ -1567,7 +2076,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
}
bagValue = bagValue.data.getDerValue();
if (bagId.equals((Object)PKCS8ShroudedKeyBag_OID)) {
- KeyEntry kEntry = new KeyEntry();
+ PrivateKeyEntry kEntry = new PrivateKeyEntry();
kEntry.protectedPrivKey = bagValue.toByteArray();
bagItem = kEntry;
privateKeyCount++;
@@ -1585,6 +2094,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
cert = (X509Certificate)cf.generateCertificate
(new ByteArrayInputStream(certValue.getOctetString()));
bagItem = cert;
+ certificateCount++;
+ } else if (bagId.equals((Object)SecretBag_OID)) {
+ DerInputStream ss = new DerInputStream(bagValue.toByteArray());
+ DerValue[] secretValues = ss.getSequence(2);
+ ObjectIdentifier secretId = secretValues[0].getOID();
+ if (!secretValues[1].isContextSpecific((byte)0)) {
+ throw new IOException(
+ "unsupported PKCS12 secret value type "
+ + secretValues[1].tag);
+ }
+ DerValue secretValue = secretValues[1].data.getDerValue();
+ SecretKeyEntry kEntry = new SecretKeyEntry();
+ kEntry.protectedSecretKey = secretValue.getOctetString();
+ bagItem = kEntry;
} else {
if (debug != null) {
@@ -1594,7 +2117,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
DerValue[] attrSet;
try {
- attrSet = sbi.getSet(2);
+ attrSet = sbi.getSet(3);
} catch (IOException e) {
// entry does not have attributes
// Note: CA certs can have no attributes
@@ -1604,11 +2127,13 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
String alias = null;
byte[] keyId = null;
+ ObjectIdentifier[] trustedKeyUsage = null;
+ Set attributes = new HashSet<>();
if (attrSet != null) {
for (int j = 0; j < attrSet.length; j++) {
- DerInputStream as =
- new DerInputStream(attrSet[j].toByteArray());
+ byte[] encoded = attrSet[j].toByteArray();
+ DerInputStream as = new DerInputStream(encoded);
DerValue[] attrSeq = as.getSequence(2);
ObjectIdentifier attrId = attrSeq[0].getOID();
DerInputStream vs =
@@ -1624,12 +2149,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
alias = valSet[0].getBMPString();
} else if (attrId.equals((Object)PKCS9LocalKeyId_OID)) {
keyId = valSet[0].getOctetString();
- } else {
-
- if (debug != null) {
- debug.println("Unsupported PKCS12 bag attribute: " +
- attrId);
+ } else if
+ (attrId.equals((Object)TrustedKeyUsage_OID)) {
+ trustedKeyUsage = new ObjectIdentifier[valSet.length];
+ for (int k = 0; k < valSet.length; k++) {
+ trustedKeyUsage[k] = valSet[k].getOID();
}
+ } else {
+ attributes.add(new PKCS12Attribute(encoded));
}
}
}
@@ -1645,16 +2172,19 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
*/
if (bagItem instanceof KeyEntry) {
KeyEntry entry = (KeyEntry)bagItem;
- if (keyId == null) {
- // Insert a localKeyID for the privateKey
- // Note: This is a workaround to allow null localKeyID
- // attribute in pkcs12 with one private key entry and
- // associated cert-chain
- if (privateKeyCount == 1) {
- keyId = "01".getBytes("UTF8");
- } else {
- continue;
- }
+
+ if (bagItem instanceof PrivateKeyEntry) {
+ if (keyId == null) {
+ // Insert a localKeyID for the privateKey
+ // Note: This is a workaround to allow null localKeyID
+ // attribute in pkcs12 with one private key entry and
+ // associated cert-chain
+ if (privateKeyCount == 1) {
+ keyId = "01".getBytes("UTF8");
+ } else {
+ continue;
+ }
+ }
}
entry.keyId = keyId;
// restore date if it exists
@@ -1672,11 +2202,16 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
date = new Date();
}
entry.date = date;
- keyList.add(entry);
- if (alias == null)
+
+ if (bagItem instanceof PrivateKeyEntry) {
+ keyList.add((PrivateKeyEntry) entry);
+ }
+ if (alias == null) {
alias = getUnfriendlyName();
+ }
entry.alias = alias;
entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
+
} else if (bagItem instanceof X509Certificate) {
X509Certificate cert = (X509Certificate)bagItem;
// Insert a localKeyID for the corresponding cert
@@ -1689,7 +2224,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
keyId = "01".getBytes("UTF8");
}
}
- certEntries.add(new CertEntry(cert, keyId, alias));
+ if (alias == null) {
+ alias = getUnfriendlyName();
+ }
+ // Trusted certificate
+ if (trustedKeyUsage != null) {
+ CertEntry certEntry =
+ new CertEntry(cert, keyId, alias, trustedKeyUsage,
+ attributes);
+ entries.put(alias.toLowerCase(Locale.ENGLISH), certEntry);
+ } else {
+ certEntries.add(new CertEntry(cert, keyId, alias));
+ }
X500Principal subjectDN = cert.getSubjectX500Principal();
if (subjectDN != null) {
if (!certsMap.containsKey(subjectDN)) {
diff --git a/jdk/src/share/classes/sun/security/x509/AlgorithmId.java b/jdk/src/share/classes/sun/security/x509/AlgorithmId.java
index e2d6c60c111..f34d973fc73 100644
--- a/jdk/src/share/classes/sun/security/x509/AlgorithmId.java
+++ b/jdk/src/share/classes/sun/security/x509/AlgorithmId.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -502,6 +502,11 @@ public class AlgorithmId implements Serializable, DerEncoder {
return AlgorithmId.ECDH_oid;
}
+ // Secret key algorithms
+ if (name.equalsIgnoreCase("AES")) {
+ return AlgorithmId.AES_oid;
+ }
+
// Common signature types
if (name.equalsIgnoreCase("MD5withRSA")
|| name.equalsIgnoreCase("MD5/RSA")) {
@@ -660,6 +665,12 @@ public class AlgorithmId implements Serializable, DerEncoder {
public static final ObjectIdentifier RSA_oid;
public static final ObjectIdentifier RSAEncryption_oid;
+ /*
+ * COMMON SECRET KEY TYPES
+ */
+ public static final ObjectIdentifier AES_oid =
+ oid(2, 16, 840, 1, 101, 3, 4, 1);
+
/*
* COMMON SIGNATURE ALGORITHMS
*/
@@ -893,6 +904,8 @@ public class AlgorithmId implements Serializable, DerEncoder {
nameTable.put(EC_oid, "EC");
nameTable.put(ECDH_oid, "ECDH");
+ nameTable.put(AES_oid, "AES");
+
nameTable.put(sha1WithECDSA_oid, "SHA1withECDSA");
nameTable.put(sha224WithECDSA_oid, "SHA224withECDSA");
nameTable.put(sha256WithECDSA_oid, "SHA256withECDSA");
diff --git a/jdk/test/sun/security/pkcs12/StorePasswordTest.java b/jdk/test/sun/security/pkcs12/StorePasswordTest.java
new file mode 100644
index 00000000000..fc2f77c51c1
--- /dev/null
+++ b/jdk/test/sun/security/pkcs12/StorePasswordTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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 8005408
+ * @summary KeyStore API enhancements
+ */
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import javax.crypto.*;
+import javax.crypto.spec.*;
+import java.security.spec.InvalidKeySpecException;
+
+// Store a password in a keystore and retrieve it again.
+
+public class StorePasswordTest {
+ private final static String DIR = System.getProperty("test.src", ".");
+ private static final char[] PASSWORD = "passphrase".toCharArray();
+ private static final String KEYSTORE = "pwdstore.p12";
+ private static final String ALIAS = "my password";
+ private static final String USER_PASSWORD = "hello1";
+
+ public static void main(String[] args) throws Exception {
+
+ new File(KEYSTORE).delete();
+
+ try {
+
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+ keystore.load(null, null);
+
+ // Set entry
+ keystore.setEntry(ALIAS,
+ new KeyStore.SecretKeyEntry(convertPassword(USER_PASSWORD)),
+ new KeyStore.PasswordProtection(PASSWORD));
+
+ System.out.println("Storing keystore to: " + KEYSTORE);
+ keystore.store(new FileOutputStream(KEYSTORE), PASSWORD);
+
+ System.out.println("Loading keystore from: " + KEYSTORE);
+ keystore.load(new FileInputStream(KEYSTORE), PASSWORD);
+ System.out.println("Loaded keystore with " + keystore.size() +
+ " entries");
+ KeyStore.Entry entry = keystore.getEntry(ALIAS,
+ new KeyStore.PasswordProtection(PASSWORD));
+ System.out.println("Retrieved entry: " + entry);
+
+ SecretKey key = (SecretKey) keystore.getKey(ALIAS, PASSWORD);
+ SecretKeyFactory factory =
+ SecretKeyFactory.getInstance(key.getAlgorithm());
+ PBEKeySpec keySpec =
+ (PBEKeySpec) factory.getKeySpec(key, PBEKeySpec.class);
+ char[] pwd = keySpec.getPassword();
+ System.out.println("Recovered credential: " + new String(pwd));
+
+ if (!Arrays.equals(USER_PASSWORD.toCharArray(), pwd)) {
+ throw new Exception("Failed to recover the stored password");
+ }
+ } finally {
+ new File(KEYSTORE).delete();
+ }
+ }
+
+ private static SecretKey convertPassword(String password)
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
+ return factory.generateSecret(new PBEKeySpec(password.toCharArray()));
+ }
+}
diff --git a/jdk/test/sun/security/pkcs12/StoreSecretKeyTest.java b/jdk/test/sun/security/pkcs12/StoreSecretKeyTest.java
new file mode 100644
index 00000000000..9a91148a41b
--- /dev/null
+++ b/jdk/test/sun/security/pkcs12/StoreSecretKeyTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 8005408
+ * @summary KeyStore API enhancements
+ */
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import javax.crypto.*;
+import javax.crypto.spec.*;
+
+// Store a secret key in a keystore and retrieve it again.
+
+public class StoreSecretKeyTest {
+ private final static String DIR = System.getProperty("test.src", ".");
+ private static final char[] PASSWORD = "passphrase".toCharArray();
+ private static final String KEYSTORE = "keystore.p12";
+ private static final String ALIAS = "my secret key";
+
+ public static void main(String[] args) throws Exception {
+
+ new File(KEYSTORE).delete();
+
+ try {
+
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+ keystore.load(null, null);
+
+ // Set entry
+ keystore.setEntry(ALIAS,
+ new KeyStore.SecretKeyEntry(generateSecretKey("AES", 128)),
+ new KeyStore.PasswordProtection(PASSWORD));
+
+ System.out.println("Storing keystore to: " + KEYSTORE);
+ keystore.store(new FileOutputStream(KEYSTORE), PASSWORD);
+
+ System.out.println("Loading keystore from: " + KEYSTORE);
+ keystore.load(new FileInputStream(KEYSTORE), PASSWORD);
+ System.out.println("Loaded keystore with " + keystore.size() +
+ " entries");
+ KeyStore.Entry entry = keystore.getEntry(ALIAS,
+ new KeyStore.PasswordProtection(PASSWORD));
+ System.out.println("Retrieved entry: " + entry);
+
+ if (entry instanceof KeyStore.SecretKeyEntry) {
+ System.out.println("Retrieved secret key entry: " +
+ entry);
+ } else {
+ throw new Exception("Not a secret key entry");
+ }
+ } finally {
+ new File(KEYSTORE).delete();
+ }
+ }
+
+ private static SecretKey generateSecretKey(String algorithm, int size)
+ throws NoSuchAlgorithmException {
+ KeyGenerator generator = KeyGenerator.getInstance(algorithm);
+ generator.init(size);
+ return generator.generateKey();
+ }
+}
diff --git a/jdk/test/sun/security/pkcs12/StoreTrustedCertTest.java b/jdk/test/sun/security/pkcs12/StoreTrustedCertTest.java
new file mode 100644
index 00000000000..a1481749d7e
--- /dev/null
+++ b/jdk/test/sun/security/pkcs12/StoreTrustedCertTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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 8005408
+ * @summary KeyStore API enhancements
+ */
+
+import java.io.*;
+import java.security.*;
+import java.security.cert.*;
+import java.util.*;
+import java.security.cert.Certificate;
+import javax.crypto.*;
+import javax.crypto.spec.*;
+
+// Store a trusted certificate in a keystore and retrieve it again.
+
+public class StoreTrustedCertTest {
+ private final static String DIR = System.getProperty("test.src", ".");
+ private static final char[] PASSWORD = "passphrase".toCharArray();
+ private static final String KEYSTORE = "truststore.p12";
+ private static final String CERT = DIR + "/trusted.pem";
+ private static final String ALIAS = "my trustedcert";
+ private static final String ALIAS2 = "my trustedcert with attributes";
+
+ public static void main(String[] args) throws Exception {
+
+ new File(KEYSTORE).delete();
+
+ try {
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+ keystore.load(null, null);
+
+ Certificate cert = loadCertificate(CERT);
+ Set attributes = new HashSet<>();
+ attributes.add(new PKCS12Attribute("1.3.5.7.9", "that's odd"));
+ attributes.add(new PKCS12Attribute("2.4.6.8.10", "that's even"));
+
+ // Set trusted certificate entry
+ keystore.setEntry(ALIAS,
+ new KeyStore.TrustedCertificateEntry(cert), null);
+
+ // Set trusted certificate entry with attributes
+ keystore.setEntry(ALIAS2,
+ new KeyStore.TrustedCertificateEntry(cert, attributes), null);
+
+ System.out.println("Storing keystore to: " + KEYSTORE);
+ keystore.store(new FileOutputStream(KEYSTORE), PASSWORD);
+
+ System.out.println("Loading keystore from: " + KEYSTORE);
+ keystore.load(new FileInputStream(KEYSTORE), PASSWORD);
+ System.out.println("Loaded keystore with " + keystore.size() +
+ " entries");
+
+ KeyStore.Entry entry = keystore.getEntry(ALIAS, null);
+ if (entry instanceof KeyStore.TrustedCertificateEntry) {
+ System.out.println("Retrieved trusted certificate entry: " +
+ entry);
+ } else {
+ throw new Exception("Not a trusted certificate entry");
+ }
+ System.out.println();
+
+ entry = keystore.getEntry(ALIAS2, null);
+ if (entry instanceof KeyStore.TrustedCertificateEntry) {
+ KeyStore.TrustedCertificateEntry trustedEntry =
+ (KeyStore.TrustedCertificateEntry) entry;
+ Set entryAttributes =
+ trustedEntry.getAttributes();
+
+ if (entryAttributes.containsAll(attributes)) {
+ System.out.println("Retrieved trusted certificate entry " +
+ "with attributes: " + entry);
+ } else {
+ throw new Exception("Failed to retrieve entry attributes");
+ }
+ } else {
+ throw new Exception("Not a trusted certificate entry");
+ }
+
+ } finally {
+ new File(KEYSTORE).delete();
+ }
+ }
+
+ private static Certificate loadCertificate(String certFile)
+ throws Exception {
+ X509Certificate cert = null;
+ try (FileInputStream certStream = new FileInputStream(certFile)) {
+ CertificateFactory factory =
+ CertificateFactory.getInstance("X.509");
+ return factory.generateCertificate(certStream);
+ }
+ }
+}
diff --git a/jdk/test/sun/security/pkcs12/trusted.pem b/jdk/test/sun/security/pkcs12/trusted.pem
new file mode 100644
index 00000000000..32e7b84f357
--- /dev/null
+++ b/jdk/test/sun/security/pkcs12/trusted.pem
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIF5DCCBMygAwIBAgIQGVCD3zqdD1ZMZZ/zLAPnQzANBgkqhkiG9w0BAQUFADCBvDELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29t
+L3JwYSAoYykxMDE2MDQGA1UEAxMtVmVyaVNpZ24gQ2xhc3MgMyBJbnRlcm5hdGlvbmFsIFNlcnZl
+ciBDQSAtIEczMB4XDTEyMDcxMDAwMDAwMFoXDTEzMDczMTIzNTk1OVowgbgxCzAJBgNVBAYTAlVT
+MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQHFA5SZWR3b29kIFNob3JlczEbMBkGA1UEChQS
+T3JhY2xlIENvcnBvcmF0aW9uMRIwEAYDVQQLFAlHbG9iYWwgSVQxMzAxBgNVBAsUKlRlcm1zIG9m
+IHVzZSBhdCB3d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNTEVMBMGA1UEAxQMKi5vcmFjbGUuY29t
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/dOCGrWzPj62q0ZkF59Oj9Fli4wHAuX
+U4/S0yBXF8j6K7TKWFTQkGZt3+08KUhmLm1CE1DbbyRJT292YNXYXunNaKdABob8kaBO/NESUOEJ
+0SZh7fd0xCSJAAPiwOMrM5jLeb/dEpU6nP74Afrhu5ffvKdcvTRGguj9H2oVsisTK8Z1HsiiwcJG
+JXcrjvdCZoPU4FHvK03XZPAqPHKNSaJOrux6kRIWYjQMlmL+qDOb0nNHa6gBdi+VqqJHJHeAM677
+dcUd0jn2m2OWtUnrM3MJZQof7/z27RTdX5J8np0ChkUgm63biDgRZO7uZP0DARQ0I6lZMlrarT8/
+sct3twIDAQABo4IB4jCCAd4wFwYDVR0RBBAwDoIMKi5vcmFjbGUuY29tMAkGA1UdEwQCMAAwCwYD
+VR0PBAQDAgWgMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHFwMwKjAoBggrBgEFBQcCARYcaHR0cHM6
+Ly93d3cudmVyaXNpZ24uY29tL3JwYTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwbgYI
+KwYBBQUHAQwEYjBgoV6gXDBaMFgwVhYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUS2u5KJYGDLvQ
+UjibKaxLB4shBRgwJhYkaHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nbzEuZ2lmMHIGCCsG
+AQUFBwEBBGYwZDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AudmVyaXNpZ24uY29tMDwGCCsGAQUF
+BzAChjBodHRwOi8vc3ZyaW50bC1nMy1haWEudmVyaXNpZ24uY29tL1NWUkludGxHMy5jZXIwQQYD
+VR0fBDowODA2oDSgMoYwaHR0cDovL3N2cmludGwtZzMtY3JsLnZlcmlzaWduLmNvbS9TVlJJbnRs
+RzMuY3JsMB8GA1UdIwQYMBaAFNebfNgioBX33a1fzimbWMO8RgC1MA0GCSqGSIb3DQEBBQUAA4IB
+AQAITRBlEo+qXLwCL53Db2BGnhDgnSomjne8aCmU7Yt4Kp91tzJdhNuaC/wwDuzD2dPJqzemae3s
+wKiOXrmDQZDj9NNTdkrXHnCvDR4TpOynWe3zBa0bwKnV2cIRKcv482yV53u0kALyFZbagYPwOOz3
+YJA/2SqdcDn9Ztc/ABQ1SkyXyA5j4LJdf2g7BtYrFxjy0RG6We2iM781WSB/9MCNKyHgiwd3KpLf
+urdSKLzy1elNAyt1P3UHwBIIvZ6sJIr/eeELc54Lxt6PtQCXx8qwxYTYXWPXbLgKBHdebgrmAbPK
+TfD69wysvjk6vwSHjmvaqB4R4WRcgkuT+1gxx+ve
+-----END CERTIFICATE-----