提交 a77b812a 编写于 作者: V vinnie

8005408: KeyStore API enhancements

Reviewed-by: mullan
上级 b691337a
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
package java.security; package java.security;
import java.io.*; import java.io.*;
import java.net.URI;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
...@@ -405,7 +406,44 @@ public class KeyStore { ...@@ -405,7 +406,44 @@ public class KeyStore {
* *
* @since 1.5 * @since 1.5
*/ */
public static interface Entry { } public static interface Entry {
/**
* Retrieves the attributes associated with an entry.
* <p>
* The default implementation returns an empty {@code Set}.
*
* @return an unmodifiable {@code Set} of attributes, possibly empty
*
* @since 1.8
*/
public default Set<Attribute> getAttributes() {
return Collections.<Attribute>emptySet();
}
/**
* An attribute associated with a keystore entry.
* It comprises a name and one or more values.
*
* @since 1.8
*/
public interface Attribute {
/**
* Returns the attribute's name.
*
* @return the attribute name
*/
public String getName();
/**
* Returns the attribute's value.
* Multi-valued attributes encode their values as a single string.
*
* @return the attribute value
*/
public String getValue();
}
}
/** /**
* A <code>KeyStore</code> entry that holds a <code>PrivateKey</code> * A <code>KeyStore</code> entry that holds a <code>PrivateKey</code>
...@@ -417,6 +455,7 @@ public class KeyStore { ...@@ -417,6 +455,7 @@ public class KeyStore {
private final PrivateKey privKey; private final PrivateKey privKey;
private final Certificate[] chain; private final Certificate[] chain;
private final Set<Attribute> attributes;
/** /**
* Constructs a <code>PrivateKeyEntry</code> with a * Constructs a <code>PrivateKeyEntry</code> with a
...@@ -443,7 +482,39 @@ public class KeyStore { ...@@ -443,7 +482,39 @@ public class KeyStore {
* in the end entity <code>Certificate</code> (at index 0) * in the end entity <code>Certificate</code> (at index 0)
*/ */
public PrivateKeyEntry(PrivateKey privateKey, Certificate[] chain) { public PrivateKeyEntry(PrivateKey privateKey, Certificate[] chain) {
if (privateKey == null || chain == null) { this(privateKey, chain, Collections.<Attribute>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
*/
public PrivateKeyEntry(PrivateKey privateKey, Certificate[] chain,
Set<Attribute> attributes) {
if (privateKey == null || chain == null || attributes == null) {
throw new NullPointerException("invalid null input"); throw new NullPointerException("invalid null input");
} }
if (chain.length == 0) { if (chain.length == 0) {
...@@ -478,6 +549,9 @@ public class KeyStore { ...@@ -478,6 +549,9 @@ public class KeyStore {
} else { } else {
this.chain = clonedChain; this.chain = clonedChain;
} }
this.attributes =
Collections.unmodifiableSet(new HashSet<>(attributes));
} }
/** /**
...@@ -518,6 +592,19 @@ public class KeyStore { ...@@ -518,6 +592,19 @@ public class KeyStore {
return chain[0]; return chain[0];
} }
/**
* Retrieves the attributes associated with an entry.
* <p>
*
* @return an unmodifiable {@code Set} of attributes, possibly empty
*
* @since 1.8
*/
@Override
public Set<Attribute> getAttributes() {
return attributes;
}
/** /**
* Returns a string representation of this PrivateKeyEntry. * Returns a string representation of this PrivateKeyEntry.
* @return a string representation of this PrivateKeyEntry. * @return a string representation of this PrivateKeyEntry.
...@@ -543,6 +630,7 @@ public class KeyStore { ...@@ -543,6 +630,7 @@ public class KeyStore {
public static final class SecretKeyEntry implements Entry { public static final class SecretKeyEntry implements Entry {
private final SecretKey sKey; private final SecretKey sKey;
private final Set<Attribute> attributes;
/** /**
* Constructs a <code>SecretKeyEntry</code> with a * Constructs a <code>SecretKeyEntry</code> with a
...@@ -558,6 +646,32 @@ public class KeyStore { ...@@ -558,6 +646,32 @@ public class KeyStore {
throw new NullPointerException("invalid null input"); throw new NullPointerException("invalid null input");
} }
this.sKey = secretKey; this.sKey = secretKey;
this.attributes = Collections.<Attribute>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
*/
public SecretKeyEntry(SecretKey secretKey, Set<Attribute> attributes) {
if (secretKey == null || attributes == null) {
throw new NullPointerException("invalid null input");
}
this.sKey = secretKey;
this.attributes =
Collections.unmodifiableSet(new HashSet<>(attributes));
} }
/** /**
...@@ -569,6 +683,19 @@ public class KeyStore { ...@@ -569,6 +683,19 @@ public class KeyStore {
return sKey; return sKey;
} }
/**
* Retrieves the attributes associated with an entry.
* <p>
*
* @return an unmodifiable {@code Set} of attributes, possibly empty
*
* @since 1.8
*/
@Override
public Set<Attribute> getAttributes() {
return attributes;
}
/** /**
* Returns a string representation of this SecretKeyEntry. * Returns a string representation of this SecretKeyEntry.
* @return a string representation of this SecretKeyEntry. * @return a string representation of this SecretKeyEntry.
...@@ -587,6 +714,7 @@ public class KeyStore { ...@@ -587,6 +714,7 @@ public class KeyStore {
public static final class TrustedCertificateEntry implements Entry { public static final class TrustedCertificateEntry implements Entry {
private final Certificate cert; private final Certificate cert;
private final Set<Attribute> attributes;
/** /**
* Constructs a <code>TrustedCertificateEntry</code> with a * Constructs a <code>TrustedCertificateEntry</code> with a
...@@ -602,6 +730,32 @@ public class KeyStore { ...@@ -602,6 +730,32 @@ public class KeyStore {
throw new NullPointerException("invalid null input"); throw new NullPointerException("invalid null input");
} }
this.cert = trustedCert; this.cert = trustedCert;
this.attributes = Collections.<Attribute>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
*/
public TrustedCertificateEntry(Certificate trustedCert,
Set<Attribute> attributes) {
if (trustedCert == null || attributes == null) {
throw new NullPointerException("invalid null input");
}
this.cert = trustedCert;
this.attributes =
Collections.unmodifiableSet(new HashSet<>(attributes));
} }
/** /**
...@@ -613,6 +767,19 @@ public class KeyStore { ...@@ -613,6 +767,19 @@ public class KeyStore {
return cert; return cert;
} }
/**
* Retrieves the attributes associated with an entry.
* <p>
*
* @return an unmodifiable {@code Set} of attributes, possibly empty
*
* @since 1.8
*/
@Override
public Set<Attribute> getAttributes() {
return attributes;
}
/** /**
* Returns a string representation of this TrustedCertificateEntry. * Returns a string representation of this TrustedCertificateEntry.
* @return a string representation of this TrustedCertificateEntry. * @return a string representation of this TrustedCertificateEntry.
......
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.security;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.regex.Pattern;
import sun.security.util.*;
/**
* An attribute associated with a PKCS12 keystore entry.
* The attribute name is an ASN.1 Object Identifier and the attribute
* value is a set of ASN.1 types.
*
* @since 1.8
*/
public final class PKCS12Attribute implements KeyStore.Entry.Attribute {
private static final Pattern COLON_SEPARATED_HEX_PAIRS =
Pattern.compile("^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2})+$");
private String name;
private String value;
private byte[] encoded;
private int hashValue = -1;
/**
* Constructs a PKCS12 attribute from its name and value.
* The name is an ASN.1 Object Identifier represented as a list of
* dot-separated integers.
* A string value is represented as the string itself.
* A binary value is represented as a string of colon-separated
* pairs of hexadecimal digits.
* Multi-valued attributes are represented as a comma-separated
* list of values, enclosed in square brackets. See
* {@link Arrays.toString}.
* <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
*/
@Override
public String getName() {
return name;
}
/**
* Returns the attribute's ASN.1 DER-encoded value as a string.
* An ASN.1 DER-encoded value is returned in one of the following
* {@code String} formats:
* <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
*/
@Override
public String getValue() {
return value;
}
/**
* Returns the attribute's ASN.1 DER encoding.
*
* @return a clone of the attribute's DER encoding
*/
public byte[] getEncoded() {
return encoded.clone();
}
/**
* Compares this {@code PKCS12Attribute} and a specified object for
* equality.
*
* @param obj the comparison object
*
* @return true if {@code obj} is a {@code PKCS12Attribute} and
* their DER encodings are equal.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PKCS12Attribute)) {
return false;
}
return Arrays.equals(encoded, ((PKCS12Attribute) obj).getEncoded());
}
/**
* Returns the hashcode for this {@code PKCS12Attribute}.
* The hash code is computed from its DER encoding.
*
* @return the hash code
*/
@Override
public int hashCode() {
if (hashValue == -1) {
Arrays.hashCode(encoded);
}
return hashValue;
}
/**
* Returns a string representation of this {@code PKCS12Attribute}.
*
* @return a name/value pair separated by an 'equals' symbol
*/
@Override
public String toString() {
return (name + "=" + value);
}
private byte[] encode(ObjectIdentifier type, String[] values)
throws IOException {
DerOutputStream attribute = new DerOutputStream();
attribute.putOID(type);
DerOutputStream attrContent = new DerOutputStream();
for (String value : values) {
if (COLON_SEPARATED_HEX_PAIRS.matcher(value).matches()) {
byte[] bytes =
new BigInteger(value.replace(":", ""), 16).toByteArray();
if (bytes[0] == 0) {
bytes = Arrays.copyOfRange(bytes, 1, bytes.length);
}
attrContent.putOctetString(bytes);
} else {
attrContent.putUTF8String(value);
}
}
attribute.write(DerValue.tag_Set, attrContent);
DerOutputStream attributeValue = new DerOutputStream();
attributeValue.write(DerValue.tag_Sequence, attribute);
return attributeValue.toByteArray();
}
private void parse(byte[] encoded) throws IOException {
DerInputStream attributeValue = new DerInputStream(encoded);
DerValue[] attrSeq = attributeValue.getSequence(2);
ObjectIdentifier type = attrSeq[0].getOID();
DerInputStream attrContent =
new DerInputStream(attrSeq[1].toByteArray());
DerValue[] attrValueSet = attrContent.getSet(1);
String[] values = new String[attrValueSet.length];
String printableString;
for (int i = 0; i < attrValueSet.length; i++) {
if (attrValueSet[i].tag == DerValue.tag_OctetString) {
values[i] = Debug.toString(attrValueSet[i].getOctetString());
} else if ((printableString = attrValueSet[i].getAsString())
!= null) {
values[i] = printableString;
} else if (attrValueSet[i].tag == DerValue.tag_ObjectId) {
values[i] = attrValueSet[i].getOID().toString();
} else if (attrValueSet[i].tag == DerValue.tag_GeneralizedTime) {
values[i] = attrValueSet[i].getGeneralizedTime().toString();
} else if (attrValueSet[i].tag == DerValue.tag_UtcTime) {
values[i] = attrValueSet[i].getUTCTime().toString();
} else if (attrValueSet[i].tag == DerValue.tag_Integer) {
values[i] = attrValueSet[i].getBigInteger().toString();
} else if (attrValueSet[i].tag == DerValue.tag_Boolean) {
values[i] = String.valueOf(attrValueSet[i].getBoolean());
} else {
values[i] = Debug.toString(attrValueSet[i].getDataBytes());
}
}
this.name = type.toString();
this.value = values.length == 1 ? values[0] : Arrays.toString(values);
}
}
...@@ -30,27 +30,30 @@ import java.security.AccessController; ...@@ -30,27 +30,30 @@ import java.security.AccessController;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Key; import java.security.Key;
import java.security.KeyStore;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.PrivateKey; import java.security.KeyStore;
import java.security.PrivilegedAction;
import java.security.KeyStoreSpi; import java.security.KeyStoreSpi;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.PKCS12Attribute;
import java.security.PrivateKey;
import java.security.PrivilegedAction;
import java.security.UnrecoverableEntryException; import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.security.Security;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*; import java.util.*;
import java.security.AlgorithmParameters; import java.security.AlgorithmParameters;
import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.SecretKeyFactory; import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.Cipher; import javax.crypto.Cipher;
...@@ -107,11 +110,12 @@ import sun.security.pkcs.EncryptedPrivateKeyInfo; ...@@ -107,11 +110,12 @@ import sun.security.pkcs.EncryptedPrivateKeyInfo;
* OpenSSL PKCS#12 code. All. All. * 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 * PKCS#12 is mainly used to deliver private keys with their associated
* certificate chain and aliases. In a PKCS12 keystore, entries are * certificate chain and aliases. In a PKCS12 keystore, entries are
* identified by the alias, and a localKeyId is required to match the * 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 Seema Malkani
* @author Jeff Nisewanger * @author Jeff Nisewanger
...@@ -136,6 +140,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -136,6 +140,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
private static final int keyBag[] = {1, 2, 840, 113549, 1, 12, 10, 1, 2}; private static final int 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 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 pkcs9Name[] = {1, 2, 840, 113549, 1, 9, 20};
private static final int pkcs9KeyId[] = {1, 2, 840, 113549, 1, 9, 21}; private static final int pkcs9KeyId[] = {1, 2, 840, 113549, 1, 9, 21};
...@@ -147,15 +152,26 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -147,15 +152,26 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
private static final int pbeWithSHAAnd3KeyTripleDESCBC[] = private static final int pbeWithSHAAnd3KeyTripleDESCBC[] =
{1, 2, 840, 113549, 1, 12, 1, 3}; {1, 2, 840, 113549, 1, 12, 1, 3};
private static final int pbes2[] = {1, 2, 840, 113549, 1, 5, 13}; private static final int pbes2[] = {1, 2, 840, 113549, 1, 5, 13};
// TODO: temporary Oracle OID
/*
* { joint-iso-itu-t(2) country(16) us(840) organization(1) oracle(113894)
* jdk(746875) crypto(1) id-at-trustedKeyUsage(1) }
*/
private static final int TrustedKeyUsage[] =
{2, 16, 840, 1, 113894, 746875, 1, 1};
private static final int AnyExtendedKeyUsage[] = {2, 5, 29, 37, 0};
private static ObjectIdentifier PKCS8ShroudedKeyBag_OID; private static ObjectIdentifier PKCS8ShroudedKeyBag_OID;
private static ObjectIdentifier CertBag_OID; private static ObjectIdentifier CertBag_OID;
private static ObjectIdentifier SecretBag_OID;
private static ObjectIdentifier PKCS9FriendlyName_OID; private static ObjectIdentifier PKCS9FriendlyName_OID;
private static ObjectIdentifier PKCS9LocalKeyId_OID; private static ObjectIdentifier PKCS9LocalKeyId_OID;
private static ObjectIdentifier PKCS9CertType_OID; private static ObjectIdentifier PKCS9CertType_OID;
private static ObjectIdentifier pbeWithSHAAnd40BitRC2CBC_OID; private static ObjectIdentifier pbeWithSHAAnd40BitRC2CBC_OID;
private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID; private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID;
private static ObjectIdentifier pbes2_OID; private static ObjectIdentifier pbes2_OID;
private static ObjectIdentifier TrustedKeyUsage_OID;
private static ObjectIdentifier[] AnyUsage;
private int counter = 0; private int counter = 0;
private static final int iterationCount = 1024; private static final int iterationCount = 1024;
...@@ -166,6 +182,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -166,6 +182,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
// in pkcs12 with one private key entry and associated cert-chain // in pkcs12 with one private key entry and associated cert-chain
private int privateKeyCount = 0; private int privateKeyCount = 0;
// secret key count
private int secretKeyCount = 0;
// certificate count
private int certificateCount = 0;
// the source of randomness // the source of randomness
private SecureRandom random; private SecureRandom random;
...@@ -173,6 +195,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -173,6 +195,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
try { try {
PKCS8ShroudedKeyBag_OID = new ObjectIdentifier(keyBag); PKCS8ShroudedKeyBag_OID = new ObjectIdentifier(keyBag);
CertBag_OID = new ObjectIdentifier(certBag); CertBag_OID = new ObjectIdentifier(certBag);
SecretBag_OID = new ObjectIdentifier(secretBag);
PKCS9FriendlyName_OID = new ObjectIdentifier(pkcs9Name); PKCS9FriendlyName_OID = new ObjectIdentifier(pkcs9Name);
PKCS9LocalKeyId_OID = new ObjectIdentifier(pkcs9KeyId); PKCS9LocalKeyId_OID = new ObjectIdentifier(pkcs9KeyId);
PKCS9CertType_OID = new ObjectIdentifier(pkcs9certType); PKCS9CertType_OID = new ObjectIdentifier(pkcs9certType);
...@@ -181,38 +204,67 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -181,38 +204,67 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
pbeWithSHAAnd3KeyTripleDESCBC_OID = pbeWithSHAAnd3KeyTripleDESCBC_OID =
new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC); new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC);
pbes2_OID = new ObjectIdentifier(pbes2); pbes2_OID = new ObjectIdentifier(pbes2);
TrustedKeyUsage_OID = new ObjectIdentifier(TrustedKeyUsage);
AnyUsage = new ObjectIdentifier[]{
new ObjectIdentifier(AnyExtendedKeyUsage)};
} catch (IOException ioe) { } catch (IOException ioe) {
// should not happen // should not happen
} }
} }
// Private keys and their supporting certificate chains // A keystore entry and associated attributes
private static class KeyEntry { private static class Entry {
Date date; // the creation date of this entry Date date; // the creation date of this entry
String alias;
byte[] keyId;
Set<KeyStore.Entry.Attribute> 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; byte[] protectedPrivKey;
Certificate chain[]; Certificate chain[];
byte[] keyId;
String alias;
}; };
// A certificate with its PKCS #9 attributes // A secret key
private static class CertEntry { private static class SecretKeyEntry extends KeyEntry {
byte[] protectedSecretKey;
};
// A certificate entry
private static class CertEntry extends Entry {
final X509Certificate cert; final X509Certificate cert;
final byte[] keyId; ObjectIdentifier[] trustedKeyUsage;
final String alias;
CertEntry(X509Certificate cert, byte[] keyId, String alias) { CertEntry(X509Certificate cert, byte[] keyId, String alias) {
this(cert, keyId, alias, null, null);
}
CertEntry(X509Certificate cert, byte[] keyId, String alias,
ObjectIdentifier[] trustedKeyUsage,
Set<? extends KeyStore.Entry.Attribute> attributes) {
this.date = new Date();
this.cert = cert; this.cert = cert;
this.keyId = keyId; this.keyId = keyId;
this.alias = alias; 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. * Private keys and certificates are stored in a map.
* Hash entries are keyed by alias names. * Map entries are keyed by alias names.
*/ */
private Hashtable<String, KeyEntry> entries = private Map<String, Entry> entries =
new Hashtable<String, KeyEntry>(); Collections.synchronizedMap(new LinkedHashMap<String, Entry>());
private ArrayList<KeyEntry> keyList = new ArrayList<KeyEntry>(); private ArrayList<KeyEntry> keyList = new ArrayList<KeyEntry>();
private LinkedHashMap<X500Principal, X509Certificate> certsMap = private LinkedHashMap<X500Principal, X509Certificate> certsMap =
...@@ -237,15 +289,22 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -237,15 +289,22 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
public Key engineGetKey(String alias, char[] password) public Key engineGetKey(String alias, char[] password)
throws NoSuchAlgorithmException, UnrecoverableKeyException throws NoSuchAlgorithmException, UnrecoverableKeyException
{ {
KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
Key key = null; Key key = null;
if (entry == null) { if (entry == null || (!(entry instanceof KeyEntry))) {
return null; return null;
} }
// get the encoded private key // get the encoded private key or secret key
byte[] encrBytes = entry.protectedPrivKey; 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; byte[] encryptedKey;
AlgorithmParameters algParams; AlgorithmParameters algParams;
...@@ -271,14 +330,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -271,14 +330,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
} }
try { try {
byte[] privateKeyInfo; byte[] keyInfo;
while (true) { while (true) {
try { try {
// Use JCE // Use JCE
SecretKey skey = getPBEKey(password); SecretKey skey = getPBEKey(password);
Cipher cipher = Cipher.getInstance(algOid.toString()); Cipher cipher = Cipher.getInstance(algOid.toString());
cipher.init(Cipher.DECRYPT_MODE, skey, algParams); cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
privateKeyInfo = cipher.doFinal(encryptedKey); keyInfo = cipher.doFinal(encryptedKey);
break; break;
} catch (Exception e) { } catch (Exception e) {
if (password.length == 0) { if (password.length == 0) {
...@@ -291,27 +350,52 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -291,27 +350,52 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
} }
} }
PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(privateKeyInfo);
/* /*
* Parse the key algorithm and then use a JCA key factory * 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(); DerInputStream in = val.toDerInputStream();
int i = in.getInteger(); int i = in.getInteger();
DerValue[] value = in.getSequence(2); DerValue[] value = in.getSequence(2);
AlgorithmId algId = new AlgorithmId(value[0].getOID()); AlgorithmId algId = new AlgorithmId(value[0].getOID());
String algName = algId.getName(); String keyAlgo = algId.getName();
KeyFactory kfac = KeyFactory.getInstance(algName); // decode private key
if (entry instanceof PrivateKeyEntry) {
KeyFactory kfac = KeyFactory.getInstance(keyAlgo);
PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(keyInfo);
key = kfac.generatePrivate(kspec); key = kfac.generatePrivate(kspec);
if (debug != null) { if (debug != null) {
debug.println("Retrieved a protected private key at alias '" + debug.println("Retrieved a protected private key (" +
alias + "'"); key.getClass().getName() + ") at alias '" + alias +
"'");
}
// decode secret key
} else {
SecretKeyFactory sKeyFactory =
SecretKeyFactory.getInstance(keyAlgo);
byte[] keyBytes = in.getOctetString();
SecretKeySpec secretKeySpec =
new SecretKeySpec(keyBytes, keyAlgo);
// Special handling required for PBE: needs a PBEKeySpec
if (keyAlgo.startsWith("PBE")) {
KeySpec pbeKeySpec =
sKeyFactory.getKeySpec(secretKeySpec, PBEKeySpec.class);
key = sKeyFactory.generateSecret(pbeKeySpec);
} else {
key = sKeyFactory.generateSecret(secretKeySpec);
} }
if (debug != null) {
debug.println("Retrieved a protected secret key (" +
key.getClass().getName() + ") at alias '" + alias +
"'");
}
}
} catch (Exception e) { } catch (Exception e) {
UnrecoverableKeyException uke = UnrecoverableKeyException uke =
new UnrecoverableKeyException("Get Key failed: " + new UnrecoverableKeyException("Get Key failed: " +
...@@ -334,19 +418,19 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -334,19 +418,19 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* <i>key entry</i> without a certificate chain). * <i>key entry</i> without a certificate chain).
*/ */
public Certificate[] engineGetCertificateChain(String alias) { public Certificate[] engineGetCertificateChain(String alias) {
KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
if (entry != null) { if (entry != null && entry instanceof PrivateKeyEntry) {
if (entry.chain == null) { if (((PrivateKeyEntry) entry).chain == null) {
return null; return null;
} else { } else {
if (debug != null) { if (debug != null) {
debug.println("Retrieved a " + debug.println("Retrieved a " +
entry.chain.length + ((PrivateKeyEntry) entry).chain.length +
"-certificate chain at alias '" + alias + "'"); "-certificate chain at alias '" + alias + "'");
} }
return entry.chain.clone(); return ((PrivateKeyEntry) entry).chain.clone();
} }
} else { } else {
return null; return null;
...@@ -369,9 +453,28 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -369,9 +453,28 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* does not contain a certificate. * does not contain a certificate.
*/ */
public Certificate engineGetCertificate(String alias) { public Certificate engineGetCertificate(String alias) {
KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
if (entry != null) { if (entry == null) {
if (entry.chain == 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; return null;
} else { } else {
...@@ -380,8 +483,9 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -380,8 +483,9 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
"'"); "'");
} }
return entry.chain[0]; return ((PrivateKeyEntry) entry).chain[0];
} }
} else { } else {
return null; return null;
} }
...@@ -396,7 +500,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -396,7 +500,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* not exist * not exist
*/ */
public Date engineGetCreationDate(String alias) { public Date engineGetCreationDate(String alias) {
KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
if (entry != null) { if (entry != null) {
return new Date(entry.date.getTime()); return new Date(entry.date.getTime());
} else { } else {
...@@ -434,7 +538,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -434,7 +538,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
new KeyStore.PasswordProtection(password); new KeyStore.PasswordProtection(password);
try { try {
setKeyEntry(alias, key, passwordProtection, chain); setKeyEntry(alias, key, passwordProtection, chain, null);
} finally { } finally {
try { try {
...@@ -446,57 +550,94 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -446,57 +550,94 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
} }
/* /*
* Sets a key entry * Sets a key entry (with attributes, when present)
*/ */
private void setKeyEntry(String alias, Key key, private void setKeyEntry(String alias, Key key,
KeyStore.PasswordProtection passwordProtection, Certificate[] chain) KeyStore.PasswordProtection passwordProtection, Certificate[] chain,
Set<KeyStore.Entry.Attribute> attributes)
throws KeyStoreException throws KeyStoreException
{ {
try { try {
KeyEntry entry = new KeyEntry(); Entry entry;
entry.date = new Date();
if (key instanceof PrivateKey) { if (key instanceof PrivateKey) {
PrivateKeyEntry keyEntry = new PrivateKeyEntry();
keyEntry.date = new Date();
if ((key.getFormat().equals("PKCS#8")) || if ((key.getFormat().equals("PKCS#8")) ||
(key.getFormat().equals("PKCS8"))) { (key.getFormat().equals("PKCS8"))) {
// Encrypt the private key
if (debug != null) { if (debug != null) {
debug.println("Setting a protected private key at " + debug.println("Setting a protected private key (" +
"alias '" + alias + "'"); key.getClass().getName() + ") at alias '" + alias +
"'");
} }
entry.protectedPrivKey = // Encrypt the private key
keyEntry.protectedPrivKey =
encryptPrivateKey(key.getEncoded(), passwordProtection); encryptPrivateKey(key.getEncoded(), passwordProtection);
} else { } else {
throw new KeyStoreException("Private key is not encoded" + throw new KeyStoreException("Private key is not encoded" +
"as PKCS#8"); "as PKCS#8");
} }
} else {
throw new KeyStoreException("Key is not a PrivateKey");
}
// clone the chain // clone the chain
if (chain != null) { if (chain != null) {
// validate cert-chain // validate cert-chain
if ((chain.length > 1) && (!validateChain(chain))) if ((chain.length > 1) && (!validateChain(chain)))
throw new KeyStoreException("Certificate chain is " + throw new KeyStoreException("Certificate chain is " +
"not validate"); "not valid");
entry.chain = chain.clone(); keyEntry.chain = chain.clone();
certificateCount += chain.length;
if (debug != null) { if (debug != null) {
debug.println("Setting a " + chain.length + debug.println("Setting a " + chain.length +
"-certificate chain at alias '" + alias + "'"); "-certificate chain at alias '" + alias + "'");
} }
} }
privateKeyCount++;
entry = keyEntry;
} else if (key instanceof SecretKey) {
SecretKeyEntry keyEntry = new SecretKeyEntry();
keyEntry.date = new Date();
// Encode secret key in a PKCS#8
DerOutputStream pkcs8 = new DerOutputStream();
DerOutputStream secretKeyInfo = new DerOutputStream();
secretKeyInfo.putInteger(0);
AlgorithmId algId = AlgorithmId.get(key.getAlgorithm());
algId.encode(secretKeyInfo);
secretKeyInfo.putOctetString(key.getEncoded());
pkcs8.write(DerValue.tag_Sequence, secretKeyInfo);
// Encrypt the secret key (using same PBE as for private keys)
keyEntry.protectedSecretKey =
encryptPrivateKey(pkcs8.toByteArray(), passwordProtection);
if (debug != null) {
debug.println("Setting a protected secret key (" +
key.getClass().getName() + ") at alias '" + alias +
"'");
}
secretKeyCount++;
entry = keyEntry;
} else {
throw new KeyStoreException("Unsupported Key type");
}
entry.attributes = new HashSet<>();
if (attributes != null) {
entry.attributes.addAll(attributes);
}
// set the keyId to current date // set the keyId to current date
entry.keyId = ("Time " + (entry.date).getTime()).getBytes("UTF8"); entry.keyId = ("Time " + (entry.date).getTime()).getBytes("UTF8");
// set the alias // set the alias
entry.alias = alias.toLowerCase(Locale.ENGLISH); entry.alias = alias.toLowerCase(Locale.ENGLISH);
// add the entry // add the entry
entries.put(alias.toLowerCase(Locale.ENGLISH), entry); entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
} catch (Exception nsae) { } catch (Exception nsae) {
throw new KeyStoreException("Key protection " + throw new KeyStoreException("Key protection " +
" algorithm not found: " + nsae, nsae); " algorithm not found: " + nsae, nsae);
...@@ -530,7 +671,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -530,7 +671,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
Certificate[] chain) Certificate[] chain)
throws KeyStoreException throws KeyStoreException
{ {
// key must be encoded as EncryptedPrivateKeyInfo // Private key must be encoded as EncryptedPrivateKeyInfo
// as defined in PKCS#8 // as defined in PKCS#8
try { try {
new EncryptedPrivateKeyInfo(key); new EncryptedPrivateKeyInfo(key);
...@@ -539,11 +680,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -539,11 +680,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
+ " as PKCS#8 EncryptedPrivateKeyInfo: " + ioe, ioe); + " as PKCS#8 EncryptedPrivateKeyInfo: " + ioe, ioe);
} }
KeyEntry entry = new KeyEntry(); PrivateKeyEntry entry = new PrivateKeyEntry();
entry.date = new Date(); entry.date = new Date();
if (debug != null) { if (debug != null) {
debug.println("Setting a protected key at alias '" + alias + "'"); debug.println("Setting a protected private key at alias '" +
alias + "'");
} }
try { try {
...@@ -558,6 +700,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -558,6 +700,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
entry.protectedPrivKey = key.clone(); entry.protectedPrivKey = key.clone();
if (chain != null) { if (chain != null) {
entry.chain = chain.clone(); entry.chain = chain.clone();
certificateCount += chain.length;
if (debug != null) { if (debug != null) {
debug.println("Setting a " + entry.chain.length + debug.println("Setting a " + entry.chain.length +
...@@ -566,6 +709,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -566,6 +709,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
} }
// add the entry // add the entry
privateKeyCount++;
entries.put(alias.toLowerCase(Locale.ENGLISH), entry); entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
} }
...@@ -644,6 +788,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -644,6 +788,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
PBEKeySpec keySpec = new PBEKeySpec(password); PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE"); SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE");
skey = skFac.generateSecret(keySpec); skey = skFac.generateSecret(keySpec);
keySpec.clearPassword();
} catch (Exception e) { } catch (Exception e) {
throw new IOException("getSecretKey failed: " + throw new IOException("getSecretKey failed: " +
e.getMessage(), e); e.getMessage(), e);
...@@ -695,7 +840,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -695,7 +840,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
new PrivilegedAction<String>() { new PrivilegedAction<String>() {
public String run() { public String run() {
String prop = String prop =
Security.getProperty( Security.getProperty
KEY_PROTECTION_ALGORITHM[0]); KEY_PROTECTION_ALGORITHM[0]);
if (prop == null) { if (prop == null) {
prop = Security.getProperty( prop = Security.getProperty(
...@@ -762,17 +907,36 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -762,17 +907,36 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* @param cert the certificate * @param cert the certificate
* *
* @exception KeyStoreException if the given alias already exists and does * @exception KeyStoreException if the given alias already exists and does
* identify a <i>key entry</i>, or on an attempt to create a * not identify a <i>trusted certificate entry</i>, or this operation fails
* <i>trusted cert entry</i> which is currently not supported. * for some other reason.
*/ */
public synchronized void engineSetCertificateEntry(String alias, public synchronized void engineSetCertificateEntry(String alias,
Certificate cert) throws KeyStoreException Certificate cert) throws KeyStoreException
{ {
KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); setCertEntry(alias, cert, null);
if (entry != null) { }
/*
* Sets a trusted cert entry (with attributes, when present)
*/
private void setCertEntry(String alias, Certificate cert,
Set<KeyStore.Entry.Attribute> attributes) throws KeyStoreException {
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
if (entry != null && entry instanceof KeyEntry) {
throw new KeyStoreException("Cannot overwrite own certificate"); throw new KeyStoreException("Cannot overwrite own certificate");
} else }
throw new KeyStoreException("TrustedCertEntry not supported");
CertEntry certEntry =
new CertEntry((X509Certificate) cert, null, alias, AnyUsage,
attributes);
certificateCount++;
entries.put(alias, certEntry);
if (debug != null) {
debug.println("Setting a trusted certificate at alias '" + alias +
"'");
}
} }
/** /**
...@@ -789,6 +953,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -789,6 +953,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
debug.println("Removing entry at alias '" + alias + "'"); 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)); entries.remove(alias.toLowerCase(Locale.ENGLISH));
} }
...@@ -798,7 +974,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -798,7 +974,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* @return enumeration of the alias names * @return enumeration of the alias names
*/ */
public Enumeration<String> engineAliases() { public Enumeration<String> engineAliases() {
return entries.keys(); return Collections.enumeration(entries.keySet());
} }
/** /**
...@@ -829,8 +1005,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -829,8 +1005,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* <i>key entry</i>, false otherwise. * <i>key entry</i>, false otherwise.
*/ */
public boolean engineIsKeyEntry(String alias) { public boolean engineIsKeyEntry(String alias) {
KeyEntry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
if (entry != null) { if (entry != null && entry instanceof KeyEntry) {
return true; return true;
} else { } else {
return false; return false;
...@@ -845,9 +1021,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -845,9 +1021,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* <i>trusted certificate entry</i>, false otherwise. * <i>trusted certificate entry</i>, false otherwise.
*/ */
public boolean engineIsCertificateEntry(String alias) { public boolean engineIsCertificateEntry(String alias) {
// TrustedCertEntry is not supported Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
if (entry != null && entry instanceof CertEntry &&
((CertEntry) entry).trustedKeyUsage != null) {
return true;
} else {
return false; return false;
} }
}
/** /**
* Returns the (alias) name of the first keystore entry whose certificate * Returns the (alias) name of the first keystore entry whose certificate
...@@ -868,11 +1049,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -868,11 +1049,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
public String engineGetCertificateAlias(Certificate cert) { public String engineGetCertificateAlias(Certificate cert) {
Certificate certElem = null; Certificate certElem = null;
for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) { for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) {
String alias = e.nextElement(); String alias = e.nextElement();
KeyEntry entry = entries.get(alias); Entry entry = entries.get(alias);
if (entry.chain != null) { if (entry instanceof PrivateKeyEntry) {
certElem = entry.chain[0]; 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)) { if (certElem.equals(cert)) {
return alias; return alias;
...@@ -918,6 +1106,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -918,6 +1106,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
DerOutputStream authSafeContentInfo = new DerOutputStream(); DerOutputStream authSafeContentInfo = new DerOutputStream();
// -- create safeContent Data ContentInfo // -- create safeContent Data ContentInfo
if (privateKeyCount > 0 || secretKeyCount > 0) {
if (debug != null) { if (debug != null) {
debug.println("Storing " + privateKeyCount + debug.println("Storing " + privateKeyCount +
" protected key(s) in a PKCS#7 data content-type"); " protected key(s) in a PKCS#7 data content-type");
...@@ -926,11 +1116,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -926,11 +1116,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
byte[] safeContentData = createSafeContent(); byte[] safeContentData = createSafeContent();
ContentInfo dataContentInfo = new ContentInfo(safeContentData); ContentInfo dataContentInfo = new ContentInfo(safeContentData);
dataContentInfo.encode(authSafeContentInfo); dataContentInfo.encode(authSafeContentInfo);
}
// -- create EncryptedContentInfo // -- create EncryptedContentInfo
if (certificateCount > 0) {
if (debug != null) { if (debug != null) {
debug.println("Storing certificate(s) in a PKCS#7 encryptedData " + debug.println("Storing " + certificateCount +
"content-type"); " certificate(s) in a PKCS#7 encryptedData content-type");
} }
byte[] encrData = createEncryptedData(password); byte[] encrData = createEncryptedData(password);
...@@ -938,6 +1131,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -938,6 +1131,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID, new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID,
new DerValue(encrData)); new DerValue(encrData));
encrContentInfo.encode(authSafeContentInfo); encrContentInfo.encode(authSafeContentInfo);
}
// wrap as SequenceOf ContentInfos // wrap as SequenceOf ContentInfos
DerOutputStream cInfo = new DerOutputStream(); DerOutputStream cInfo = new DerOutputStream();
...@@ -962,6 +1156,207 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -962,6 +1156,207 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
stream.flush(); stream.flush();
} }
/**
* 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));
if (protParam == null) {
if (engineIsCertificateEntry(alias)) {
if (entry instanceof CertEntry &&
((CertEntry) entry).trustedKeyUsage != null) {
if (debug != null) {
debug.println("Retrieved a trusted certificate at " +
"alias '" + alias + "'");
}
return new KeyStore.TrustedCertificateEntry(
((CertEntry)entry).cert, getAttributes(entry));
}
} else {
throw new UnrecoverableKeyException
("requested entry requires a password");
}
}
if (protParam instanceof KeyStore.PasswordProtection) {
if (engineIsCertificateEntry(alias)) {
throw new UnsupportedOperationException
("trusted certificate entries are not password-protected");
} else if (engineIsKeyEntry(alias)) {
KeyStore.PasswordProtection pp =
(KeyStore.PasswordProtection)protParam;
char[] password = pp.getPassword();
Key key = engineGetKey(alias, password);
if (key instanceof PrivateKey) {
Certificate[] chain = engineGetCertificateChain(alias);
return new KeyStore.PrivateKeyEntry((PrivateKey)key, chain,
getAttributes(entry));
} else if (key instanceof SecretKey) {
return new KeyStore.SecretKeyEntry((SecretKey)key,
getAttributes(entry));
}
} else if (!engineIsKeyEntry(alias)) {
throw new UnsupportedOperationException
("untrusted certificate entries are not " +
"password-protected");
}
}
throw new UnsupportedOperationException();
}
/**
* Saves a <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
if (entry instanceof KeyStore.TrustedCertificateEntry) {
if (protParam != null && pProtect.getPassword() != null) {
// pre-1.5 style setCertificateEntry did not allow password
throw new KeyStoreException
("trusted certificate entries are not password-protected");
} else {
KeyStore.TrustedCertificateEntry tce =
(KeyStore.TrustedCertificateEntry)entry;
setCertEntry(alias, tce.getTrustedCertificate(),
tce.getAttributes());
return;
}
} else if (entry instanceof KeyStore.PrivateKeyEntry) {
if (pProtect == null || pProtect.getPassword() == null) {
// pre-1.5 style setKeyEntry required password
throw new KeyStoreException
("non-null password required to create PrivateKeyEntry");
} else {
KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry)entry;
setKeyEntry(alias, pke.getPrivateKey(), pProtect,
pke.getCertificateChain(), pke.getAttributes());
return;
}
} else if (entry instanceof KeyStore.SecretKeyEntry) {
if (pProtect == null || pProtect.getPassword() == null) {
// pre-1.5 style setKeyEntry required password
throw new KeyStoreException
("non-null password required to create SecretKeyEntry");
} else {
KeyStore.SecretKeyEntry ske = (KeyStore.SecretKeyEntry)entry;
setKeyEntry(alias, ske.getSecretKey(), pProtect,
(Certificate[])null, ske.getAttributes());
return;
}
}
throw new KeyStoreException
("unsupported entry type: " + entry.getClass().getName());
}
/*
* Assemble the entry attributes
*/
private Set<KeyStore.Entry.Attribute> 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. * Generate Hash.
*/ */
...@@ -1036,11 +1431,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1036,11 +1431,12 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
/* /*
* Create PKCS#12 Attributes, friendlyName and localKeyId. * Create PKCS#12 Attributes, friendlyName, localKeyId and trustedKeyUsage.
* *
* Although attributes are optional, they could be required. * Although attributes are optional, they could be required.
* For e.g. localKeyId attribute is required to match the * For e.g. localKeyId attribute is required to match the
* private key with the associated end-entity certificate. * private key with the associated end-entity certificate.
* The trustedKeyUsage attribute is used to denote a trusted certificate.
* *
* PKCS8ShroudedKeyBags include unique localKeyID and friendlyName. * PKCS8ShroudedKeyBags include unique localKeyID and friendlyName.
* CertBags may or may not include attributes depending on the type * CertBags may or may not include attributes depending on the type
...@@ -1062,20 +1458,28 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1062,20 +1458,28 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* friendlyName unique same/ same/ unique * friendlyName unique same/ same/ unique
* unique unique/ * unique unique/
* null * null
* trustedKeyUsage - - - true
* *
* Note: OpenSSL adds friendlyName for end-entity cert only, and * Note: OpenSSL adds friendlyName for end-entity cert only, and
* removes the localKeyID and friendlyName for CA certs. * removes the localKeyID and friendlyName for CA certs.
* If the CertBag did not have a friendlyName, most vendors will * If the CertBag did not have a friendlyName, most vendors will
* add it, and assign it to the DN of the cert. * add it, and assign it to the DN of the cert.
*/ */
private byte[] getBagAttributes(String alias, byte[] keyId) private byte[] getBagAttributes(String alias, byte[] keyId,
throws IOException { Set<KeyStore.Entry.Attribute> attributes) throws IOException {
return getBagAttributes(alias, keyId, null, attributes);
}
private byte[] getBagAttributes(String alias, byte[] keyId,
ObjectIdentifier[] trustedUsage,
Set<KeyStore.Entry.Attribute> attributes) throws IOException {
byte[] localKeyID = null; byte[] localKeyID = null;
byte[] friendlyName = null; byte[] friendlyName = null;
byte[] trustedKeyUsage = null;
// return null if both attributes are null // return null if all three attributes are null
if ((alias == null) && (keyId == null)) { if ((alias == null) && (keyId == null) && (trustedKeyUsage == null)) {
return null; return null;
} }
...@@ -1106,6 +1510,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1106,6 +1510,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
localKeyID = bagAttrValue2.toByteArray(); 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(); DerOutputStream attrs = new DerOutputStream();
if (friendlyName != null) { if (friendlyName != null) {
attrs.write(friendlyName); attrs.write(friendlyName);
...@@ -1113,11 +1531,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1113,11 +1531,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
if (localKeyID != null) { if (localKeyID != null) {
attrs.write(localKeyID); attrs.write(localKeyID);
} }
if (trustedKeyUsage != null) {
attrs.write(trustedKeyUsage);
}
if (attributes != null) {
for (KeyStore.Entry.Attribute attribute : attributes) {
attrs.write(((PKCS12Attribute) attribute).getEncoded());
}
}
bagAttrs.write(DerValue.tag_Set, attrs); bagAttrs.write(DerValue.tag_Set, attrs);
return bagAttrs.toByteArray(); return bagAttrs.toByteArray();
} }
/* /*
* Create EncryptedData content type, that contains EncryptedContentInfo. * Create EncryptedData content type, that contains EncryptedContentInfo.
* Includes certificates in individual SafeBags of type CertBag. * Includes certificates in individual SafeBags of type CertBag.
...@@ -1128,17 +1555,26 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1128,17 +1555,26 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
throws CertificateException, IOException throws CertificateException, IOException
{ {
DerOutputStream out = new DerOutputStream(); DerOutputStream out = new DerOutputStream();
for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) { for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) {
String alias = e.nextElement(); String alias = e.nextElement();
KeyEntry entry = entries.get(alias); Entry entry = entries.get(alias);
// certificate chain // certificate chain
int chainLen; int chainLen = 1;
if (entry.chain == null) { Certificate[] certs = null;
if (entry instanceof PrivateKeyEntry) {
PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
if (keyEntry.chain == null) {
chainLen = 0; chainLen = 0;
} else { } else {
chainLen = entry.chain.length; 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++) { for (int i = 0; i < chainLen; i++) {
...@@ -1152,7 +1588,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1152,7 +1588,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
// write encoded certs in a context-specific tag // write encoded certs in a context-specific tag
DerOutputStream certValue = new DerOutputStream(); DerOutputStream certValue = new DerOutputStream();
X509Certificate cert = (X509Certificate)entry.chain[i]; X509Certificate cert = (X509Certificate) certs[i];
certValue.putOctetString(cert.getEncoded()); certValue.putOctetString(cert.getEncoded());
certBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, certBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
true, (byte) 0), certValue); true, (byte) 0), certValue);
...@@ -1175,7 +1611,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1175,7 +1611,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
byte[] bagAttrs = null; byte[] bagAttrs = null;
if (i == 0) { if (i == 0) {
// Only End-Entity Cert should have a localKeyId. // 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 { } else {
// Trusted root CA certs and Intermediate CA certs do not // Trusted root CA certs and Intermediate CA certs do not
// need to have a localKeyId, and hence localKeyId is null // need to have a localKeyId, and hence localKeyId is null
...@@ -1184,7 +1631,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1184,7 +1631,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
// certificate chain to have unique or null localKeyID. // certificate chain to have unique or null localKeyID.
// However, IE/OpenSSL do not impose this restriction. // However, IE/OpenSSL do not impose this restriction.
bagAttrs = getBagAttributes( bagAttrs = getBagAttributes(
cert.getSubjectX500Principal().getName(), null); cert.getSubjectX500Principal().getName(), null,
entry.attributes);
} }
if (bagAttrs != null) { if (bagAttrs != null) {
safeBag.write(bagAttrs); safeBag.write(bagAttrs);
...@@ -1214,6 +1662,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1214,6 +1662,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
/* /*
* Create SafeContent Data content type. * 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. * Includes encrypted private key in a SafeBag of type PKCS8ShroudedKeyBag.
* Each PKCS8ShroudedKeyBag includes pkcs12 attributes * Each PKCS8ShroudedKeyBag includes pkcs12 attributes
* (see comments in getBagAttributes) * (see comments in getBagAttributes)
...@@ -1222,23 +1671,31 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1222,23 +1671,31 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
throws CertificateException, IOException { throws CertificateException, IOException {
DerOutputStream out = new DerOutputStream(); DerOutputStream out = new DerOutputStream();
for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) { for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) {
String alias = e.nextElement(); 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 // Create SafeBag of type pkcs8ShroudedKeyBag
DerOutputStream safeBag = new DerOutputStream();
safeBag.putOID(PKCS8ShroudedKeyBag_OID); safeBag.putOID(PKCS8ShroudedKeyBag_OID);
// get the encrypted private key // get the encrypted private key
byte[] encrBytes = entry.protectedPrivKey; byte[] encrBytes = ((PrivateKeyEntry)keyEntry).protectedPrivKey;
EncryptedPrivateKeyInfo encrInfo = null; EncryptedPrivateKeyInfo encrInfo = null;
try { try {
encrInfo = new EncryptedPrivateKeyInfo(encrBytes); encrInfo = new EncryptedPrivateKeyInfo(encrBytes);
} catch (IOException ioe) { } catch (IOException ioe) {
throw new IOException("Private key not stored as " throw new IOException("Private key not stored as "
+ "PKCS#8 EncryptedPrivateKeyInfo" + ioe.getMessage()); + "PKCS#8 EncryptedPrivateKeyInfo"
+ ioe.getMessage());
} }
// Wrap the EncryptedPrivateKeyInfo in a context-specific tag. // Wrap the EncryptedPrivateKeyInfo in a context-specific tag.
...@@ -1247,8 +1704,41 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1247,8 +1704,41 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
true, (byte) 0), bagValue); true, (byte) 0), bagValue);
// DER encode the secret key
} else if (keyEntry instanceof SecretKeyEntry) {
// Create SafeBag of type SecretBag
safeBag.putOID(SecretBag_OID);
// Create a SecretBag
DerOutputStream secretBag = new DerOutputStream();
secretBag.putOID(PKCS8ShroudedKeyBag_OID);
// Write secret key in a context-specific tag
DerOutputStream secretKeyValue = new DerOutputStream();
secretKeyValue.putOctetString(
((SecretKeyEntry) keyEntry).protectedSecretKey);
secretBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
true, (byte) 0), secretKeyValue);
// Wrap SecretBag in a Sequence
DerOutputStream secretBagSeq = new DerOutputStream();
secretBagSeq.write(DerValue.tag_Sequence, secretBag);
byte[] secretBagValue = secretBagSeq.toByteArray();
// Wrap the secret bag in a context-specific tag.
DerOutputStream bagValue = new DerOutputStream();
bagValue.write(secretBagValue);
// Write SafeBag value
safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,
true, (byte) 0), bagValue);
} else {
continue; // skip this entry
}
// write SafeBag Attributes // write SafeBag Attributes
byte[] bagAttrs = getBagAttributes(alias, entry.keyId); byte[] bagAttrs =
getBagAttributes(alias, entry.keyId, entry.attributes);
safeBag.write(bagAttrs); safeBag.write(bagAttrs);
// wrap as Sequence // wrap as Sequence
...@@ -1377,8 +1867,10 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1377,8 +1867,10 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
DerValue[] safeContentsArray = as.getSequence(2); DerValue[] safeContentsArray = as.getSequence(2);
int count = safeContentsArray.length; int count = safeContentsArray.length;
// reset the count at the start // reset the counters at the start
privateKeyCount = 0; privateKeyCount = 0;
secretKeyCount = 0;
certificateCount = 0;
/* /*
* Spin over the ContentInfos. * Spin over the ContentInfos.
...@@ -1493,9 +1985,10 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1493,9 +1985,10 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
/* /*
* Match up private keys with certificate chains. * 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++) { for (int m = 0; m < list.length; m++) {
KeyEntry entry = list[m]; PrivateKeyEntry entry = list[m];
if (entry.keyId != null) { if (entry.keyId != null) {
ArrayList<X509Certificate> chain = ArrayList<X509Certificate> chain =
new ArrayList<X509Certificate>(); new ArrayList<X509Certificate>();
...@@ -1513,6 +2006,22 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1513,6 +2006,22 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
entry.chain = chain.toArray(new Certificate[chain.size()]); 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(); certEntries.clear();
certsMap.clear(); certsMap.clear();
keyList.clear(); keyList.clear();
...@@ -1523,7 +2032,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1523,7 +2032,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
* @param entry the KeyEntry to match * @param entry the KeyEntry to match
* @return a certificate, null if not found * @return a certificate, null if not found
*/ */
private X509Certificate findMatchedCertificate(KeyEntry entry) { private X509Certificate findMatchedCertificate(PrivateKeyEntry entry) {
CertEntry keyIdMatch = null; CertEntry keyIdMatch = null;
CertEntry aliasMatch = null; CertEntry aliasMatch = null;
for (CertEntry ce: certEntries) { for (CertEntry ce: certEntries) {
...@@ -1567,7 +2076,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1567,7 +2076,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
} }
bagValue = bagValue.data.getDerValue(); bagValue = bagValue.data.getDerValue();
if (bagId.equals((Object)PKCS8ShroudedKeyBag_OID)) { if (bagId.equals((Object)PKCS8ShroudedKeyBag_OID)) {
KeyEntry kEntry = new KeyEntry(); PrivateKeyEntry kEntry = new PrivateKeyEntry();
kEntry.protectedPrivKey = bagValue.toByteArray(); kEntry.protectedPrivKey = bagValue.toByteArray();
bagItem = kEntry; bagItem = kEntry;
privateKeyCount++; privateKeyCount++;
...@@ -1585,6 +2094,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1585,6 +2094,20 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
cert = (X509Certificate)cf.generateCertificate cert = (X509Certificate)cf.generateCertificate
(new ByteArrayInputStream(certValue.getOctetString())); (new ByteArrayInputStream(certValue.getOctetString()));
bagItem = cert; 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 { } else {
if (debug != null) { if (debug != null) {
...@@ -1594,7 +2117,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1594,7 +2117,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
DerValue[] attrSet; DerValue[] attrSet;
try { try {
attrSet = sbi.getSet(2); attrSet = sbi.getSet(3);
} catch (IOException e) { } catch (IOException e) {
// entry does not have attributes // entry does not have attributes
// Note: CA certs can have no attributes // Note: CA certs can have no attributes
...@@ -1604,11 +2127,13 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1604,11 +2127,13 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
String alias = null; String alias = null;
byte[] keyId = null; byte[] keyId = null;
ObjectIdentifier[] trustedKeyUsage = null;
Set<PKCS12Attribute> attributes = new HashSet<>();
if (attrSet != null) { if (attrSet != null) {
for (int j = 0; j < attrSet.length; j++) { for (int j = 0; j < attrSet.length; j++) {
DerInputStream as = byte[] encoded = attrSet[j].toByteArray();
new DerInputStream(attrSet[j].toByteArray()); DerInputStream as = new DerInputStream(encoded);
DerValue[] attrSeq = as.getSequence(2); DerValue[] attrSeq = as.getSequence(2);
ObjectIdentifier attrId = attrSeq[0].getOID(); ObjectIdentifier attrId = attrSeq[0].getOID();
DerInputStream vs = DerInputStream vs =
...@@ -1624,12 +2149,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1624,12 +2149,14 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
alias = valSet[0].getBMPString(); alias = valSet[0].getBMPString();
} else if (attrId.equals((Object)PKCS9LocalKeyId_OID)) { } else if (attrId.equals((Object)PKCS9LocalKeyId_OID)) {
keyId = valSet[0].getOctetString(); keyId = valSet[0].getOctetString();
} else { } else if
(attrId.equals((Object)TrustedKeyUsage_OID)) {
if (debug != null) { trustedKeyUsage = new ObjectIdentifier[valSet.length];
debug.println("Unsupported PKCS12 bag attribute: " + for (int k = 0; k < valSet.length; k++) {
attrId); trustedKeyUsage[k] = valSet[k].getOID();
} }
} else {
attributes.add(new PKCS12Attribute(encoded));
} }
} }
} }
...@@ -1645,6 +2172,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1645,6 +2172,8 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
*/ */
if (bagItem instanceof KeyEntry) { if (bagItem instanceof KeyEntry) {
KeyEntry entry = (KeyEntry)bagItem; KeyEntry entry = (KeyEntry)bagItem;
if (bagItem instanceof PrivateKeyEntry) {
if (keyId == null) { if (keyId == null) {
// Insert a localKeyID for the privateKey // Insert a localKeyID for the privateKey
// Note: This is a workaround to allow null localKeyID // Note: This is a workaround to allow null localKeyID
...@@ -1656,6 +2185,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1656,6 +2185,7 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
continue; continue;
} }
} }
}
entry.keyId = keyId; entry.keyId = keyId;
// restore date if it exists // restore date if it exists
String keyIdStr = new String(keyId, "UTF8"); String keyIdStr = new String(keyId, "UTF8");
...@@ -1672,11 +2202,16 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1672,11 +2202,16 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
date = new Date(); date = new Date();
} }
entry.date = date; entry.date = date;
keyList.add(entry);
if (alias == null) if (bagItem instanceof PrivateKeyEntry) {
keyList.add((PrivateKeyEntry) entry);
}
if (alias == null) {
alias = getUnfriendlyName(); alias = getUnfriendlyName();
}
entry.alias = alias; entry.alias = alias;
entries.put(alias.toLowerCase(Locale.ENGLISH), entry); entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
} else if (bagItem instanceof X509Certificate) { } else if (bagItem instanceof X509Certificate) {
X509Certificate cert = (X509Certificate)bagItem; X509Certificate cert = (X509Certificate)bagItem;
// Insert a localKeyID for the corresponding cert // Insert a localKeyID for the corresponding cert
...@@ -1689,7 +2224,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi { ...@@ -1689,7 +2224,18 @@ public final class PKCS12KeyStore extends KeyStoreSpi {
keyId = "01".getBytes("UTF8"); keyId = "01".getBytes("UTF8");
} }
} }
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)); certEntries.add(new CertEntry(cert, keyId, alias));
}
X500Principal subjectDN = cert.getSubjectX500Principal(); X500Principal subjectDN = cert.getSubjectX500Principal();
if (subjectDN != null) { if (subjectDN != null) {
if (!certsMap.containsKey(subjectDN)) { if (!certsMap.containsKey(subjectDN)) {
......
/* /*
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -502,6 +502,11 @@ public class AlgorithmId implements Serializable, DerEncoder { ...@@ -502,6 +502,11 @@ public class AlgorithmId implements Serializable, DerEncoder {
return AlgorithmId.ECDH_oid; return AlgorithmId.ECDH_oid;
} }
// Secret key algorithms
if (name.equalsIgnoreCase("AES")) {
return AlgorithmId.AES_oid;
}
// Common signature types // Common signature types
if (name.equalsIgnoreCase("MD5withRSA") if (name.equalsIgnoreCase("MD5withRSA")
|| name.equalsIgnoreCase("MD5/RSA")) { || name.equalsIgnoreCase("MD5/RSA")) {
...@@ -660,6 +665,12 @@ public class AlgorithmId implements Serializable, DerEncoder { ...@@ -660,6 +665,12 @@ public class AlgorithmId implements Serializable, DerEncoder {
public static final ObjectIdentifier RSA_oid; public static final ObjectIdentifier RSA_oid;
public static final ObjectIdentifier RSAEncryption_oid; public static final ObjectIdentifier RSAEncryption_oid;
/*
* COMMON SECRET KEY TYPES
*/
public static final ObjectIdentifier AES_oid =
oid(2, 16, 840, 1, 101, 3, 4, 1);
/* /*
* COMMON SIGNATURE ALGORITHMS * COMMON SIGNATURE ALGORITHMS
*/ */
...@@ -893,6 +904,8 @@ public class AlgorithmId implements Serializable, DerEncoder { ...@@ -893,6 +904,8 @@ public class AlgorithmId implements Serializable, DerEncoder {
nameTable.put(EC_oid, "EC"); nameTable.put(EC_oid, "EC");
nameTable.put(ECDH_oid, "ECDH"); nameTable.put(ECDH_oid, "ECDH");
nameTable.put(AES_oid, "AES");
nameTable.put(sha1WithECDSA_oid, "SHA1withECDSA"); nameTable.put(sha1WithECDSA_oid, "SHA1withECDSA");
nameTable.put(sha224WithECDSA_oid, "SHA224withECDSA"); nameTable.put(sha224WithECDSA_oid, "SHA224withECDSA");
nameTable.put(sha256WithECDSA_oid, "SHA256withECDSA"); nameTable.put(sha256WithECDSA_oid, "SHA256withECDSA");
......
/*
* 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()));
}
}
/*
* 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();
}
}
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8005408
* @summary KeyStore API enhancements
*/
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.security.cert.Certificate;
import javax.crypto.*;
import javax.crypto.spec.*;
// Store a trusted certificate in a keystore and retrieve it again.
public class StoreTrustedCertTest {
private final static String DIR = System.getProperty("test.src", ".");
private static final char[] PASSWORD = "passphrase".toCharArray();
private static final String KEYSTORE = "truststore.p12";
private static final String CERT = DIR + "/trusted.pem";
private static final String ALIAS = "my trustedcert";
private static final String ALIAS2 = "my trustedcert with attributes";
public static void main(String[] args) throws Exception {
new File(KEYSTORE).delete();
try {
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(null, null);
Certificate cert = loadCertificate(CERT);
Set<KeyStore.Entry.Attribute> attributes = new HashSet<>();
attributes.add(new PKCS12Attribute("1.3.5.7.9", "that's odd"));
attributes.add(new PKCS12Attribute("2.4.6.8.10", "that's even"));
// Set trusted certificate entry
keystore.setEntry(ALIAS,
new KeyStore.TrustedCertificateEntry(cert), null);
// Set trusted certificate entry with attributes
keystore.setEntry(ALIAS2,
new KeyStore.TrustedCertificateEntry(cert, attributes), null);
System.out.println("Storing keystore to: " + KEYSTORE);
keystore.store(new FileOutputStream(KEYSTORE), PASSWORD);
System.out.println("Loading keystore from: " + KEYSTORE);
keystore.load(new FileInputStream(KEYSTORE), PASSWORD);
System.out.println("Loaded keystore with " + keystore.size() +
" entries");
KeyStore.Entry entry = keystore.getEntry(ALIAS, null);
if (entry instanceof KeyStore.TrustedCertificateEntry) {
System.out.println("Retrieved trusted certificate entry: " +
entry);
} else {
throw new Exception("Not a trusted certificate entry");
}
System.out.println();
entry = keystore.getEntry(ALIAS2, null);
if (entry instanceof KeyStore.TrustedCertificateEntry) {
KeyStore.TrustedCertificateEntry trustedEntry =
(KeyStore.TrustedCertificateEntry) entry;
Set<KeyStore.Entry.Attribute> entryAttributes =
trustedEntry.getAttributes();
if (entryAttributes.containsAll(attributes)) {
System.out.println("Retrieved trusted certificate entry " +
"with attributes: " + entry);
} else {
throw new Exception("Failed to retrieve entry attributes");
}
} else {
throw new Exception("Not a trusted certificate entry");
}
} finally {
new File(KEYSTORE).delete();
}
}
private static Certificate loadCertificate(String certFile)
throws Exception {
X509Certificate cert = null;
try (FileInputStream certStream = new FileInputStream(certFile)) {
CertificateFactory factory =
CertificateFactory.getInstance("X.509");
return factory.generateCertificate(certStream);
}
}
}
-----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-----
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册