changeset 8781:0134b20e49ba

8005408: KeyStore API enhancements Reviewed-by: mullan
author vinnie
date Thu, 16 Nov 2017 18:28:17 +0000
parents 827b23f9c231
children f919f1243d0b
files src/share/classes/java/security/KeyStore.java src/share/classes/sun/misc/JavaSecurityKeyStoreAccess.java src/share/classes/sun/misc/SharedSecrets.java src/share/classes/sun/security/pkcs12/PKCS12Attribute.java src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java src/share/classes/sun/security/x509/AlgorithmId.java test/sun/security/pkcs12/StorePasswordTest.java test/sun/security/pkcs12/StoreSecretKeyTest.java test/sun/security/pkcs12/StoreTrustedCertTest.java test/sun/security/pkcs12/trusted.pem
diffstat 10 files changed, 1730 insertions(+), 136 deletions(-) [+]
line wrap: on
line diff
--- 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<PKCS12Attribute> attributes) {
+                return new PrivateKeyEntry(privateKey, chain, attributes);
+            }
+
+            @Override
+            public Set<PKCS12Attribute> getPrivateKeyEntryAttributes(PrivateKeyEntry entry) {
+                return entry.getAttributes();
+            }
+
+            @Override
+            public SecretKeyEntry constructSecretKeyEntry(SecretKey secretKey,
+                                                          Set<PKCS12Attribute> attributes) {
+                return new SecretKeyEntry(secretKey, attributes);
+            }
+
+            @Override
+            public Set<PKCS12Attribute> getSecretKeyEntryAttributes(SecretKeyEntry entry) {
+                return entry.getAttributes();
+            }
+
+            public TrustedCertificateEntry constructTrustedCertificateEntry(Certificate trustedCert,
+                                                                            Set<PKCS12Attribute> attributes) {
+                return new TrustedCertificateEntry(trustedCert, attributes);
+            }
+
+            @Override
+            public Set<PKCS12Attribute> 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<PKCS12Attribute> attributes;
 
         /**
          * Constructs a <code>PrivateKeyEntry</code> with a
@@ -381,7 +424,39 @@
          *      in the end entity <code>Certificate</code> (at index 0)
          */
         public PrivateKeyEntry(PrivateKey privateKey, Certificate[] chain) {
-            if (privateKey == null || chain == null) {
+            this(privateKey, chain, Collections.<PKCS12Attribute>emptySet());
+        }
+
+        /**
+         * Constructs a {@code PrivateKeyEntry} with a {@code PrivateKey} and
+         * corresponding certificate chain and associated entry attributes.
+         *
+         * <p> 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<PKCS12Attribute> 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.
+         * <p>
+         *
+         * @return an unmodifiable {@code Set} of attributes, possibly empty
+         *
+         * @since 1.8
+         */
+        private Set<PKCS12Attribute> 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<PKCS12Attribute> attributes;
 
         /**
          * Constructs a <code>SecretKeyEntry</code> with a
@@ -496,6 +587,32 @@
                 throw new NullPointerException("invalid null input");
             }
             this.sKey = secretKey;
+            this.attributes = Collections.<PKCS12Attribute>emptySet();
+        }
+
+        /**
+         * Constructs a {@code SecretKeyEntry} with a {@code SecretKey} and
+         * associated entry attributes.
+         *
+         * <p> 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<PKCS12Attribute> 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.
+         * <p>
+         *
+         * @return an unmodifiable {@code Set} of attributes, possibly empty
+         *
+         * @since 1.8
+         */
+        private Set<PKCS12Attribute> 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<PKCS12Attribute> attributes;
 
         /**
          * Constructs a <code>TrustedCertificateEntry</code> with a
@@ -540,6 +670,32 @@
                 throw new NullPointerException("invalid null input");
             }
             this.cert = trustedCert;
+            this.attributes = Collections.<PKCS12Attribute>emptySet();
+        }
+
+        /**
+         * Constructs a {@code TrustedCertificateEntry} with a
+         * trusted {@code Certificate} and associated entry attributes.
+         *
+         * <p> 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<PKCS12Attribute> 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.
+         * <p>
+         *
+         * @return an unmodifiable {@code Set} of attributes, possibly empty
+         *
+         * @since 1.8
+         */
+        private Set<PKCS12Attribute> getAttributes() {
+            return attributes;
+        }
+
+        /**
          * Returns a string representation of this TrustedCertificateEntry.
          * @return a string representation of this TrustedCertificateEntry.
          */
--- /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.
+     *
+     * <p> 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<PKCS12Attribute> attributes);
+
+    /**
+     * Retrieves the attributes associated with a {@code PrivateKeyEntry}.
+     * <p>
+     *
+     * @return an unmodifiable {@code Set} of attributes, possibly empty
+     *
+     * @since 1.8
+     */
+    Set<PKCS12Attribute> getPrivateKeyEntryAttributes(PrivateKeyEntry entry);
+
+    /**
+     * Constructs a {@code SecretKeyEntry} with a {@code SecretKey} and
+     * associated entry attributes.
+     *
+     * <p> 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<PKCS12Attribute> attributes);
+
+    /**
+     * Retrieves the attributes associated with a {@code SecretKeyEntry}.
+     * <p>
+     *
+     * @return an unmodifiable {@code Set} of attributes, possibly empty
+     *
+     * @since 1.8
+     */
+    Set<PKCS12Attribute> getSecretKeyEntryAttributes(SecretKeyEntry entry);
+
+    /**
+     * Constructs a {@code TrustedCertificateEntry} with a
+     * trusted {@code Certificate} and associated entry attributes.
+     *
+     * <p> 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<PKCS12Attribute> attributes);
+
+    /**
+     * Retrieves the attributes associated with a {@code TrustedCertificateEntry}.
+     * <p>
+     *
+     * @return an unmodifiable {@code Set} of attributes, possibly empty
+     *
+     * @since 1.8
+     */
+    Set<PKCS12Attribute> getTrustedCertificateEntryAttributes(TrustedCertificateEntry entry);
+}
--- 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;
+    }
 }
--- /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}.
+     * <p>
+     * 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:
+     * <pre>
+     *
+     * Attribute ::= SEQUENCE {
+     *     type   AttributeType,
+     *     values SET OF AttributeValue
+     * }
+     * AttributeType ::= OBJECT IDENTIFIER
+     * AttributeValue ::= ANY defined by type
+     *
+     * </pre>
+     *
+     * @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:
+     * <ul>
+     * <li> 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.
+     * <li> 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.
+     * </ul>
+     * 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);
+    }
+}
--- 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<PKCS12Attribute> 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<? extends PKCS12Attribute> 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<String, KeyEntry> entries =
-                                new Hashtable<String, KeyEntry>();
+    private Map<String, Entry> entries =
+        Collections.synchronizedMap(new LinkedHashMap<String, Entry>());
 
     private ArrayList<KeyEntry> keyList = new ArrayList<KeyEntry>();
     private LinkedHashMap<X500Principal, X509Certificate> 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 @@
      * <i>key entry</i> 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<PKCS12Attribute> 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 <i>key entry</i>, or on an attempt to create a
-     * <i>trusted cert entry</i> which is currently not supported.
+     * not identify a <i>trusted certificate entry</i>, 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<PKCS12Attribute> 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<String> engineAliases() {
-        return entries.keys();
+        return Collections.enumeration(entries.keySet());
     }
 
     /**
@@ -697,8 +937,8 @@
      * <i>key entry</i>, 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 @@
      * <i>trusted certificate entry</i>, 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<String> e = entries.keys(); e.hasMoreElements(); ) {
+        for (Enumeration<String> 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 <code>KeyStore.Entry</code> for the specified alias
+     * with the specified protection parameter.
+     *
+     * @param alias get the <code>KeyStore.Entry</code> for this alias
+     * @param protParam the <code>ProtectionParameter</code>
+     *          used to protect the <code>Entry</code>,
+     *          which may be <code>null</code>
+     *
+     * @return the <code>KeyStore.Entry</code> for the specified alias,
+     *          or <code>null</code> 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
+     *          <code>protParam</code> were insufficient or invalid
+     * @exception UnrecoverableKeyException if the entry is a
+     *          <code>PrivateKeyEntry</code> or <code>SecretKeyEntry</code>
+     *          and the specified <code>protParam</code> 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 <code>KeyStore.Entry</code> under the specified alias.
+     * The specified protection parameter is used to protect the
+     * <code>Entry</code>.
+     *
+     * <p> If an entry already exists for the specified alias,
+     * it is overridden.
+     *
+     * @param alias save the <code>KeyStore.Entry</code> under this alias
+     * @param entry the <code>Entry</code> to save
+     * @param protParam the <code>ProtectionParameter</code>
+     *          used to protect the <code>Entry</code>,
+     *          which may be <code>null</code>
+     *
+     * @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<PKCS12Attribute> 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<PKCS12Attribute> attributes) throws IOException {
+        return getBagAttributes(alias, keyId, null, attributes);
+    }
+
+    private byte[] getBagAttributes(String alias, byte[] keyId,
+        ObjectIdentifier[] trustedUsage,
+        Set<PKCS12Attribute> 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<String> e = entries.keys(); e.hasMoreElements(); ) {
+        for (Enumeration<String> 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<String> e = entries.keys(); e.hasMoreElements(); ) {
+        for (Enumeration<String> 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<X509Certificate> chain =
                                 new ArrayList<X509Certificate>();
@@ -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<PKCS12Attribute> 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)) {
--- 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");
--- /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()));
+    }
+}
--- /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();
+    }
+}
--- /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<PKCS12Attribute> 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<PKCS12Attribute> 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);
+        }
+    }
+}
--- /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-----