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: + * + * 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 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-----