/*
 * Copyright (c) 1999, 2020, 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.*;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreSpi;
import java.security.KeyStoreException;
import java.security.PKCS12Attribute;
import java.security.PrivateKey;
import java.security.PrivilegedAction;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
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.security.tools.KeyStoreUtil;
import sun.security.util.Debug;
import sun.security.util.DerInputStream;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.util.ObjectIdentifier;
import sun.security.pkcs.ContentInfo;
import sun.security.util.SecurityProperties;
import sun.security.x509.AlgorithmId;
import sun.security.pkcs.EncryptedPrivateKeyInfo;
import sun.security.provider.JavaKeyStore.JKS;
import sun.security.util.KeyStoreDelegator;
import sun.security.x509.AuthorityKeyIdentifierExtension;


This class provides the keystore implementation referred to as "PKCS12". Implements the PKCS#12 PFX protected using the Password privacy mode. The contents are protected using Password integrity mode. Currently these PBE algorithms are used by default: - PBEWithSHA1AndDESede to encrypt private keys, iteration count 50000. - PBEWithSHA1AndRC2_40 to encrypt certificates, iteration count 50000. The default Mac algorithm is HmacPBESHA1, iteration count 100000. Supported encryption of various implementations : Software and mode. Certificate encryption Private key encryption --------------------------------------------------------------------- MSIE4 (domestic 40 bit RC2. 40 bit RC2 and xport versions) PKCS#12 export. MSIE4, 5 (domestic 40 bit RC2, 40 bit RC2, and export versions) 3 key triple DES 3 key triple DES PKCS#12 import. MSIE5 40 bit RC2 3 key triple DES, PKCS#12 export. with SHA1 (168 bits) Netscape Communicator 40 bit RC2 3 key triple DES, (domestic and export with SHA1 (168 bits) versions) PKCS#12 export Netscape Communicator 40 bit ciphers only All. (export version) PKCS#12 import. Netscape Communicator All. All. (domestic or fortified version) PKCS#12 import. OpenSSL PKCS#12 code. All. All. --------------------------------------------------------------------- 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. Trusted certificate entries are identified by the presence of an trustedKeyUsage attribute.
Author:Seema Malkani, Jeff Nisewanger, Jan Luehe
See Also:
/** * This class provides the keystore implementation referred to as "PKCS12". * Implements the PKCS#12 PFX protected using the Password privacy mode. * The contents are protected using Password integrity mode. * * Currently these PBE algorithms are used by default: * - PBEWithSHA1AndDESede to encrypt private keys, iteration count 50000. * - PBEWithSHA1AndRC2_40 to encrypt certificates, iteration count 50000. * * The default Mac algorithm is HmacPBESHA1, iteration count 100000. * * Supported encryption of various implementations : * * Software and mode. Certificate encryption Private key encryption * --------------------------------------------------------------------- * MSIE4 (domestic 40 bit RC2. 40 bit RC2 * and xport versions) * PKCS#12 export. * * MSIE4, 5 (domestic 40 bit RC2, 40 bit RC2, * and export versions) 3 key triple DES 3 key triple DES * PKCS#12 import. * * MSIE5 40 bit RC2 3 key triple DES, * PKCS#12 export. with SHA1 (168 bits) * * Netscape Communicator 40 bit RC2 3 key triple DES, * (domestic and export with SHA1 (168 bits) * versions) PKCS#12 export * * Netscape Communicator 40 bit ciphers only All. * (export version) * PKCS#12 import. * * Netscape Communicator All. All. * (domestic or fortified * version) PKCS#12 import. * * OpenSSL PKCS#12 code. All. All. * --------------------------------------------------------------------- * * 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. Trusted certificate entries are identified * by the presence of an trustedKeyUsage attribute. * * @author Seema Malkani * @author Jeff Nisewanger * @author Jan Luehe * * @see java.security.KeyStoreSpi */
public final class PKCS12KeyStore extends KeyStoreSpi { // special PKCS12 keystore that supports PKCS12 and JKS file formats public static final class DualFormatPKCS12 extends KeyStoreDelegator { public DualFormatPKCS12() { super("PKCS12", PKCS12KeyStore.class, "JKS", JKS.class); } } public static final int VERSION_3 = 3; private static final int MAX_ITERATION_COUNT = 5000000; private static final int SALT_LEN = 20; // friendlyName, localKeyId, trustedKeyUsage private static final String[] CORE_ATTRIBUTES = { "1.2.840.113549.1.9.20", "1.2.840.113549.1.9.21", "2.16.840.1.113894.746875.1.1" }; private static final Debug debug = Debug.getInstance("pkcs12"); 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}; private static final int[] pkcs9certType = {1, 2, 840, 113549, 1, 9, 22, 1}; 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 final ObjectIdentifier PKCS8ShroudedKeyBag_OID; private static final ObjectIdentifier CertBag_OID; private static final ObjectIdentifier SecretBag_OID; private static final ObjectIdentifier PKCS9FriendlyName_OID; private static final ObjectIdentifier PKCS9LocalKeyId_OID; private static final ObjectIdentifier PKCS9CertType_OID; private static final ObjectIdentifier pbes2_OID; private static final ObjectIdentifier TrustedKeyUsage_OID; private static final ObjectIdentifier[] AnyUsage; private int counter = 0; // private key count // Note: This is a workaround to allow null localKeyID attribute // 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; // Alg/params used for *this* keystore. Initialized as -1 for ic and // null for algorithm names. When an existing file is read, they will be // assigned inside engineLoad() so storing an existing keystore uses the // old alg/params. This makes sure if a keystore is created password-less // it will be password-less forever. Otherwise, engineStore() will read // the default values. These fields are always reset when load() is called. private String certProtectionAlgorithm = null; private int certPbeIterationCount = -1; private String macAlgorithm = null; private int macIterationCount = -1; // the source of randomness private SecureRandom random; static { 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); pbes2_OID = new ObjectIdentifier(pbes2); TrustedKeyUsage_OID = new ObjectIdentifier(TrustedKeyUsage); AnyUsage = new ObjectIdentifier[]{ new ObjectIdentifier(AnyExtendedKeyUsage)}; } catch (IOException ioe) { throw new AssertionError("OID not initialized", ioe); } } // A keystore entry and associated attributes private static class 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; Certificate[] chain; }; // A secret key private static class SecretKeyEntry extends KeyEntry { byte[] protectedSecretKey; }; // A certificate entry private static class CertEntry extends Entry { final X509Certificate cert; ObjectIdentifier[] trustedKeyUsage; CertEntry(X509Certificate cert, byte[] keyId, String alias) { this(cert, keyId, alias, null, null); } CertEntry(X509Certificate cert, byte[] keyId, String alias, ObjectIdentifier[] trustedKeyUsage, Set<? extends KeyStore.Entry.Attribute> attributes) { this.date = new Date(); this.cert = cert; this.keyId = keyId; this.alias = alias; this.trustedKeyUsage = trustedKeyUsage; this.attributes = new HashSet<>(); if (attributes != null) { this.attributes.addAll(attributes); } } }
Retries an action with password "\0" if "" fails.
Type parameters:
  • <T> – the return type
/** * Retries an action with password "\0" if "" fails. * @param <T> the return type */
@FunctionalInterface private interface RetryWithZero<T> { T tryOnce(char[] password) throws Exception; static <S> S run(RetryWithZero<S> f, char[] password) throws Exception { try { return f.tryOnce(password); } catch (Exception e) { if (password.length == 0) { // Retry using an empty password with a NUL terminator. if (debug != null) { debug.println("Retry with a NUL password"); } return f.tryOnce(new char[1]); } throw e; } } }
Private keys and certificates are stored in a map. Map entries are keyed by alias names.
/** * Private keys and certificates are stored in a map. * Map entries are keyed by alias names. */
private Map<String, Entry> entries = Collections.synchronizedMap(new LinkedHashMap<String, Entry>()); private ArrayList<KeyEntry> keyList = new ArrayList<KeyEntry>(); private List<X509Certificate> allCerts = new ArrayList<>(); private ArrayList<CertEntry> certEntries = new ArrayList<CertEntry>();
Returns the key associated with the given alias, using the given password to recover it.
Params:
  • alias – the alias name
  • password – the password for recovering the key
Throws:
Returns:the requested key, or null if the given alias does not exist or does not identify a key entry.
/** * Returns the key associated with the given alias, using the given * password to recover it. * * @param alias the alias name * @param password the password for recovering the key * * @return the requested key, or null if the given alias does not exist * or does not identify a <i>key entry</i>. * * @exception NoSuchAlgorithmException if the algorithm for recovering the * key cannot be found * @exception UnrecoverableKeyException if the key cannot be recovered * (e.g., the given password is wrong). */
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); Key key = null; if (entry == null || (!(entry instanceof KeyEntry))) { return null; } // 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 = new EncryptedPrivateKeyInfo(encrBytes); encryptedKey = encrInfo.getEncryptedData(); // parse Algorithm parameters DerValue val = new DerValue(encrInfo.getAlgorithm().encode()); DerInputStream in = val.toDerInputStream(); algOid = in.getOID(); algParams = parseAlgParameters(algOid, in); } catch (IOException ioe) { UnrecoverableKeyException uke = new UnrecoverableKeyException("Private key not stored as " + "PKCS#8 EncryptedPrivateKeyInfo: " + ioe); uke.initCause(ioe); throw uke; } try { PBEParameterSpec pbeSpec; int ic; if (algParams != null) { try { pbeSpec = algParams.getParameterSpec(PBEParameterSpec.class); } catch (InvalidParameterSpecException ipse) { throw new IOException("Invalid PBE algorithm parameters"); } ic = pbeSpec.getIterationCount(); if (ic > MAX_ITERATION_COUNT) { throw new IOException("key PBE iteration count too large"); } } else { ic = 0; } key = RetryWithZero.run(pass -> { // Use JCE SecretKey skey = getPBEKey(pass); Cipher cipher = Cipher.getInstance( mapPBEParamsToAlgorithm(algOid, algParams)); cipher.init(Cipher.DECRYPT_MODE, skey, algParams); byte[] keyInfo = cipher.doFinal(encryptedKey); /* * Parse the key algorithm and then use a JCA key factory * to re-create the key. */ DerValue val = new DerValue(keyInfo); DerInputStream in = val.toDerInputStream(); int i = in.getInteger(); DerValue[] value = in.getSequence(2); if (value.length < 1 || value.length > 2) { throw new IOException("Invalid length for AlgorithmIdentifier"); } AlgorithmId algId = new AlgorithmId(value[0].getOID()); String keyAlgo = algId.getName(); // decode private key if (entry instanceof PrivateKeyEntry) { KeyFactory kfac = KeyFactory.getInstance(keyAlgo); PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(keyInfo); Key tmp = kfac.generatePrivate(kspec); if (debug != null) { debug.println("Retrieved a protected private key at alias" + " '" + alias + "' (" + mapPBEParamsToAlgorithm(algOid, algParams) + " iterations: " + ic + ")"); } return tmp; // decode secret key } else { byte[] keyBytes = in.getOctetString(); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, keyAlgo); // Special handling required for PBE: needs a PBEKeySpec Key tmp; if (keyAlgo.startsWith("PBE")) { SecretKeyFactory sKeyFactory = SecretKeyFactory.getInstance(keyAlgo); KeySpec pbeKeySpec = sKeyFactory.getKeySpec(secretKeySpec, PBEKeySpec.class); tmp = sKeyFactory.generateSecret(pbeKeySpec); } else { tmp = secretKeySpec; } if (debug != null) { debug.println("Retrieved a protected secret key at alias " + "'" + alias + "' (" + mapPBEParamsToAlgorithm(algOid, algParams) + " iterations: " + ic + ")"); } return tmp; } }, password); } catch (Exception e) { UnrecoverableKeyException uke = new UnrecoverableKeyException("Get Key failed: " + e.getMessage()); uke.initCause(e); throw uke; } return key; }
Returns the certificate chain associated with the given alias.
Params:
  • alias – the alias name
Returns:the certificate chain (ordered with the user's certificate first and the root certificate authority last), or null if the given alias does not exist or does not contain a certificate chain (i.e., the given alias identifies either a trusted certificate entry or a key entry without a certificate chain).
/** * Returns the certificate chain associated with the given alias. * * @param alias the alias name * * @return the certificate chain (ordered with the user's certificate first * and the root certificate authority last), or null if the given alias * does not exist or does not contain a certificate chain (i.e., the given * alias identifies either a <i>trusted certificate entry</i> or a * <i>key entry</i> without a certificate chain). */
public Certificate[] engineGetCertificateChain(String alias) { Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); if (entry != null && entry instanceof PrivateKeyEntry) { if (((PrivateKeyEntry) entry).chain == null) { return null; } else { if (debug != null) { debug.println("Retrieved a " + ((PrivateKeyEntry) entry).chain.length + "-certificate chain at alias '" + alias + "'"); } return ((PrivateKeyEntry) entry).chain.clone(); } } else { return null; } }
Returns the certificate associated with the given alias.

If the given alias name identifies a trusted certificate entry, the certificate associated with that entry is returned. If the given alias name identifies a key entry, the first element of the certificate chain of that entry is returned, or null if that entry does not have a certificate chain.

Params:
  • alias – the alias name
Returns:the certificate, or null if the given alias does not exist or does not contain a certificate.
/** * Returns the certificate associated with the given alias. * * <p>If the given alias name identifies a * <i>trusted certificate entry</i>, the certificate associated with that * entry is returned. If the given alias name identifies a * <i>key entry</i>, the first element of the certificate chain of that * entry is returned, or null if that entry does not have a certificate * chain. * * @param alias the alias name * * @return the certificate, or null if the given alias does not exist or * does not contain a certificate. */
public Certificate engineGetCertificate(String alias) { 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 { if (debug != null) { debug.println("Retrieved a certificate at alias '" + alias + "'"); } return ((PrivateKeyEntry) entry).chain[0]; } } else { return null; } }
Returns the creation date of the entry identified by the given alias.
Params:
  • alias – the alias name
Returns:the creation date of this entry, or null if the given alias does not exist
/** * Returns the creation date of the entry identified by the given alias. * * @param alias the alias name * * @return the creation date of this entry, or null if the given alias does * not exist */
public Date engineGetCreationDate(String alias) { Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); if (entry != null) { return new Date(entry.date.getTime()); } else { return null; } }
Assigns the given key to the given alias, protecting it with the given password.

If the given key is of type java.security.PrivateKey, it must be accompanied by a certificate chain certifying the corresponding public key.

If the given alias already exists, the keystore information associated with it is overridden by the given key (and possibly certificate chain).

Params:
  • alias – the alias name
  • key – the key to be associated with the alias
  • password – the password to protect the key
  • chain – the certificate chain for the corresponding public key (only required if the given key is of type java.security.PrivateKey).
Throws:
  • KeyStoreException – if the given key cannot be protected, or this operation fails for some other reason
/** * Assigns the given key to the given alias, protecting it with the given * password. * * <p>If the given key is of type <code>java.security.PrivateKey</code>, * it must be accompanied by a certificate chain certifying the * corresponding public key. * * <p>If the given alias already exists, the keystore information * associated with it is overridden by the given key (and possibly * certificate chain). * * @param alias the alias name * @param key the key to be associated with the alias * @param password the password to protect the key * @param chain the certificate chain for the corresponding public * key (only required if the given key is of type * <code>java.security.PrivateKey</code>). * * @exception KeyStoreException if the given key cannot be protected, or * this operation fails for some other reason */
public synchronized void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException { KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection(password); try { setKeyEntry(alias, key, passwordProtection, chain, null); } finally { try { passwordProtection.destroy(); } catch (DestroyFailedException dfe) { // ignore } } } /* * Sets a key entry (with attributes, when present) */ private void setKeyEntry(String alias, Key key, KeyStore.PasswordProtection passwordProtection, Certificate[] chain, Set<KeyStore.Entry.Attribute> attributes) throws KeyStoreException { try { Entry entry; if (key instanceof PrivateKey) { // Check that all the certs are X.509 certs checkX509Certs(chain); 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 at alias '" + alias + "'"); } // Encrypt the private key keyEntry.protectedPrivKey = encryptPrivateKey(key.getEncoded(), passwordProtection); } 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(), passwordProtection); if (debug != null) { debug.println("Setting a protected secret key at alias '" + alias + "'"); } secretKeyCount++; entry = keyEntry; } else { throw new KeyStoreException("Unsupported Key type"); } entry.attributes = new HashSet<>(); if (attributes != null) { entry.attributes.addAll(attributes); } // set the keyId to current date entry.keyId = ("Time " + (entry.date).getTime()).getBytes(UTF_8); // set the alias entry.alias = alias.toLowerCase(Locale.ENGLISH); // add the entry entries.put(alias.toLowerCase(Locale.ENGLISH), entry); } catch (KeyStoreException kse) { throw kse; } catch (Exception nsae) { throw new KeyStoreException("Key protection" + " algorithm not found: " + nsae, nsae); } }
Assigns the given key (that has already been protected) to the given alias.

If the protected key is of type java.security.PrivateKey, it must be accompanied by a certificate chain certifying the corresponding public key. If the underlying keystore implementation is of type jks, key must be encoded as an EncryptedPrivateKeyInfo as defined in the PKCS #8 standard.

If the given alias already exists, the keystore information associated with it is overridden by the given key (and possibly certificate chain).

Params:
  • alias – the alias name
  • key – the key (in protected format) to be associated with the alias
  • chain – the certificate chain for the corresponding public key (only useful if the protected key is of type java.security.PrivateKey).
Throws:
/** * Assigns the given key (that has already been protected) to the given * alias. * * <p>If the protected key is of type * <code>java.security.PrivateKey</code>, it must be accompanied by a * certificate chain certifying the corresponding public key. If the * underlying keystore implementation is of type <code>jks</code>, * <code>key</code> must be encoded as an * <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard. * * <p>If the given alias already exists, the keystore information * associated with it is overridden by the given key (and possibly * certificate chain). * * @param alias the alias name * @param key the key (in protected format) to be associated with the alias * @param chain the certificate chain for the corresponding public * key (only useful if the protected key is of type * <code>java.security.PrivateKey</code>). * * @exception KeyStoreException if this operation fails. */
public synchronized void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException { // Check that all the certs are X.509 certs checkX509Certs(chain); // Private key must be encoded as EncryptedPrivateKeyInfo // as defined in PKCS#8 try { new EncryptedPrivateKeyInfo(key); } catch (IOException ioe) { throw new KeyStoreException("Private key is not stored" + " as PKCS#8 EncryptedPrivateKeyInfo: " + ioe, ioe); } PrivateKeyEntry entry = new PrivateKeyEntry(); entry.date = new Date(); if (debug != null) { debug.println("Setting a protected private key at alias '" + alias + "'"); } // set the keyId to current date entry.keyId = ("Time " + (entry.date).getTime()).getBytes(UTF_8); // set the alias entry.alias = alias.toLowerCase(Locale.ENGLISH); entry.protectedPrivKey = key.clone(); if (chain != null) { // validate cert-chain if ((chain.length > 1) && (!validateChain(chain))) { throw new KeyStoreException("Certificate chain is " + "not valid"); } 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); } /* * Generate random salt */ private byte[] getSalt() { // Generate a random salt. byte[] salt = new byte[SALT_LEN]; if (random == null) { random = new SecureRandom(); } random.nextBytes(salt); return salt; } /* * Generate PBE Algorithm Parameters */ private AlgorithmParameters getPBEAlgorithmParameters( String algorithm, int iterationCount) throws IOException { AlgorithmParameters algParams = null; // create PBE parameters from salt and iteration count PBEParameterSpec paramSpec = new PBEParameterSpec(getSalt(), iterationCount); try { algParams = AlgorithmParameters.getInstance(algorithm); algParams.init(paramSpec); } catch (Exception e) { throw new IOException("getPBEAlgorithmParameters failed: " + e.getMessage(), e); } return algParams; } /* * parse Algorithm Parameters */ private AlgorithmParameters parseAlgParameters(ObjectIdentifier algorithm, DerInputStream in) throws IOException { AlgorithmParameters algParams = null; try { DerValue params; if (in.available() == 0) { params = null; } else { params = in.getDerValue(); if (params.tag == DerValue.tag_Null) { params = null; } } if (params != null) { if (algorithm.equals(pbes2_OID)) { algParams = AlgorithmParameters.getInstance("PBES2"); } else { algParams = AlgorithmParameters.getInstance("PBE"); } algParams.init(params.toByteArray()); } } catch (Exception e) { throw new IOException("parseAlgParameters failed: " + e.getMessage(), e); } return algParams; } /* * Generate PBE key */ private SecretKey getPBEKey(char[] password) throws IOException { SecretKey skey = null; try { 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); } return skey; } /* * Encrypt private key or secret key using Password-based encryption (PBE) * as defined in PKCS#5. * * NOTE: By default, pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is * used to derive the key and IV. * * @return encrypted private key or secret key encoded as * EncryptedPrivateKeyInfo */ private byte[] encryptPrivateKey(byte[] data, KeyStore.PasswordProtection passwordProtection) throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException { byte[] key = null; try { String algorithm; AlgorithmParameters algParams; AlgorithmId algid; // Initialize PBE algorithm and parameters algorithm = passwordProtection.getProtectionAlgorithm(); if (algorithm != null) { AlgorithmParameterSpec algParamSpec = passwordProtection.getProtectionParameters(); if (algParamSpec != null) { algParams = AlgorithmParameters.getInstance(algorithm); algParams.init(algParamSpec); } else { algParams = getPBEAlgorithmParameters(algorithm, defaultKeyPbeIterationCount()); } } else { // Check default key protection algorithm for PKCS12 keystores algorithm = defaultKeyProtectionAlgorithm(); algParams = getPBEAlgorithmParameters(algorithm, defaultKeyPbeIterationCount()); } ObjectIdentifier pbeOID = mapPBEAlgorithmToOID(algorithm); if (pbeOID == null) { throw new IOException("PBE algorithm '" + algorithm + " 'is not supported for key entry protection"); } // Use JCE SecretKey skey = getPBEKey(passwordProtection.getPassword()); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); byte[] encryptedKey = cipher.doFinal(data); algid = new AlgorithmId(pbeOID, cipher.getParameters()); if (debug != null) { debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + ")"); } // wrap encrypted private key in EncryptedPrivateKeyInfo // as defined in PKCS#8 EncryptedPrivateKeyInfo encrInfo = new EncryptedPrivateKeyInfo(algid, encryptedKey); key = encrInfo.getEncoded(); } catch (Exception e) { UnrecoverableKeyException uke = new UnrecoverableKeyException("Encrypt Private Key failed: " + e.getMessage()); uke.initCause(e); throw uke; } return key; } /* * Map a PBE algorithm name onto its object identifier */ private static ObjectIdentifier mapPBEAlgorithmToOID(String algorithm) throws NoSuchAlgorithmException { // Check for PBES2 algorithms if (algorithm.toLowerCase(Locale.ENGLISH).startsWith("pbewithhmacsha")) { return pbes2_OID; } return AlgorithmId.get(algorithm).getOID(); } /* * Map a PBE algorithm parameters onto its algorithm name */ private static String mapPBEParamsToAlgorithm(ObjectIdentifier algorithm, AlgorithmParameters algParams) throws NoSuchAlgorithmException { // Check for PBES2 algorithms if (algorithm.equals(pbes2_OID) && algParams != null) { return algParams.toString(); } return new AlgorithmId(algorithm).getName(); }
Assigns the given certificate to the given alias.

If the given alias already exists in this keystore and identifies a trusted certificate entry, the certificate associated with it is overridden by the given certificate.

Params:
  • alias – the alias name
  • cert – the certificate
Throws:
  • KeyStoreException – if the given alias already exists and does not identify a trusted certificate entry, or this operation fails for some other reason.
/** * Assigns the given certificate to the given alias. * * <p>If the given alias already exists in this keystore and identifies a * <i>trusted certificate entry</i>, the certificate associated with it is * overridden by the given certificate. * * @param alias the alias name * @param cert the certificate * * @exception KeyStoreException if the given alias already exists and does * 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 { setCertEntry(alias, cert, null); } /* * Sets a trusted cert entry (with attributes, when present) */ private void setCertEntry(String alias, Certificate cert, Set<KeyStore.Entry.Attribute> attributes) throws KeyStoreException { // Check that the cert is an X.509 cert if (cert != null && (!(cert instanceof X509Certificate))) { throw new KeyStoreException( "Only X.509 certificates are supported - rejecting class: " + cert.getClass().getName()); } Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); if (entry != null && entry instanceof KeyEntry) { throw new KeyStoreException("Cannot overwrite own certificate"); } CertEntry certEntry = new CertEntry((X509Certificate) cert, null, alias, AnyUsage, attributes); certificateCount++; entries.put(alias.toLowerCase(Locale.ENGLISH), certEntry); if (debug != null) { debug.println("Setting a trusted certificate at alias '" + alias + "'"); } }
Deletes the entry identified by the given alias from this keystore.
Params:
  • alias – the alias name
Throws:
/** * Deletes the entry identified by the given alias from this keystore. * * @param alias the alias name * * @exception KeyStoreException if the entry cannot be removed. */
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)); }
Lists all the alias names of this keystore.
Returns:enumeration of the alias names
/** * Lists all the alias names of this keystore. * * @return enumeration of the alias names */
public Enumeration<String> engineAliases() { return Collections.enumeration(entries.keySet()); }
Checks if the given alias exists in this keystore.
Params:
  • alias – the alias name
Returns:true if the alias exists, false otherwise
/** * Checks if the given alias exists in this keystore. * * @param alias the alias name * * @return true if the alias exists, false otherwise */
public boolean engineContainsAlias(String alias) { return entries.containsKey(alias.toLowerCase(Locale.ENGLISH)); }
Retrieves the number of entries in this keystore.
Returns:the number of entries in this keystore
/** * Retrieves the number of entries in this keystore. * * @return the number of entries in this keystore */
public int engineSize() { return entries.size(); }
Returns true if the entry identified by the given alias is a key entry, and false otherwise.
Returns:true if the entry identified by the given alias is a key entry, false otherwise.
/** * Returns true if the entry identified by the given alias is a * <i>key entry</i>, and false otherwise. * * @return true if the entry identified by the given alias is a * <i>key entry</i>, false otherwise. */
public boolean engineIsKeyEntry(String alias) { Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); if (entry != null && entry instanceof KeyEntry) { return true; } else { return false; } }
Returns true if the entry identified by the given alias is a trusted certificate entry, and false otherwise.
Returns:true if the entry identified by the given alias is a trusted certificate entry, false otherwise.
/** * Returns true if the entry identified by the given alias is a * <i>trusted certificate entry</i>, and false otherwise. * * @return true if the entry identified by the given alias is a * <i>trusted certificate entry</i>, false otherwise. */
public boolean engineIsCertificateEntry(String alias) { Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); if (entry != null && entry instanceof CertEntry && ((CertEntry) entry).trustedKeyUsage != null) { return true; } else { return false; } }
Determines if the keystore Entry for the specified alias is an instance or subclass of the specified entryClass.
Params:
  • alias – the alias name
  • entryClass – the entry class
Returns:true if the keystore Entry for the specified alias is an instance or subclass of the specified entryClass, false otherwise
Since:1.5
/** * Determines if the keystore {@code Entry} for the specified * {@code alias} is an instance or subclass of the specified * {@code entryClass}. * * @param alias the alias name * @param entryClass the entry class * * @return true if the keystore {@code Entry} for the specified * {@code alias} is an instance or subclass of the * specified {@code entryClass}, false otherwise * * @since 1.5 */
@Override public boolean engineEntryInstanceOf(String alias, Class<? extends KeyStore.Entry> entryClass) { if (entryClass == KeyStore.TrustedCertificateEntry.class) { return engineIsCertificateEntry(alias); } Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); if (entryClass == KeyStore.PrivateKeyEntry.class) { return (entry != null && entry instanceof PrivateKeyEntry); } if (entryClass == KeyStore.SecretKeyEntry.class) { return (entry != null && entry instanceof SecretKeyEntry); } return false; }
Returns the (alias) name of the first keystore entry whose certificate matches the given certificate.

This method attempts to match the given certificate with each keystore entry. If the entry being considered is a trusted certificate entry, the given certificate is compared to that entry's certificate. If the entry being considered is a key entry, the given certificate is compared to the first element of that entry's certificate chain (if a chain exists).

Params:
  • cert – the certificate to match with.
Returns:the (alias) name of the first entry with matching certificate, or null if no such entry exists in this keystore.
/** * Returns the (alias) name of the first keystore entry whose certificate * matches the given certificate. * * <p>This method attempts to match the given certificate with each * keystore entry. If the entry being considered * is a <i>trusted certificate entry</i>, the given certificate is * compared to that entry's certificate. If the entry being considered is * a <i>key entry</i>, the given certificate is compared to the first * element of that entry's certificate chain (if a chain exists). * * @param cert the certificate to match with. * * @return the (alias) name of the first entry with matching certificate, * or null if no such entry exists in this keystore. */
public String engineGetCertificateAlias(Certificate cert) { Certificate certElem = null; for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) { String alias = e.nextElement(); 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 != null && certElem.equals(cert)) { return alias; } } return null; }
Stores this keystore to the given output stream, and protects its integrity with the given password.
Params:
  • stream – the output stream to which this keystore is written.
  • password – the password to generate the keystore integrity check
Throws:
/** * Stores this keystore to the given output stream, and protects its * integrity with the given password. * * @param stream the output stream to which this keystore is written. * @param password the password to generate the keystore integrity check * * @exception IOException if there was an I/O problem with data * @exception NoSuchAlgorithmException if the appropriate data integrity * algorithm could not be found * @exception CertificateException if any of the certificates included in * the keystore data could not be stored */
public synchronized void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { // -- Create PFX DerOutputStream pfx = new DerOutputStream(); // PFX version (always write the latest version) DerOutputStream version = new DerOutputStream(); version.putInteger(VERSION_3); byte[] pfxVersion = version.toByteArray(); pfx.write(pfxVersion); // -- Create AuthSafe DerOutputStream authSafe = new DerOutputStream(); // -- Create ContentInfos DerOutputStream authSafeContentInfo = new DerOutputStream(); // -- create safeContent Data ContentInfo if (privateKeyCount > 0 || secretKeyCount > 0) { if (debug != null) { debug.println("Storing " + (privateKeyCount + secretKeyCount) + " protected key(s) in a PKCS#7 data"); } byte[] safeContentData = createSafeContent(); ContentInfo dataContentInfo = new ContentInfo(safeContentData); dataContentInfo.encode(authSafeContentInfo); } // -- create EncryptedContentInfo if (certificateCount > 0) { if (certProtectionAlgorithm == null) { certProtectionAlgorithm = defaultCertProtectionAlgorithm(); } if (certPbeIterationCount < 0) { certPbeIterationCount = defaultCertPbeIterationCount(); } if (debug != null) { debug.println("Storing " + certificateCount + " certificate(s) in a PKCS#7 encryptedData"); } byte[] encrData = createEncryptedData(password); if (!certProtectionAlgorithm.equalsIgnoreCase("NONE")) { ContentInfo encrContentInfo = new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID, new DerValue(encrData)); encrContentInfo.encode(authSafeContentInfo); } else { ContentInfo dataContentInfo = new ContentInfo(encrData); dataContentInfo.encode(authSafeContentInfo); } } // wrap as SequenceOf ContentInfos DerOutputStream cInfo = new DerOutputStream(); cInfo.write(DerValue.tag_SequenceOf, authSafeContentInfo); byte[] authenticatedSafe = cInfo.toByteArray(); // Create Encapsulated ContentInfo ContentInfo contentInfo = new ContentInfo(authenticatedSafe); contentInfo.encode(authSafe); byte[] authSafeData = authSafe.toByteArray(); pfx.write(authSafeData); // -- MAC if (macAlgorithm == null) { macAlgorithm = defaultMacAlgorithm(); } if (macIterationCount < 0) { macIterationCount = defaultMacIterationCount(); } if (!macAlgorithm.equalsIgnoreCase("NONE")) { byte[] macData = calculateMac(password, authenticatedSafe); pfx.write(macData); } // write PFX to output stream DerOutputStream pfxout = new DerOutputStream(); pfxout.write(DerValue.tag_Sequence, pfx); byte[] pfxData = pfxout.toByteArray(); stream.write(pfxData); stream.flush(); }
Gets a KeyStore.Entry for the specified alias with the specified protection parameter.
Params:
  • alias – get the KeyStore.Entry for this alias
  • protParam – the ProtectionParameter used to protect the Entry, which may be null
Throws:
Returns:the KeyStore.Entry for the specified alias, or null if there is no such entry
Since:1.5
/** * 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 KeyStore.Entry under the specified alias. The specified protection parameter is used to protect the Entry.

If an entry already exists for the specified alias, it is overridden.

Params:
  • alias – save the KeyStore.Entry under this alias
  • entry – the Entry to save
  • protParam – the ProtectionParameter used to protect the Entry, which may be null
Throws:
Since:1.5
/** * 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; } /* * Calculate MAC using HMAC algorithm (required for password integrity) * * Hash-based MAC algorithm combines secret key with message digest to * create a message authentication code (MAC) */ private byte[] calculateMac(char[] passwd, byte[] data) throws IOException { byte[] mData = null; String algName = macAlgorithm.substring(7); try { // Generate a random salt. byte[] salt = getSalt(); // generate MAC (MAC key is generated within JCE) Mac m = Mac.getInstance(macAlgorithm); PBEParameterSpec params = new PBEParameterSpec(salt, macIterationCount); SecretKey key = getPBEKey(passwd); m.init(key, params); m.update(data); byte[] macResult = m.doFinal(); // encode as MacData MacData macData = new MacData(algName, macResult, salt, macIterationCount); DerOutputStream bytes = new DerOutputStream(); bytes.write(macData.getEncoded()); mData = bytes.toByteArray(); } catch (Exception e) { throw new IOException("calculateMac failed: " + e, e); } return mData; } /* * Validate Certificate Chain */ private boolean validateChain(Certificate[] certChain) { for (int i = 0; i < certChain.length-1; i++) { X500Principal issuerDN = ((X509Certificate)certChain[i]).getIssuerX500Principal(); X500Principal subjectDN = ((X509Certificate)certChain[i+1]).getSubjectX500Principal(); if (!(issuerDN.equals(subjectDN))) return false; } // Check for loops in the chain. If there are repeated certs, // the Set of certs in the chain will contain fewer certs than // the chain Set<Certificate> set = new HashSet<>(Arrays.asList(certChain)); return set.size() == certChain.length; } /* * Check that all the certificates are X.509 certificates */ private static void checkX509Certs(Certificate[] certs) throws KeyStoreException { if (certs != null) { for (Certificate cert : certs) { if (!(cert instanceof X509Certificate)) { throw new KeyStoreException( "Only X.509 certificates are supported - " + "rejecting class: " + cert.getClass().getName()); } } } } /* * 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 * of Certificate. In end-entity certificates, localKeyID should be * unique, and the corresponding private key should have the same * localKeyID. For trusted CA certs in the cert-chain, localKeyID * attribute is not required, hence most vendors don't include it. * NSS/Netscape require it to be unique or null, where as IE/OpenSSL * ignore it. * * Here is a list of pkcs12 attribute values in CertBags. * * PKCS12 Attribute NSS/Netscape IE OpenSSL J2SE * -------------------------------------------------------------- * LocalKeyId * (In EE cert only, * NULL in CA certs) true true true true * * 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, 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[] friendlyName = null; byte[] trustedKeyUsage = null; // return null if all three attributes are null if ((alias == null) && (keyId == null) && (trustedKeyUsage == null)) { return null; } // SafeBag Attributes DerOutputStream bagAttrs = new DerOutputStream(); // Encode the friendlyname oid. if (alias != null) { DerOutputStream bagAttr1 = new DerOutputStream(); bagAttr1.putOID(PKCS9FriendlyName_OID); DerOutputStream bagAttrContent1 = new DerOutputStream(); DerOutputStream bagAttrValue1 = new DerOutputStream(); bagAttrContent1.putBMPString(alias); bagAttr1.write(DerValue.tag_Set, bagAttrContent1); bagAttrValue1.write(DerValue.tag_Sequence, bagAttr1); friendlyName = bagAttrValue1.toByteArray(); } // Encode the localkeyId oid. if (keyId != null) { DerOutputStream bagAttr2 = new DerOutputStream(); bagAttr2.putOID(PKCS9LocalKeyId_OID); DerOutputStream bagAttrContent2 = new DerOutputStream(); DerOutputStream bagAttrValue2 = new DerOutputStream(); bagAttrContent2.putOctetString(keyId); bagAttr2.write(DerValue.tag_Set, bagAttrContent2); bagAttrValue2.write(DerValue.tag_Sequence, bagAttr2); 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); } if (localKeyID != null) { attrs.write(localKeyID); } if (trustedKeyUsage != null) { attrs.write(trustedKeyUsage); } if (attributes != null) { for (KeyStore.Entry.Attribute attribute : attributes) { String attributeName = attribute.getName(); // skip friendlyName, localKeyId and trustedKeyUsage if (CORE_ATTRIBUTES[0].equals(attributeName) || CORE_ATTRIBUTES[1].equals(attributeName) || CORE_ATTRIBUTES[2].equals(attributeName)) { continue; } 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. * Each CertBag may include pkcs12 attributes * (see comments in getBagAttributes) */ private byte[] createEncryptedData(char[] password) throws CertificateException, IOException { DerOutputStream out = new DerOutputStream(); for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) { String alias = e.nextElement(); Entry entry = entries.get(alias); // certificate chain Certificate[] certs; if (entry instanceof PrivateKeyEntry) { PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; if (keyEntry.chain != null) { certs = keyEntry.chain; } else { certs = new Certificate[0]; } } else if (entry instanceof CertEntry) { certs = new Certificate[]{((CertEntry) entry).cert}; } else { certs = new Certificate[0]; } for (int i = 0; i < certs.length; i++) { // create SafeBag of Type CertBag DerOutputStream safeBag = new DerOutputStream(); safeBag.putOID(CertBag_OID); // create a CertBag DerOutputStream certBag = new DerOutputStream(); certBag.putOID(PKCS9CertType_OID); // write encoded certs in a context-specific tag DerOutputStream certValue = new DerOutputStream(); X509Certificate cert = (X509Certificate) certs[i]; certValue.putOctetString(cert.getEncoded()); certBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 0), certValue); // wrap CertBag in a Sequence DerOutputStream certout = new DerOutputStream(); certout.write(DerValue.tag_Sequence, certBag); byte[] certBagValue = certout.toByteArray(); // Wrap the CertBag encoding in a context-specific tag. DerOutputStream bagValue = new DerOutputStream(); bagValue.write(certBagValue); // write SafeBag Value safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 0), bagValue); // write SafeBag Attributes // All Certs should have a unique friendlyName. // This change is made to meet NSS requirements. byte[] bagAttrs = null; if (i == 0) { // Only End-Entity Cert should have a localKeyId. 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 // This change is made to meet NSS/Netscape requirements. // NSS pkcs12 library requires trusted CA certs in the // certificate chain to have unique or null localKeyID. // However, IE/OpenSSL do not impose this restriction. bagAttrs = getBagAttributes( cert.getSubjectX500Principal().getName(), null, entry.attributes); } if (bagAttrs != null) { safeBag.write(bagAttrs); } // wrap as Sequence out.write(DerValue.tag_Sequence, safeBag); } // for cert-chain } // wrap as SequenceOf SafeBag DerOutputStream safeBagValue = new DerOutputStream(); safeBagValue.write(DerValue.tag_SequenceOf, out); byte[] safeBagData = safeBagValue.toByteArray(); // encrypt the content (EncryptedContentInfo) if (!certProtectionAlgorithm.equalsIgnoreCase("NONE")) { byte[] encrContentInfo = encryptContent(safeBagData, password); // -- SEQUENCE of EncryptedData DerOutputStream encrData = new DerOutputStream(); DerOutputStream encrDataContent = new DerOutputStream(); encrData.putInteger(0); encrData.write(encrContentInfo); encrDataContent.write(DerValue.tag_Sequence, encrData); return encrDataContent.toByteArray(); } else { return safeBagData; } } /* * 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) */ private byte[] createSafeContent() throws CertificateException, IOException { DerOutputStream out = new DerOutputStream(); for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) { String alias = e.nextElement(); 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); // get the encrypted private key byte[] encrBytes = ((PrivateKeyEntry)keyEntry).protectedPrivKey; EncryptedPrivateKeyInfo encrInfo = null; try { encrInfo = new EncryptedPrivateKeyInfo(encrBytes); } catch (IOException ioe) { throw new IOException("Private key not stored as " + "PKCS#8 EncryptedPrivateKeyInfo" + ioe.getMessage()); } // Wrap the EncryptedPrivateKeyInfo in a context-specific tag. DerOutputStream bagValue = new DerOutputStream(); bagValue.write(encrInfo.getEncoded()); safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 0), bagValue); // DER encode the secret key } else if (keyEntry instanceof SecretKeyEntry) { // Create SafeBag of type SecretBag safeBag.putOID(SecretBag_OID); // Create a SecretBag DerOutputStream secretBag = new DerOutputStream(); secretBag.putOID(PKCS8ShroudedKeyBag_OID); // Write secret key in a context-specific tag DerOutputStream secretKeyValue = new DerOutputStream(); secretKeyValue.putOctetString( ((SecretKeyEntry) keyEntry).protectedSecretKey); secretBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 0), secretKeyValue); // Wrap SecretBag in a Sequence DerOutputStream secretBagSeq = new DerOutputStream(); secretBagSeq.write(DerValue.tag_Sequence, secretBag); byte[] secretBagValue = secretBagSeq.toByteArray(); // Wrap the secret bag in a context-specific tag. DerOutputStream bagValue = new DerOutputStream(); bagValue.write(secretBagValue); // Write SafeBag value safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 0), bagValue); } else { continue; // skip this entry } // write SafeBag Attributes byte[] bagAttrs = getBagAttributes(alias, entry.keyId, entry.attributes); safeBag.write(bagAttrs); // wrap as Sequence out.write(DerValue.tag_Sequence, safeBag); } // wrap as Sequence DerOutputStream safeBagValue = new DerOutputStream(); safeBagValue.write(DerValue.tag_Sequence, out); return safeBagValue.toByteArray(); } /* * Encrypt the contents using Password-based (PBE) encryption * as defined in PKCS #5. * * NOTE: Currently pbeWithSHAAnd40BiteRC2-CBC algorithmID is used * to derive the key and IV. * * @return encrypted contents encoded as EncryptedContentInfo */ private byte[] encryptContent(byte[] data, char[] password) throws IOException { byte[] encryptedData = null; try { // create AlgorithmParameters AlgorithmParameters algParams = getPBEAlgorithmParameters( certProtectionAlgorithm, certPbeIterationCount); DerOutputStream bytes = new DerOutputStream(); // Use JCE SecretKey skey = getPBEKey(password); Cipher cipher = Cipher.getInstance(certProtectionAlgorithm); cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); encryptedData = cipher.doFinal(data); AlgorithmId algId = new AlgorithmId( mapPBEAlgorithmToOID(certProtectionAlgorithm), cipher.getParameters()); // cipher.getParameters() now has IV algId.encode(bytes); byte[] encodedAlgId = bytes.toByteArray(); if (debug != null) { debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + ")"); } // create EncryptedContentInfo DerOutputStream bytes2 = new DerOutputStream(); bytes2.putOID(ContentInfo.DATA_OID); bytes2.write(encodedAlgId); // Wrap encrypted data in a context-specific tag. DerOutputStream tmpout2 = new DerOutputStream(); tmpout2.putOctetString(encryptedData); bytes2.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT, false, (byte) 0), tmpout2); // wrap EncryptedContentInfo in a Sequence DerOutputStream out = new DerOutputStream(); out.write(DerValue.tag_Sequence, bytes2); return out.toByteArray(); } catch (IOException ioe) { throw ioe; } catch (Exception e) { throw new IOException("Failed to encrypt" + " safe contents entry: " + e, e); } }
Loads the keystore from the given input stream.

If a password is given, it is used to check the integrity of the keystore data. Otherwise, the integrity of the keystore is not checked.

Params:
  • stream – the input stream from which the keystore is loaded
  • password – the (optional) password used to check the integrity of the keystore.
Throws:
/** * Loads the keystore from the given input stream. * * <p>If a password is given, it is used to check the integrity of the * keystore data. Otherwise, the integrity of the keystore is not checked. * * @param stream the input stream from which the keystore is loaded * @param password the (optional) password used to check the integrity of * the keystore. * * @exception IOException if there is an I/O or format problem with the * keystore data * @exception NoSuchAlgorithmException if the algorithm used to check * the integrity of the keystore cannot be found * @exception CertificateException if any of the certificates in the * keystore could not be loaded */
public synchronized void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { // Reset config when loading a different keystore. certProtectionAlgorithm = null; certPbeIterationCount = -1; macAlgorithm = null; macIterationCount = -1; if (stream == null) return; // reset the counter counter = 0; DerValue val = new DerValue(stream); DerInputStream s = val.toDerInputStream(); int version = s.getInteger(); if (version != VERSION_3) { throw new IOException("PKCS12 keystore not in version 3 format"); } entries.clear(); /* * Read the authSafe. */ byte[] authSafeData; ContentInfo authSafe = new ContentInfo(s); ObjectIdentifier contentType = authSafe.getContentType(); if (contentType.equals(ContentInfo.DATA_OID)) { authSafeData = authSafe.getData(); } else /* signed data */ { throw new IOException("public key protected PKCS12 not supported"); } DerInputStream as = new DerInputStream(authSafeData); DerValue[] safeContentsArray = as.getSequence(2); int count = safeContentsArray.length; // reset the counters at the start privateKeyCount = 0; secretKeyCount = 0; certificateCount = 0; boolean seeEncBag = false; /* * Spin over the ContentInfos. */ for (int i = 0; i < count; i++) { ContentInfo safeContents; DerInputStream sci; byte[] eAlgId = null; sci = new DerInputStream(safeContentsArray[i].toByteArray()); safeContents = new ContentInfo(sci); contentType = safeContents.getContentType(); if (contentType.equals(ContentInfo.DATA_OID)) { if (debug != null) { debug.println("Loading PKCS#7 data"); } loadSafeContents(new DerInputStream(safeContents.getData())); } else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) { if (password == null) { if (debug != null) { debug.println("Warning: skipping PKCS#7 encryptedData" + " - no password was supplied"); } // No password to decrypt ENCRYPTED_DATA_OID. *Skip it*. // This means user will see a PrivateKeyEntry without // certificates and a whole TrustedCertificateEntry will // be lost. This is not a perfect solution but alternative // solutions are more disruptive: // // We cannot just fail, since KeyStore.load(is, null) // has been known to never fail because of a null password. // // We cannot just throw away the whole PrivateKeyEntry, // this is too silent and no one will notice anything. // // We also cannot fail when getCertificate() on such a // PrivateKeyEntry is called, since the method has not // specified this behavior. continue; } DerInputStream edi = safeContents.getContent().toDerInputStream(); int edVersion = edi.getInteger(); DerValue[] seq = edi.getSequence(3); if (seq.length != 3) { // We require the encryptedContent field, even though // it is optional throw new IOException("Invalid length for EncryptedContentInfo"); } ObjectIdentifier edContentType = seq[0].getOID(); eAlgId = seq[1].toByteArray(); if (!seq[2].isContextSpecific((byte)0)) { throw new IOException("unsupported encrypted content type " + seq[2].tag); } byte newTag = DerValue.tag_OctetString; if (seq[2].isConstructed()) newTag |= 0x20; seq[2].resetTag(newTag); byte[] rawData = seq[2].getOctetString(); // parse Algorithm parameters DerInputStream in = seq[1].toDerInputStream(); ObjectIdentifier algOid = in.getOID(); AlgorithmParameters algParams = parseAlgParameters(algOid, in); PBEParameterSpec pbeSpec; int ic = 0; if (algParams != null) { try { pbeSpec = algParams.getParameterSpec(PBEParameterSpec.class); } catch (InvalidParameterSpecException ipse) { throw new IOException( "Invalid PBE algorithm parameters"); } ic = pbeSpec.getIterationCount(); if (ic > MAX_ITERATION_COUNT) { throw new IOException("cert PBE iteration count too large"); } certProtectionAlgorithm = mapPBEParamsToAlgorithm(algOid, algParams); certPbeIterationCount = ic; seeEncBag = true; } if (debug != null) { debug.println("Loading PKCS#7 encryptedData " + "(" + mapPBEParamsToAlgorithm(algOid, algParams) + " iterations: " + ic + ")"); } try { RetryWithZero.run(pass -> { // Use JCE SecretKey skey = getPBEKey(pass); Cipher cipher = Cipher.getInstance( mapPBEParamsToAlgorithm(algOid, algParams)); cipher.init(Cipher.DECRYPT_MODE, skey, algParams); loadSafeContents(new DerInputStream(cipher.doFinal(rawData))); return null; }, password); } catch (Exception e) { throw new IOException("keystore password was incorrect", new UnrecoverableKeyException( "failed to decrypt safe contents entry: " + e)); } } else { throw new IOException("public key protected PKCS12" + " not supported"); } } // No ENCRYPTED_DATA_OID but see certificate. Must be passwordless. if (!seeEncBag && certificateCount > 0) { certProtectionAlgorithm = "NONE"; } // The MacData is optional. if (s.available() > 0) { // If there is no password, we cannot fail. KeyStore.load(is, null) // has been known to never fail because of a null password. if (password != null) { MacData macData = new MacData(s); int ic = macData.getIterations(); try { if (ic > MAX_ITERATION_COUNT) { throw new InvalidAlgorithmParameterException( "MAC iteration count too large: " + ic); } String algName = macData.getDigestAlgName().toUpperCase(Locale.ENGLISH); // Change SHA-1 to SHA1 algName = algName.replace("-", ""); macAlgorithm = "HmacPBE" + algName; macIterationCount = ic; // generate MAC (MAC key is created within JCE) Mac m = Mac.getInstance(macAlgorithm); PBEParameterSpec params = new PBEParameterSpec(macData.getSalt(), ic); RetryWithZero.run(pass -> { SecretKey key = getPBEKey(pass); m.init(key, params); m.update(authSafeData); byte[] macResult = m.doFinal(); if (debug != null) { debug.println("Checking keystore integrity " + "(" + m.getAlgorithm() + " iterations: " + ic + ")"); } if (!MessageDigest.isEqual(macData.getDigest(), macResult)) { throw new UnrecoverableKeyException("Failed PKCS12" + " integrity checking"); } return (Void) null; }, password); } catch (Exception e) { throw new IOException("Integrity check failed: " + e, e); } } } else { macAlgorithm = "NONE"; } /* * Match up private keys with certificate chains. */ PrivateKeyEntry[] list = keyList.toArray(new PrivateKeyEntry[keyList.size()]); for (int m = 0; m < list.length; m++) { PrivateKeyEntry entry = list[m]; if (entry.keyId != null) { ArrayList<X509Certificate> chain = new ArrayList<X509Certificate>(); X509Certificate cert = findMatchedCertificate(entry); mainloop: while (cert != null) { // Check for loops in the certificate chain if (!chain.isEmpty()) { for (X509Certificate chainCert : chain) { if (cert.equals(chainCert)) { if (debug != null) { debug.println("Loop detected in " + "certificate chain. Skip adding " + "repeated cert to chain. Subject: " + cert.getSubjectX500Principal() .toString()); } break mainloop; } } } chain.add(cert); if (KeyStoreUtil.isSelfSigned(cert)) { break; } cert = findIssuer(cert); } /* Update existing KeyEntry in entries table */ if (chain.size() > 0) { entry.chain = chain.toArray(new Certificate[chain.size()]); } else { // Remove private key entries where there is no associated // certs. Most likely the keystore is loaded with a null // password. entries.remove(entry); } } } if (debug != null) { debug.println("PKCS12KeyStore load: private key count: " + privateKeyCount + ". secret key count: " + secretKeyCount + ". certificate count: " + certificateCount); } certEntries.clear(); allCerts.clear(); keyList.clear(); }
Find the issuer of input in allCerts. If the input has an AuthorityKeyIdentifier extension and the keyId inside matches the keyId of the SubjectKeyIdentifier of a cert. This cert is returned. Otherwise, a cert whose subjectDN is the same as the input's issuerDN is returned.
Params:
  • input – the input certificate
Returns:the isssuer, or null if none matches
/** * Find the issuer of input in allCerts. If the input has an * AuthorityKeyIdentifier extension and the keyId inside matches * the keyId of the SubjectKeyIdentifier of a cert. This cert is * returned. Otherwise, a cert whose subjectDN is the same as the * input's issuerDN is returned. * * @param input the input certificate * @return the isssuer, or null if none matches */
private X509Certificate findIssuer(X509Certificate input) { X509Certificate fallback = null; // the DN match X500Principal issuerPrinc = input.getIssuerX500Principal(); // AuthorityKeyIdentifier value encoded as an OCTET STRING byte[] issuerIdExtension = input.getExtensionValue("2.5.29.35"); byte[] issuerId = null; if (issuerIdExtension != null) { try { issuerId = new AuthorityKeyIdentifierExtension( false, new DerValue(issuerIdExtension).getOctetString()) .getEncodedKeyIdentifier(); } catch (IOException e) { // ignored. issuerId is still null } } for (X509Certificate cert : allCerts) { if (cert.getSubjectX500Principal().equals(issuerPrinc)) { if (issuerId != null) { // SubjectKeyIdentifier value encoded as an OCTET STRING byte[] subjectIdExtension = cert.getExtensionValue("2.5.29.14"); byte[] subjectId = null; if (subjectIdExtension != null) { try { subjectId = new DerValue(subjectIdExtension) .getOctetString(); } catch (IOException e) { // ignored. issuerId is still null } } if (subjectId != null) { if (Arrays.equals(issuerId, subjectId)) { // keyId exact match! return cert; } else { // Different keyId. Not a fallback. continue; } } else { // A DN match with no subjectId fallback = cert; } } else { // if there is no issuerId, return the 1st DN match return cert; } } } return fallback; }
Returns if a pkcs12 file is password-less. This means no cert is encrypted and there is no Mac. Please note that the private key can be encrypted. This is a simplified version of KeyStoreSpi.engineLoad that only looks at the ContentInfo types.
Params:
  • f – the pkcs12 file
Throws:
Returns:if it's password-less
/** * Returns if a pkcs12 file is password-less. This means no cert is * encrypted and there is no Mac. Please note that the private key * can be encrypted. * * This is a simplified version of {@link #engineLoad} that only looks * at the ContentInfo types. * * @param f the pkcs12 file * @return if it's password-less * @throws IOException */
public static boolean isPasswordless(File f) throws IOException { try (FileInputStream stream = new FileInputStream(f)) { DerValue val = new DerValue(stream); DerInputStream s = val.toDerInputStream(); s.getInteger(); // skip version ContentInfo authSafe = new ContentInfo(s); DerInputStream as = new DerInputStream(authSafe.getData()); for (DerValue seq : as.getSequence(2)) { DerInputStream sci = new DerInputStream(seq.toByteArray()); ContentInfo safeContents = new ContentInfo(sci); if (safeContents.getContentType() .equals(ContentInfo.ENCRYPTED_DATA_OID)) { // Certificate encrypted return false; } } if (s.available() > 0) { // The MacData exists. return false; } } return true; }
Locates a matched CertEntry from certEntries, and returns its cert.
Params:
  • entry – the KeyEntry to match
Returns:a certificate, null if not found
/** * Locates a matched CertEntry from certEntries, and returns its cert. * @param entry the KeyEntry to match * @return a certificate, null if not found */
private X509Certificate findMatchedCertificate(PrivateKeyEntry entry) { CertEntry keyIdMatch = null; CertEntry aliasMatch = null; for (CertEntry ce: certEntries) { if (Arrays.equals(entry.keyId, ce.keyId)) { keyIdMatch = ce; if (entry.alias.equalsIgnoreCase(ce.alias)) { // Full match! return ce.cert; } } else if (entry.alias.equalsIgnoreCase(ce.alias)) { aliasMatch = ce; } } // keyId match first, for compatibility if (keyIdMatch != null) return keyIdMatch.cert; else if (aliasMatch != null) return aliasMatch.cert; else return null; } private void loadSafeContents(DerInputStream stream) throws IOException, NoSuchAlgorithmException, CertificateException { DerValue[] safeBags = stream.getSequence(2); int count = safeBags.length; /* * Spin over the SafeBags. */ for (int i = 0; i < count; i++) { ObjectIdentifier bagId; DerInputStream sbi; DerValue bagValue; Object bagItem = null; sbi = safeBags[i].toDerInputStream(); bagId = sbi.getOID(); bagValue = sbi.getDerValue(); if (!bagValue.isContextSpecific((byte)0)) { throw new IOException("unsupported PKCS12 bag value type " + bagValue.tag); } bagValue = bagValue.data.getDerValue(); if (bagId.equals(PKCS8ShroudedKeyBag_OID)) { PrivateKeyEntry kEntry = new PrivateKeyEntry(); kEntry.protectedPrivKey = bagValue.toByteArray(); bagItem = kEntry; privateKeyCount++; } else if (bagId.equals(CertBag_OID)) { DerInputStream cs = new DerInputStream(bagValue.toByteArray()); DerValue[] certValues = cs.getSequence(2); if (certValues.length != 2) { throw new IOException("Invalid length for CertBag"); } ObjectIdentifier certId = certValues[0].getOID(); if (!certValues[1].isContextSpecific((byte)0)) { throw new IOException("unsupported PKCS12 cert value type " + certValues[1].tag); } DerValue certValue = certValues[1].data.getDerValue(); CertificateFactory cf = CertificateFactory.getInstance("X509"); X509Certificate cert; cert = (X509Certificate)cf.generateCertificate (new ByteArrayInputStream(certValue.getOctetString())); bagItem = cert; certificateCount++; } else if (bagId.equals(SecretBag_OID)) { DerInputStream ss = new DerInputStream(bagValue.toByteArray()); DerValue[] secretValues = ss.getSequence(2); if (secretValues.length != 2) { throw new IOException("Invalid length for SecretBag"); } 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; secretKeyCount++; } else { if (debug != null) { debug.println("Unsupported PKCS12 bag type: " + bagId); } } DerValue[] attrSet; try { attrSet = sbi.getSet(3); } catch (IOException e) { // entry does not have attributes // Note: CA certs can have no attributes // OpenSSL generates pkcs12 with no attr for CA certs. attrSet = null; } 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++) { byte[] encoded = attrSet[j].toByteArray(); DerInputStream as = new DerInputStream(encoded); DerValue[] attrSeq = as.getSequence(2); if (attrSeq.length != 2) { throw new IOException("Invalid length for Attribute"); } ObjectIdentifier attrId = attrSeq[0].getOID(); DerInputStream vs = new DerInputStream(attrSeq[1].toByteArray()); DerValue[] valSet; try { valSet = vs.getSet(1); } catch (IOException e) { throw new IOException("Attribute " + attrId + " should have a value " + e.getMessage()); } if (attrId.equals(PKCS9FriendlyName_OID)) { alias = valSet[0].getBMPString(); } else if (attrId.equals(PKCS9LocalKeyId_OID)) { keyId = valSet[0].getOctetString(); } else if (attrId.equals(TrustedKeyUsage_OID)) { trustedKeyUsage = new ObjectIdentifier[valSet.length]; for (int k = 0; k < valSet.length; k++) { trustedKeyUsage[k] = valSet[k].getOID(); } } else { attributes.add(new PKCS12Attribute(encoded)); } } } /* * As per PKCS12 v1.0 friendlyname (alias) and localKeyId (keyId) * are optional PKCS12 bagAttributes. But entries in the keyStore * are identified by their alias. Hence we need to have an * Unfriendlyname in the alias, if alias is null. The keyId * attribute is required to match the private key with the * certificate. If we get a bagItem of type KeyEntry with a * null keyId, we should skip it entirely. */ if (bagItem instanceof KeyEntry) { KeyEntry entry = (KeyEntry)bagItem; if (keyId == null) { if (bagItem instanceof PrivateKeyEntry) { // 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(UTF_8); } else { continue; } } else { // keyId in a SecretKeyEntry is not significant keyId = "00".getBytes(UTF_8); } } entry.keyId = keyId; // restore date if it exists String keyIdStr = new String(keyId, UTF_8); Date date = null; if (keyIdStr.startsWith("Time ")) { try { date = new Date( Long.parseLong(keyIdStr.substring(5))); } catch (Exception e) { date = null; } } if (date == null) { date = new Date(); } entry.date = date; if (bagItem instanceof PrivateKeyEntry) { keyList.add((PrivateKeyEntry) entry); } if (entry.attributes == null) { entry.attributes = new HashSet<>(); } entry.attributes.addAll(attributes); 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 // Note: This is a workaround to allow null localKeyID // attribute in pkcs12 with one private key entry and // associated cert-chain if ((keyId == null) && (privateKeyCount == 1)) { // insert localKeyID only for EE cert or self-signed cert if (i == 0) { keyId = "01".getBytes(UTF_8); } } // Trusted certificate if (trustedKeyUsage != null) { if (alias == null) { alias = getUnfriendlyName(); } CertEntry certEntry = new CertEntry(cert, keyId, alias, trustedKeyUsage, attributes); entries.put(alias.toLowerCase(Locale.ENGLISH), certEntry); } else { certEntries.add(new CertEntry(cert, keyId, alias)); } allCerts.add(cert); } } } private String getUnfriendlyName() { counter++; return (String.valueOf(counter)); } /* * PKCS12 permitted first 24 bytes: * * 30 80 02 01 03 30 80 06 09 2A 86 48 86 F7 0D 01 07 01 A0 80 24 80 04 -- * 30 82 -- -- 02 01 03 30 82 -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 8- * 30 -- 02 01 03 30 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 -- 04 -- -- -- * 30 81 -- 02 01 03 30 81 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 81 -- 04 * 30 82 -- -- 02 01 03 30 81 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 81 -- * 30 83 -- -- -- 02 01 03 30 82 -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 * 30 83 -- -- -- 02 01 03 30 83 -- -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 * 30 84 -- -- -- -- 02 01 03 30 83 -- -- -- 06 09 2A 86 48 86 F7 0D 01 07 * 30 84 -- -- -- -- 02 01 03 30 84 -- -- -- -- 06 09 2A 86 48 86 F7 0D 01 */ private static final long[][] PKCS12_HEADER_PATTERNS = { { 0x3080020103308006L, 0x092A864886F70D01L, 0x0701A08024800400L }, { 0x3082000002010330L, 0x82000006092A8648L, 0x86F70D010701A080L }, { 0x3000020103300006L, 0x092A864886F70D01L, 0x0701A00004000000L }, { 0x3081000201033081L, 0x0006092A864886F7L, 0x0D010701A0810004L }, { 0x3082000002010330L, 0x810006092A864886L, 0xF70D010701A08100L }, { 0x3083000000020103L, 0x3082000006092A86L, 0x4886F70D010701A0L }, { 0x3083000000020103L, 0x308300000006092AL, 0x864886F70D010701L }, { 0x3084000000000201L, 0x0330830000000609L, 0x2A864886F70D0107L }, { 0x3084000000000201L, 0x0330840000000006L, 0x092A864886F70D01L } }; private static final long[][] PKCS12_HEADER_MASKS = { { 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFF00L }, { 0xFFFF0000FFFFFFFFL, 0xFF0000FFFFFFFFFFL, 0xFFFFFFFFFFFFFFF0L }, { 0xFF00FFFFFFFF00FFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFF00FF000000L }, { 0xFFFF00FFFFFFFFFFL, 0x00FFFFFFFFFFFFFFL, 0xFFFFFFFFFFFF00FFL }, { 0xFFFF0000FFFFFFFFL, 0xFF00FFFFFFFFFFFFL, 0xFFFFFFFFFFFFFF00L }, { 0xFFFF000000FFFFFFL, 0xFFFF0000FFFFFFFFL, 0xFFFFFFFFFFFFFFFFL }, { 0xFFFF000000FFFFFFL, 0xFFFF000000FFFFFFL, 0xFFFFFFFFFFFFFFFFL }, { 0xFFFF00000000FFFFL, 0xFFFFFF000000FFFFL, 0xFFFFFFFFFFFFFFFFL }, { 0xFFFF00000000FFFFL, 0xFFFFFF00000000FFL, 0xFFFFFFFFFFFFFFFFL } };
Probe the first few bytes of the keystore data stream for a valid PKCS12 keystore encoding.
/** * Probe the first few bytes of the keystore data stream for a valid * PKCS12 keystore encoding. */
@Override public boolean engineProbe(InputStream stream) throws IOException { DataInputStream dataStream; if (stream instanceof DataInputStream) { dataStream = (DataInputStream)stream; } else { dataStream = new DataInputStream(stream); } long firstPeek = dataStream.readLong(); long nextPeek = dataStream.readLong(); long finalPeek = dataStream.readLong(); boolean result = false; for (int i = 0; i < PKCS12_HEADER_PATTERNS.length; i++) { if (PKCS12_HEADER_PATTERNS[i][0] == (firstPeek & PKCS12_HEADER_MASKS[i][0]) && (PKCS12_HEADER_PATTERNS[i][1] == (nextPeek & PKCS12_HEADER_MASKS[i][1])) && (PKCS12_HEADER_PATTERNS[i][2] == (finalPeek & PKCS12_HEADER_MASKS[i][2]))) { result = true; break; } } return result; } // 8076190: Customizing the generation of a PKCS12 keystore private static String defaultCertProtectionAlgorithm() { String result = SecurityProperties.privilegedGetOverridable( "keystore.pkcs12.certProtectionAlgorithm"); return (result != null && !result.isEmpty()) ? result : "PBEWithSHA1AndRC2_40"; } private static int defaultCertPbeIterationCount() { String result = SecurityProperties.privilegedGetOverridable( "keystore.pkcs12.certPbeIterationCount"); return (result != null && !result.isEmpty()) ? string2IC("certPbeIterationCount", result) : 50000; } // Read both "keystore.pkcs12.keyProtectionAlgorithm" and // "keystore.PKCS12.keyProtectionAlgorithm" for compatibility. private static String defaultKeyProtectionAlgorithm() { String result = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { String result; String name1 = "keystore.pkcs12.keyProtectionAlgorithm"; String name2 = "keystore.PKCS12.keyProtectionAlgorithm"; result = System.getProperty(name1); if (result != null) { return result; } result = System.getProperty(name2); if (result != null) { return result; } result = Security.getProperty(name1); if (result != null) { return result; } return Security.getProperty(name2); } }); return (result != null && !result.isEmpty()) ? result : "PBEWithSHA1AndDESede"; } private static int defaultKeyPbeIterationCount() { String result = SecurityProperties.privilegedGetOverridable( "keystore.pkcs12.keyPbeIterationCount"); return (result != null && !result.isEmpty()) ? string2IC("keyPbeIterationCount", result) : 50000; } private static String defaultMacAlgorithm() { String result = SecurityProperties.privilegedGetOverridable( "keystore.pkcs12.macAlgorithm"); return (result != null && !result.isEmpty()) ? result : "HmacPBESHA1"; } private static int defaultMacIterationCount() { String result = SecurityProperties.privilegedGetOverridable( "keystore.pkcs12.macIterationCount"); return (result != null && !result.isEmpty()) ? string2IC("macIterationCount", result) : 100000; } private static int string2IC(String type, String value) { int number; try { number = Integer.parseInt(value); } catch (NumberFormatException e) { throw new IllegalArgumentException("keystore.pkcs12." + type + " is not a number: " + value); } if (number <= 0 || number > MAX_ITERATION_COUNT) { throw new IllegalArgumentException("Invalid keystore.pkcs12." + type + ": " + value); } return number; } }