/* * Copyright (c) 1999, 2016, 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.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; import java.security.AlgorithmParameters; import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import javax.crypto.SecretKeyFactory; import javax.crypto.SecretKey; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.security.auth.DestroyFailedException; import javax.security.auth.x500.X500Principal; import sun.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.x509.AlgorithmId; import sun.security.pkcs.EncryptedPrivateKeyInfo; /** * 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 we support following PBE algorithms: * - pbeWithSHAAnd3KeyTripleDESCBC to encrypt private keys * - pbeWithSHAAnd40BitRC2CBC to encrypt certificates * * 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 KeyProtector * @see java.security.KeyStoreSpi * @see KeyTool * * */ public final class PKCS12KeyStore extends KeyStoreSpi { public static final int VERSION_3 = 3; private static final String[] KEY_PROTECTION_ALGORITHM = { "keystore.pkcs12.keyProtectionAlgorithm", "keystore.PKCS12.keyProtectionAlgorithm" }; // 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 pbeWithSHAAnd40BitRC2CBC[] = {1, 2, 840, 113549, 1, 12, 1, 6}; private static final int pbeWithSHAAnd3KeyTripleDESCBC[] = {1, 2, 840, 113549, 1, 12, 1, 3}; private static final int pbes2[] = {1, 2, 840, 113549, 1, 5, 13}; // TODO: temporary Oracle OID /* * { joint-iso-itu-t(2) country(16) us(840) organization(1) oracle(113894) * jdk(746875) crypto(1) id-at-trustedKeyUsage(1) } */ private static final int TrustedKeyUsage[] = {2, 16, 840, 1, 113894, 746875, 1, 1}; private static final int AnyExtendedKeyUsage[] = {2, 5, 29, 37, 0}; private static ObjectIdentifier PKCS8ShroudedKeyBag_OID; private static ObjectIdentifier CertBag_OID; private static ObjectIdentifier SecretBag_OID; private static ObjectIdentifier PKCS9FriendlyName_OID; private static ObjectIdentifier PKCS9LocalKeyId_OID; private static ObjectIdentifier PKCS9CertType_OID; private static ObjectIdentifier pbeWithSHAAnd40BitRC2CBC_OID; private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID; private static ObjectIdentifier pbes2_OID; private static ObjectIdentifier TrustedKeyUsage_OID; private static ObjectIdentifier[] AnyUsage; private int counter = 0; private static final int iterationCount = 1024; private static final int SALT_LEN = 20; // 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; // 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); pbeWithSHAAnd40BitRC2CBC_OID = new ObjectIdentifier(pbeWithSHAAnd40BitRC2CBC); pbeWithSHAAnd3KeyTripleDESCBC_OID = new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC); pbes2_OID = new ObjectIdentifier(pbes2); TrustedKeyUsage_OID = new ObjectIdentifier(TrustedKeyUsage); AnyUsage = new ObjectIdentifier[]{ new ObjectIdentifier(AnyExtendedKeyUsage)}; } catch (IOException ioe) { // should not happen } } // A keystore entry and associated attributes private static class Entry { Date date; // the creation date of this entry String alias; byte[] keyId; Set attributes; } // A key entry private static class KeyEntry extends Entry { } // A private key entry and its supporting certificate chain private static class PrivateKeyEntry extends KeyEntry { byte[] protectedPrivKey; Certificate chain[]; }; // 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 attributes) { this.date = new Date(); this.cert = cert; this.keyId = keyId; this.alias = alias; this.trustedKeyUsage = trustedKeyUsage; this.attributes = new HashSet<>(); if (attributes != null) { this.attributes.addAll(attributes); } } } /** * Private keys and certificates are stored in a map. * Map entries are keyed by alias names. */ private Map entries = Collections.synchronizedMap(new LinkedHashMap()); private ArrayList keyList = new ArrayList(); private LinkedHashMap certsMap = new LinkedHashMap(); private ArrayList certEntries = new ArrayList(); /** * 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 key entry. * * @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 { byte[] keyInfo; while (true) { try { // Use JCE SecretKey skey = getPBEKey(password); Cipher cipher = Cipher.getInstance( mapPBEParamsToAlgorithm(algOid, algParams)); cipher.init(Cipher.DECRYPT_MODE, skey, algParams); keyInfo = cipher.doFinal(encryptedKey); break; } catch (Exception e) { if (password.length == 0) { // Retry using an empty password // without a NULL terminator. password = new char[1]; continue; } throw e; } } /* * 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); 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 = kfac.generatePrivate(kspec); if (debug != null) { debug.println("Retrieved a protected private key (" + key.getClass().getName() + ") at alias '" + alias + "'"); } // decode secret key } else { byte[] keyBytes = in.getOctetString(); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, keyAlgo); // Special handling required for PBE: needs a PBEKeySpec if (keyAlgo.startsWith("PBE")) { SecretKeyFactory sKeyFactory = SecretKeyFactory.getInstance(keyAlgo); KeySpec pbeKeySpec = sKeyFactory.getKeySpec(secretKeySpec, PBEKeySpec.class); key = sKeyFactory.generateSecret(pbeKeySpec); } else { key = secretKeySpec; } if (debug != null) { debug.println("Retrieved a protected secret key (" + key.getClass().getName() + ") at alias '" + alias + "'"); } } } catch (Exception e) { UnrecoverableKeyException uke = new UnrecoverableKeyException("Get Key failed: " + e.getMessage()); uke.initCause(e); throw uke; } return key; } /** * 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 trusted certificate entry or a * key entry 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. * * @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. * * @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). * * @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 * java.security.PrivateKey). * * @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 attributes) throws KeyStoreException { try { Entry entry; if (key instanceof PrivateKey) { PrivateKeyEntry keyEntry = new PrivateKeyEntry(); keyEntry.date = new Date(); if ((key.getFormat().equals("PKCS#8")) || (key.getFormat().equals("PKCS8"))) { if (debug != null) { debug.println("Setting a protected private key (" + key.getClass().getName() + ") at alias '" + alias + "'"); } // Encrypt the private key 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 (" + key.getClass().getName() + ") at alias '" + alias + "'"); } secretKeyCount++; entry = keyEntry; } else { throw new KeyStoreException("Unsupported Key type"); } entry.attributes = new HashSet<>(); if (attributes != null) { entry.attributes.addAll(attributes); } // set the keyId to current date entry.keyId = ("Time " + (entry.date).getTime()).getBytes("UTF8"); // set the alias entry.alias = alias.toLowerCase(Locale.ENGLISH); // add the entry entries.put(alias.toLowerCase(Locale.ENGLISH), entry); } catch (Exception nsae) { throw new KeyStoreException("Key protection " + " algorithm not found: " + nsae, nsae); } } /** * 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). * * @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 * java.security.PrivateKey). * * @exception KeyStoreException if this operation fails. */ public synchronized void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException { // 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 + "'"); } try { // set the keyId to current date entry.keyId = ("Time " + (entry.date).getTime()).getBytes("UTF8"); } catch (UnsupportedEncodingException ex) { // Won't happen } // 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 getAlgorithmParameters(String algorithm) 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("getAlgorithmParameters 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((Object)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 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 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 = getAlgorithmParameters(algorithm); } } else { // Check default key protection algorithm for PKCS12 keystores algorithm = AccessController.doPrivileged( new PrivilegedAction() { public String run() { String prop = Security.getProperty( KEY_PROTECTION_ALGORITHM[0]); if (prop == null) { prop = Security.getProperty( KEY_PROTECTION_ALGORITHM[1]); } return prop; } }); if (algorithm == null || algorithm.isEmpty()) { algorithm = "PBEWithSHA1AndDESede"; } algParams = getAlgorithmParameters(algorithm); } 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((Object)pbes2_OID) && algParams != null) { return algParams.toString(); } return algorithm.toString(); } /** * 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. * * @param alias the alias name * @param cert the certificate * * @exception KeyStoreException if the given alias already exists and does * not identify a trusted certificate entry, 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 attributes) throws KeyStoreException { 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, certEntry); if (debug != null) { debug.println("Setting a trusted certificate at alias '" + alias + "'"); } } /** * 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. * * @return enumeration of the alias names */ public Enumeration engineAliases() { return Collections.enumeration(entries.keySet()); } /** * 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. * * @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. * * @return true if the entry identified by the given alias is a * key entry, 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. * * @return true if the entry identified by the given alias is a * trusted certificate entry, 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 {@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 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). * * @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 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. * * @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 { // password is mandatory when storing if (password == null) { throw new IllegalArgumentException("password can't be null"); } // -- 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 content-type"); } byte[] safeContentData = createSafeContent(); ContentInfo dataContentInfo = new ContentInfo(safeContentData); dataContentInfo.encode(authSafeContentInfo); } // -- create EncryptedContentInfo if (certificateCount > 0) { if (debug != null) { debug.println("Storing " + certificateCount + " certificate(s) in a PKCS#7 encryptedData content-type"); } byte[] encrData = createEncryptedData(password); ContentInfo encrContentInfo = new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID, new DerValue(encrData)); encrContentInfo.encode(authSafeContentInfo); } // 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 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. * * @param alias get the KeyStore.Entry for this alias * @param protParam the ProtectionParameter * used to protect the Entry, * which may be null * * @return the KeyStore.Entry for the specified alias, * or null if there is no such entry * * @exception KeyStoreException if the operation failed * @exception NoSuchAlgorithmException if the algorithm for recovering the * entry cannot be found * @exception UnrecoverableEntryException if the specified * protParam were insufficient or invalid * @exception UnrecoverableKeyException if the entry is a * PrivateKeyEntry or SecretKeyEntry * and the specified protParam does not contain * the information needed to recover the key (e.g. wrong password) * * @since 1.5 */ @Override public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter protParam) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { if (!engineContainsAlias(alias)) { return null; } Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); if (protParam == null) { if (engineIsCertificateEntry(alias)) { if (entry instanceof CertEntry && ((CertEntry) entry).trustedKeyUsage != null) { if (debug != null) { debug.println("Retrieved a trusted certificate at " + "alias '" + alias + "'"); } return new KeyStore.TrustedCertificateEntry( ((CertEntry)entry).cert, getAttributes(entry)); } } else { throw new UnrecoverableKeyException ("requested entry requires a password"); } } if (protParam instanceof KeyStore.PasswordProtection) { if (engineIsCertificateEntry(alias)) { throw new UnsupportedOperationException ("trusted certificate entries are not password-protected"); } else if (engineIsKeyEntry(alias)) { KeyStore.PasswordProtection pp = (KeyStore.PasswordProtection)protParam; char[] password = pp.getPassword(); Key key = engineGetKey(alias, password); if (key instanceof PrivateKey) { Certificate[] chain = engineGetCertificateChain(alias); return new KeyStore.PrivateKeyEntry((PrivateKey)key, chain, getAttributes(entry)); } else if (key instanceof SecretKey) { return new KeyStore.SecretKeyEntry((SecretKey)key, getAttributes(entry)); } } else if (!engineIsKeyEntry(alias)) { throw new UnsupportedOperationException ("untrusted certificate entries are not " + "password-protected"); } } throw new UnsupportedOperationException(); } /** * Saves a KeyStore.Entry under the specified alias. * The specified protection parameter is used to protect the * Entry. * *

If an entry already exists for the specified alias, * it is overridden. * * @param alias save the KeyStore.Entry under this alias * @param entry the Entry to save * @param protParam the ProtectionParameter * used to protect the Entry, * which may be null * * @exception KeyStoreException if this operation fails * * @since 1.5 */ @Override public synchronized void engineSetEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam) throws KeyStoreException { // get password if (protParam != null && !(protParam instanceof KeyStore.PasswordProtection)) { throw new KeyStoreException("unsupported protection parameter"); } KeyStore.PasswordProtection pProtect = null; if (protParam != null) { pProtect = (KeyStore.PasswordProtection)protParam; } // set entry if (entry instanceof KeyStore.TrustedCertificateEntry) { if (protParam != null && pProtect.getPassword() != null) { // pre-1.5 style setCertificateEntry did not allow password throw new KeyStoreException ("trusted certificate entries are not password-protected"); } else { KeyStore.TrustedCertificateEntry tce = (KeyStore.TrustedCertificateEntry)entry; setCertEntry(alias, tce.getTrustedCertificate(), tce.getAttributes()); return; } } else if (entry instanceof KeyStore.PrivateKeyEntry) { if (pProtect == null || pProtect.getPassword() == null) { // pre-1.5 style setKeyEntry required password throw new KeyStoreException ("non-null password required to create PrivateKeyEntry"); } else { KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry)entry; setKeyEntry(alias, pke.getPrivateKey(), pProtect, pke.getCertificateChain(), pke.getAttributes()); return; } } else if (entry instanceof KeyStore.SecretKeyEntry) { if (pProtect == null || pProtect.getPassword() == null) { // pre-1.5 style setKeyEntry required password throw new KeyStoreException ("non-null password required to create SecretKeyEntry"); } else { KeyStore.SecretKeyEntry ske = (KeyStore.SecretKeyEntry)entry; setKeyEntry(alias, ske.getSecretKey(), pProtect, (Certificate[])null, ske.getAttributes()); return; } } throw new KeyStoreException ("unsupported entry type: " + entry.getClass().getName()); } /* * Assemble the entry attributes */ private Set getAttributes(Entry entry) { if (entry.attributes == null) { entry.attributes = new HashSet<>(); } // friendlyName entry.attributes.add(new PKCS12Attribute( PKCS9FriendlyName_OID.toString(), entry.alias)); // localKeyID byte[] keyIdValue = entry.keyId; if (keyIdValue != null) { entry.attributes.add(new PKCS12Attribute( PKCS9LocalKeyId_OID.toString(), Debug.toString(keyIdValue))); } // trustedKeyUsage if (entry instanceof CertEntry) { ObjectIdentifier[] trustedKeyUsageValue = ((CertEntry) entry).trustedKeyUsage; if (trustedKeyUsageValue != null) { if (trustedKeyUsageValue.length == 1) { // omit brackets entry.attributes.add(new PKCS12Attribute( TrustedKeyUsage_OID.toString(), trustedKeyUsageValue[0].toString())); } else { // multi-valued entry.attributes.add(new PKCS12Attribute( TrustedKeyUsage_OID.toString(), Arrays.toString(trustedKeyUsageValue))); } } } return entry.attributes; } /* * Generate Hash. */ private byte[] generateHash(byte[] data) throws IOException { byte[] digest = null; try { MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(data); digest = md.digest(); } catch (Exception e) { throw new IOException("generateHash failed: " + e, e); } return digest; } /* * 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 = "SHA1"; try { // Generate a random salt. byte[] salt = getSalt(); // generate MAC (MAC key is generated within JCE) Mac m = Mac.getInstance("HmacPBESHA1"); PBEParameterSpec params = new PBEParameterSpec(salt, iterationCount); 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, iterationCount); 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 set = new HashSet<>(Arrays.asList(certChain)); return set.size() == certChain.length; } /* * 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 attributes) throws IOException { return getBagAttributes(alias, keyId, null, attributes); } private byte[] getBagAttributes(String alias, byte[] keyId, ObjectIdentifier[] trustedUsage, Set attributes) throws IOException { byte[] localKeyID = null; byte[] friendlyName = null; byte[] trustedKeyUsage = null; // return null if 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 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) 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(); } /* * 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 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; // create AlgorithmParameters AlgorithmParameters algParams = getAlgorithmParameters("PBEWithSHA1AndRC2_40"); DerOutputStream bytes = new DerOutputStream(); AlgorithmId algId = new AlgorithmId(pbeWithSHAAnd40BitRC2CBC_OID, algParams); algId.encode(bytes); byte[] encodedAlgId = bytes.toByteArray(); try { // Use JCE SecretKey skey = getPBEKey(password); Cipher cipher = Cipher.getInstance("PBEWithSHA1AndRC2_40"); cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); encryptedData = cipher.doFinal(data); if (debug != null) { debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + ")"); } } catch (Exception e) { throw new IOException("Failed to encrypt" + " safe contents entry: " + e, e); } // 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(); } /** * 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. * * @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 { DataInputStream dis; CertificateFactory cf = null; ByteArrayInputStream bais = null; byte[] encoded = null; 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((Object)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; /* * Spin over the ContentInfos. */ for (int i = 0; i < count; i++) { byte[] safeContentsData; ContentInfo safeContents; DerInputStream sci; byte[] eAlgId = null; sci = new DerInputStream(safeContentsArray[i].toByteArray()); safeContents = new ContentInfo(sci); contentType = safeContents.getContentType(); safeContentsData = null; if (contentType.equals((Object)ContentInfo.DATA_OID)) { if (debug != null) { debug.println("Loading PKCS#7 data content-type"); } safeContentsData = safeContents.getData(); } else if (contentType.equals((Object)ContentInfo.ENCRYPTED_DATA_OID)) { if (password == null) { if (debug != null) { debug.println("Warning: skipping PKCS#7 encryptedData" + " content-type - no password was supplied"); } continue; } if (debug != null) { debug.println("Loading PKCS#7 encryptedData content-type"); } DerInputStream edi = safeContents.getContent().toDerInputStream(); int edVersion = edi.getInteger(); DerValue[] seq = edi.getSequence(2); ObjectIdentifier edContentType = seq[0].getOID(); eAlgId = seq[1].toByteArray(); if (!seq[2].isContextSpecific((byte)0)) { throw new IOException("encrypted content not present!"); } byte newTag = DerValue.tag_OctetString; if (seq[2].isConstructed()) newTag |= 0x20; seq[2].resetTag(newTag); safeContentsData = seq[2].getOctetString(); // parse Algorithm parameters DerInputStream in = seq[1].toDerInputStream(); ObjectIdentifier algOid = in.getOID(); AlgorithmParameters algParams = parseAlgParameters(algOid, in); while (true) { try { // Use JCE SecretKey skey = getPBEKey(password); Cipher cipher = Cipher.getInstance(algOid.toString()); cipher.init(Cipher.DECRYPT_MODE, skey, algParams); safeContentsData = cipher.doFinal(safeContentsData); break; } catch (Exception e) { if (password.length == 0) { // Retry using an empty password // without a NULL terminator. password = new char[1]; continue; } 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"); } DerInputStream sc = new DerInputStream(safeContentsData); loadSafeContents(sc, password); } // The MacData is optional. if (password != null && s.available() > 0) { MacData macData = new MacData(s); try { String algName = macData.getDigestAlgName().toUpperCase(Locale.ENGLISH); // Change SHA-1 to SHA1 algName = algName.replace("-", ""); // generate MAC (MAC key is created within JCE) Mac m = Mac.getInstance("HmacPBE" + algName); PBEParameterSpec params = new PBEParameterSpec(macData.getSalt(), macData.getIterations()); SecretKey key = getPBEKey(password); m.init(key, params); m.update(authSafeData); byte[] macResult = m.doFinal(); if (debug != null) { debug.println("Checking keystore integrity " + "(MAC algorithm: " + m.getAlgorithm() + ")"); } if (!MessageDigest.isEqual(macData.getDigest(), macResult)) { throw new UnrecoverableKeyException("Failed PKCS12" + " integrity checking"); } } catch (Exception e) { throw new IOException("Integrity check failed: " + e, e); } } /* * 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 chain = new ArrayList(); 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); X500Principal issuerDN = cert.getIssuerX500Principal(); if (issuerDN.equals(cert.getSubjectX500Principal())) { break; } cert = certsMap.get(issuerDN); } /* Update existing KeyEntry in entries table */ if (chain.size() > 0) entry.chain = chain.toArray(new Certificate[chain.size()]); } } if (debug != null) { if (privateKeyCount > 0) { debug.println("Loaded " + privateKeyCount + " protected private key(s)"); } if (secretKeyCount > 0) { debug.println("Loaded " + secretKeyCount + " protected secret key(s)"); } if (certificateCount > 0) { debug.println("Loaded " + certificateCount + " certificate(s)"); } } certEntries.clear(); certsMap.clear(); keyList.clear(); } /** * 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, char[] password) 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((Object)PKCS8ShroudedKeyBag_OID)) { PrivateKeyEntry kEntry = new PrivateKeyEntry(); kEntry.protectedPrivKey = bagValue.toByteArray(); bagItem = kEntry; privateKeyCount++; } else if (bagId.equals((Object)CertBag_OID)) { DerInputStream cs = new DerInputStream(bagValue.toByteArray()); DerValue[] certValues = cs.getSequence(2); 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((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; 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 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); 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((Object)PKCS9FriendlyName_OID)) { alias = valSet[0].getBMPString(); } else if (attrId.equals((Object)PKCS9LocalKeyId_OID)) { keyId = valSet[0].getOctetString(); } else if (attrId.equals((Object)TrustedKeyUsage_OID)) { trustedKeyUsage = new ObjectIdentifier[valSet.length]; for (int k = 0; k < valSet.length; k++) { trustedKeyUsage[k] = valSet[k].getOID(); } } else { 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 (bagItem instanceof PrivateKeyEntry) { if (keyId == null) { // Insert a localKeyID for the privateKey // Note: This is a workaround to allow null localKeyID // attribute in pkcs12 with one private key entry and // associated cert-chain if (privateKeyCount == 1) { keyId = "01".getBytes("UTF8"); } else { continue; } } } entry.keyId = keyId; // restore date if it exists String keyIdStr = new String(keyId, "UTF8"); 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("UTF8"); } } // 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)); } X500Principal subjectDN = cert.getSubjectX500Principal(); if (subjectDN != null) { if (!certsMap.containsKey(subjectDN)) { certsMap.put(subjectDN, cert); } } } } } private String getUnfriendlyName() { counter++; return (String.valueOf(counter)); } }