# HG changeset patch # User vinnie # Date 1510856897 0 # Node ID 0134b20e49ba0d038b0e5bcf930bb3b2acca7982 # Parent 827b23f9c2317bf87fe54360d154c267ce387b4b 8005408: KeyStore API enhancements Reviewed-by: mullan diff -r 827b23f9c231 -r 0134b20e49ba src/share/classes/java/security/KeyStore.java --- a/src/share/classes/java/security/KeyStore.java Wed Nov 15 02:54:40 2017 +0000 +++ b/src/share/classes/java/security/KeyStore.java Thu Nov 16 18:28:17 2017 +0000 @@ -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; @@ -34,6 +35,10 @@ import javax.security.auth.callback.*; +import sun.misc.JavaSecurityKeyStoreAccess; +import sun.misc.SharedSecrets; + +import sun.security.pkcs12.PKCS12Attribute; import sun.security.util.Debug; /** @@ -182,6 +187,43 @@ private static final boolean skipDebug = Debug.isOn("engine=") && !Debug.isOn("keystore"); + // Set up JavaSecurityKeyStoreAccess in SharedSecrets + static { + SharedSecrets.setJavaSecurityKeyStoreAccess(new JavaSecurityKeyStoreAccess() { + public PrivateKeyEntry constructPrivateKeyEntry(PrivateKey privateKey, + Certificate[] chain, + Set attributes) { + return new PrivateKeyEntry(privateKey, chain, attributes); + } + + @Override + public Set getPrivateKeyEntryAttributes(PrivateKeyEntry entry) { + return entry.getAttributes(); + } + + @Override + public SecretKeyEntry constructSecretKeyEntry(SecretKey secretKey, + Set attributes) { + return new SecretKeyEntry(secretKey, attributes); + } + + @Override + public Set getSecretKeyEntryAttributes(SecretKeyEntry entry) { + return entry.getAttributes(); + } + + public TrustedCertificateEntry constructTrustedCertificateEntry(Certificate trustedCert, + Set attributes) { + return new TrustedCertificateEntry(trustedCert, attributes); + } + + @Override + public Set getTrustedCertificateEntryAttributes(TrustedCertificateEntry entry) { + return entry.getAttributes(); + } + }); + } + /* * Constant to lookup in the Security properties file to determine * the default keystore type. @@ -355,6 +397,7 @@ private final PrivateKey privKey; private final Certificate[] chain; + private final Set attributes; /** * Constructs a PrivateKeyEntry with a @@ -381,7 +424,39 @@ * 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 + */ + private PrivateKeyEntry(PrivateKey privateKey, Certificate[] chain, + Set attributes) { + + if (privateKey == null || chain == null || attributes == null) { throw new NullPointerException("invalid null input"); } if (chain.length == 0) { @@ -416,6 +491,9 @@ } else { this.chain = clonedChain; } + + this.attributes = + Collections.unmodifiableSet(new HashSet<>(attributes)); } /** @@ -457,6 +535,18 @@ } /** + * Retrieves the attributes associated with an entry. + *

+ * + * @return an unmodifiable {@code Set} of attributes, possibly empty + * + * @since 1.8 + */ + private Set getAttributes() { + return attributes; + } + + /** * Returns a string representation of this PrivateKeyEntry. * @return a string representation of this PrivateKeyEntry. */ @@ -481,6 +571,7 @@ public static final class SecretKeyEntry implements Entry { private final SecretKey sKey; + private final Set attributes; /** * Constructs a SecretKeyEntry with a @@ -496,6 +587,32 @@ 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 + */ + private 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)); } /** @@ -508,6 +625,18 @@ } /** + * Retrieves the attributes associated with an entry. + *

+ * + * @return an unmodifiable {@code Set} of attributes, possibly empty + * + * @since 1.8 + */ + private Set getAttributes() { + return attributes; + } + + /** * Returns a string representation of this SecretKeyEntry. * @return a string representation of this SecretKeyEntry. */ @@ -525,6 +654,7 @@ public static final class TrustedCertificateEntry implements Entry { private final Certificate cert; + private final Set attributes; /** * Constructs a TrustedCertificateEntry with a @@ -540,6 +670,32 @@ 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 + */ + private 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)); } /** @@ -552,6 +708,18 @@ } /** + * Retrieves the attributes associated with an entry. + *

+ * + * @return an unmodifiable {@code Set} of attributes, possibly empty + * + * @since 1.8 + */ + private Set getAttributes() { + return attributes; + } + + /** * Returns a string representation of this TrustedCertificateEntry. * @return a string representation of this TrustedCertificateEntry. */ diff -r 827b23f9c231 -r 0134b20e49ba src/share/classes/sun/misc/JavaSecurityKeyStoreAccess.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/misc/JavaSecurityKeyStoreAccess.java Thu Nov 16 18:28:17 2017 +0000 @@ -0,0 +1,139 @@ +/* + * Copyright 2017 Red Hat, Inc. + * 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 sun.misc; + +import java.security.cert.Certificate; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.SecretKeyEntry; +import java.security.KeyStore.TrustedCertificateEntry; +import java.security.PrivateKey; + +import javax.crypto.SecretKey; + +import java.util.Set; + +import sun.security.pkcs12.PKCS12Attribute; + +/** + * Shared secret interface to allow us + * to create key entries which hold a set of + * PKCS12Attribute objects. + */ +public interface JavaSecurityKeyStoreAccess { + + /** + * 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 + */ + PrivateKeyEntry constructPrivateKeyEntry(PrivateKey privateKey, Certificate[] chain, + Set attributes); + + /** + * Retrieves the attributes associated with a {@code PrivateKeyEntry}. + *

+ * + * @return an unmodifiable {@code Set} of attributes, possibly empty + * + * @since 1.8 + */ + Set getPrivateKeyEntryAttributes(PrivateKeyEntry entry); + + /** + * 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 + */ + SecretKeyEntry constructSecretKeyEntry(SecretKey secretKey, Set attributes); + + /** + * Retrieves the attributes associated with a {@code SecretKeyEntry}. + *

+ * + * @return an unmodifiable {@code Set} of attributes, possibly empty + * + * @since 1.8 + */ + Set getSecretKeyEntryAttributes(SecretKeyEntry entry); + + /** + * 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 + */ + TrustedCertificateEntry constructTrustedCertificateEntry(Certificate trustedCert, + Set attributes); + + /** + * Retrieves the attributes associated with a {@code TrustedCertificateEntry}. + *

+ * + * @return an unmodifiable {@code Set} of attributes, possibly empty + * + * @since 1.8 + */ + Set getTrustedCertificateEntryAttributes(TrustedCertificateEntry entry); +} diff -r 827b23f9c231 -r 0134b20e49ba src/share/classes/sun/misc/SharedSecrets.java --- a/src/share/classes/sun/misc/SharedSecrets.java Wed Nov 15 02:54:40 2017 +0000 +++ b/src/share/classes/sun/misc/SharedSecrets.java Thu Nov 16 18:28:17 2017 +0000 @@ -29,6 +29,7 @@ import java.io.FileDescriptor; import java.io.ObjectInputStream; import java.security.AccessController; +import java.security.KeyStore; import java.security.ProtectionDomain; import java.util.GregorianCalendar; import java.util.jar.JarFile; @@ -63,6 +64,7 @@ private static JavaOISAccess javaOISAccess; private static JavaObjectInputStreamAccess javaObjectInputStreamAccess; private static JavaUtilCalendarAccess javaUtilCalendarAccess; + private static JavaSecurityKeyStoreAccess javaSecurityKeyStoreAccess; public static JavaUtilJarAccess javaUtilJarAccess() { if (javaUtilJarAccess == null) { @@ -238,4 +240,14 @@ public static void setJavaUtilCalendarAccess(JavaUtilCalendarAccess access) { javaUtilCalendarAccess = access; } + + public static void setJavaSecurityKeyStoreAccess(JavaSecurityKeyStoreAccess jsksa) { + javaSecurityKeyStoreAccess = jsksa; + } + + public static JavaSecurityKeyStoreAccess getJavaSecurityKeyStoreAccess() { + if (javaSecurityKeyStoreAccess == null) + unsafe.ensureClassInitialized(KeyStore.class); + return javaSecurityKeyStoreAccess; + } } diff -r 827b23f9c231 -r 0134b20e49ba src/share/classes/sun/security/pkcs12/PKCS12Attribute.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/security/pkcs12/PKCS12Attribute.java Thu Nov 16 18:28:17 2017 +0000 @@ -0,0 +1,283 @@ +/* + * 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 sun.security.pkcs12; + +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 { + + 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 + */ + 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 + */ + 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 -r 827b23f9c231 -r 0134b20e49ba src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java --- a/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java Wed Nov 15 02:54:40 2017 +0000 +++ b/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java Thu Nov 16 18:28:17 2017 +0000 @@ -30,27 +30,35 @@ import java.security.NoSuchAlgorithmException; import java.security.Key; import java.security.KeyFactory; -import java.security.PrivateKey; +import java.security.KeyStore; import java.security.KeyStoreSpi; import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.UnrecoverableEntryException; import java.security.UnrecoverableKeyException; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.cert.CertificateException; +import java.security.spec.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; import javax.crypto.Mac; +import javax.security.auth.DestroyFailedException; import javax.security.auth.x500.X500Principal; +import sun.misc.JavaSecurityKeyStoreAccess; +import sun.misc.SharedSecrets; + import sun.security.util.Debug; import sun.security.util.DerInputStream; import sun.security.util.DerOutputStream; @@ -100,11 +108,12 @@ * 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 @@ -124,6 +133,7 @@ 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}; @@ -134,14 +144,25 @@ {1, 2, 840, 113549, 1, 12, 1, 6}; private static final int pbeWithSHAAnd3KeyTripleDESCBC[] = {1, 2, 840, 113549, 1, 12, 1, 3}; + // 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 TrustedKeyUsage_OID; + private static ObjectIdentifier[] AnyUsage; private int counter = 0; private static final int iterationCount = 1024; @@ -152,6 +173,12 @@ // 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; @@ -159,6 +186,7 @@ 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); @@ -166,38 +194,67 @@ new ObjectIdentifier(pbeWithSHAAnd40BitRC2CBC); pbeWithSHAAnd3KeyTripleDESCBC_OID = new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC); + 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 secret key + private static class SecretKeyEntry extends KeyEntry { + byte[] protectedSecretKey; }; - // A certificate with its PKCS #9 attributes - private static class CertEntry { + // 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 = @@ -222,19 +279,27 @@ 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; ObjectIdentifier algOid; + try { // get the encrypted private key EncryptedPrivateKeyInfo encrInfo = @@ -256,14 +321,14 @@ } 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) { @@ -276,21 +341,52 @@ } } - 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(); + + // 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 (" + + key.getClass().getName() + ") at alias '" + alias + + "'"); + } - KeyFactory kfac = KeyFactory.getInstance(algName); - key = kfac.generatePrivate(kspec); + // 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: " + @@ -313,12 +409,19 @@ * 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 { - return entry.chain.clone(); + + if (debug != null) { + debug.println("Retrieved a " + + ((PrivateKeyEntry) entry).chain.length + + "-certificate chain at alias '" + alias + "'"); + } + + return ((PrivateKeyEntry) entry).chain.clone(); } } else { return null; @@ -341,13 +444,39 @@ * 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 { - return entry.chain[0]; + + if (debug != null) { + debug.println("Retrieved a certificate at alias '" + alias + + "'"); + } + + return ((PrivateKeyEntry) entry).chain[0]; } + } else { return null; } @@ -362,7 +491,7 @@ * 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 { @@ -396,40 +525,98 @@ char[] password, Certificate[] chain) throws KeyStoreException { + setKeyEntry(alias, key, password, chain, null); + } + + /* + * Sets a key entry (with attributes, when present) + */ + private void setKeyEntry(String alias, Key key, + char[] password, 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"))) { + + if (debug != null) { + debug.println("Setting a protected private key (" + + key.getClass().getName() + ") at alias '" + alias + + "'"); + } + // Encrypt the private key - entry.protectedPrivKey = + keyEntry.protectedPrivKey = encryptPrivateKey(key.getEncoded(), password); } else { throw new KeyStoreException("Private key is not encoded" + "as PKCS#8"); } + + // 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(), password); + + if (debug != null) { + debug.println("Setting a protected secret key (" + + key.getClass().getName() + ") at alias '" + alias + + "'"); + } + secretKeyCount++; + entry = keyEntry; + } else { - throw new KeyStoreException("Key is not a PrivateKey"); + throw new KeyStoreException("Unsupported Key type"); } - // 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(); + 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); @@ -463,7 +650,7 @@ 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); @@ -472,9 +659,14 @@ + " 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 private key at alias '" + + alias + "'"); + } + try { // set the keyId to current date entry.keyId = ("Time " + (entry.date).getTime()).getBytes("UTF8"); @@ -491,10 +683,17 @@ throw new KeyStoreException("Certificate chain is " + "not valid"); } - entry.chain = chain.clone(); + entry.chain = chain.clone(); + certificateCount += chain.length; + + if (debug != null) { + debug.println("Setting a " + entry.chain.length + + "-certificate chain at alias '" + alias + "'"); + } } // add the entry + privateKeyCount++; entries.put(alias.toLowerCase(Locale.ENGLISH), entry); } @@ -573,6 +772,7 @@ 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); @@ -605,6 +805,11 @@ cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); byte[] encryptedKey = cipher.doFinal(data); + if (debug != null) { + debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + + ")"); + } + // wrap encrypted private key in EncryptedPrivateKeyInfo // as defined in PKCS#8 AlgorithmId algid = @@ -634,17 +839,36 @@ * @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 + + "'"); + } } /** @@ -657,6 +881,22 @@ public synchronized void engineDeleteEntry(String alias) throws KeyStoreException { + if (debug != null) { + 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)); } @@ -666,7 +906,7 @@ * @return enumeration of the alias names */ public Enumeration engineAliases() { - return entries.keys(); + return Collections.enumeration(entries.keySet()); } /** @@ -697,8 +937,8 @@ * 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; @@ -713,8 +953,13 @@ * 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; + } } /** @@ -736,11 +981,18 @@ 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; @@ -786,16 +1038,32 @@ DerOutputStream authSafeContentInfo = new DerOutputStream(); // -- create safeContent Data ContentInfo - byte[] safeContentData = createSafeContent(); - ContentInfo dataContentInfo = new ContentInfo(safeContentData); - dataContentInfo.encode(authSafeContentInfo); + if (privateKeyCount > 0 || secretKeyCount > 0) { + + 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 - byte[] encrData = createEncryptedData(password); - ContentInfo encrContentInfo = + if (certificateCount > 0) { + + 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(); @@ -821,6 +1089,211 @@ } + /** + * 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)); + JavaSecurityKeyStoreAccess jsksa = SharedSecrets.getJavaSecurityKeyStoreAccess(); + 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 jsksa.constructTrustedCertificateEntry( + ((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 jsksa.constructPrivateKeyEntry((PrivateKey)key, chain, + getAttributes(entry)); + + } else if (key instanceof SecretKey) { + + return jsksa.constructSecretKeyEntry((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 + JavaSecurityKeyStoreAccess jsksa = SharedSecrets.getJavaSecurityKeyStoreAccess(); + 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(), + jsksa.getTrustedCertificateEntryAttributes(tce)); + + 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.getPassword(), + pke.getCertificateChain(), + jsksa.getPrivateKeyEntryAttributes(pke)); + + 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.getPassword(), + (Certificate[])null, + jsksa.getSecretKeyEntryAttributes(ske)); + + 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. */ @@ -900,11 +1373,12 @@ /* - * 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 @@ -926,20 +1400,28 @@ * 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; } @@ -970,6 +1452,20 @@ 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); @@ -977,11 +1473,20 @@ if (localKeyID != null) { attrs.write(localKeyID); } + if (trustedKeyUsage != null) { + attrs.write(trustedKeyUsage); + } + + if (attributes != null) { + for (PKCS12Attribute 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. @@ -992,17 +1497,26 @@ 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++) { @@ -1016,7 +1530,7 @@ // 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); @@ -1039,7 +1553,18 @@ 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 @@ -1048,7 +1573,8 @@ // 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); @@ -1078,6 +1604,7 @@ /* * 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) @@ -1086,33 +1613,74 @@ 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); + if (entry == null || (!(entry instanceof KeyEntry))) { + continue; + } + DerOutputStream safeBag = new DerOutputStream(); + KeyEntry keyEntry = (KeyEntry) entry; + + // DER encode the private key + if (keyEntry instanceof PrivateKeyEntry) { + // Create SafeBag of type pkcs8ShroudedKeyBag + safeBag.putOID(PKCS8ShroudedKeyBag_OID); - // Create SafeBag of type pkcs8ShroudedKeyBag - DerOutputStream safeBag = new DerOutputStream(); - 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); - // 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()); + // 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 } - // 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); - // write SafeBag Attributes - byte[] bagAttrs = getBagAttributes(alias, entry.keyId); + byte[] bagAttrs = + getBagAttributes(alias, entry.keyId, entry.attributes); safeBag.write(bagAttrs); // wrap as Sequence @@ -1156,6 +1724,11 @@ cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); encryptedData = cipher.doFinal(data); + if (debug != null) { + debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + + ")"); + } + } catch (Exception e) { throw new IOException("Failed to encrypt" + " safe contents entry: " + e, e); @@ -1227,6 +1800,11 @@ ObjectIdentifier contentType = authSafe.getContentType(); if (contentType.equals((Object)ContentInfo.DATA_OID)) { + + if (debug != null) { + debug.println("Loading PKCS#7 data content-type"); + } + authSafeData = authSafe.getData(); } else /* signed data */ { throw new IOException("public key protected PKCS12 not supported"); @@ -1236,8 +1814,10 @@ 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. @@ -1294,7 +1874,7 @@ continue; } throw new IOException( - "failed to decrypt safe contents entry: " + e, e); + "failed to decrypt safe contents entry: " + e, e); } } } else { @@ -1325,6 +1905,11 @@ m.update(authSafeData); byte[] macResult = m.doFinal(); + if (debug != null) { + debug.println("Checking keystore integrity " + + "(MAC algorithm: " + m.getAlgorithm() + ")"); + } + if (!MessageDigest.isEqual(macData.getDigest(), macResult)) { throw new SecurityException("Failed PKCS12" + " integrity checking"); @@ -1337,9 +1922,10 @@ /* * 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(); @@ -1374,6 +1960,22 @@ 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(); @@ -1384,7 +1986,7 @@ * @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) { @@ -1428,7 +2030,7 @@ } 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++; @@ -1446,13 +2048,30 @@ 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 { - // log error message for "unsupported PKCS12 bag type" + + if (debug != null) { + debug.println("Unsupported PKCS12 bag type: " + bagId); + } } 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 @@ -1462,11 +2081,13 @@ 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 = @@ -1482,8 +2103,15 @@ alias = valSet[0].getBMPString(); } else if (attrId.equals((Object)PKCS9LocalKeyId_OID)) { keyId = valSet[0].getOctetString(); + } 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 { - // log error message for "unknown attr" + + attributes.add(new PKCS12Attribute(encoded)); } } } @@ -1499,16 +2127,19 @@ */ 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 @@ -1526,11 +2157,16 @@ 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 @@ -1543,7 +2179,18 @@ 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 -r 827b23f9c231 -r 0134b20e49ba src/share/classes/sun/security/x509/AlgorithmId.java --- a/src/share/classes/sun/security/x509/AlgorithmId.java Wed Nov 15 02:54:40 2017 +0000 +++ b/src/share/classes/sun/security/x509/AlgorithmId.java Thu Nov 16 18:28:17 2017 +0000 @@ -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 @@ 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")) { @@ -661,6 +666,12 @@ 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 */ private static final int md2WithRSAEncryption_data[] = @@ -893,6 +904,8 @@ 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 -r 827b23f9c231 -r 0134b20e49ba test/sun/security/pkcs12/StorePasswordTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/sun/security/pkcs12/StorePasswordTest.java Thu Nov 16 18:28:17 2017 +0000 @@ -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 -r 827b23f9c231 -r 0134b20e49ba test/sun/security/pkcs12/StoreSecretKeyTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/sun/security/pkcs12/StoreSecretKeyTest.java Thu Nov 16 18:28:17 2017 +0000 @@ -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 -r 827b23f9c231 -r 0134b20e49ba test/sun/security/pkcs12/StoreTrustedCertTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/sun/security/pkcs12/StoreTrustedCertTest.java Thu Nov 16 18:28:17 2017 +0000 @@ -0,0 +1,125 @@ +/* + * 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 + * @compile -XDignore.symbol.file StoreTrustedCertTest.java + */ + +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.*; + +import sun.misc.JavaSecurityKeyStoreAccess; +import sun.misc.SharedSecrets; +import sun.security.pkcs12.PKCS12Attribute; + +// 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 + JavaSecurityKeyStoreAccess jsksa = + SharedSecrets.getJavaSecurityKeyStoreAccess(); + keystore.setEntry(ALIAS2, + jsksa.constructTrustedCertificateEntry(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 = + jsksa.getTrustedCertificateEntryAttributes(trustedEntry); + + 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 -r 827b23f9c231 -r 0134b20e49ba test/sun/security/pkcs12/trusted.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/sun/security/pkcs12/trusted.pem Thu Nov 16 18:28:17 2017 +0000 @@ -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-----