| /* |
| * Copyright (c) 1999, 2017, 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 java.security.InvalidAlgorithmParameterException; |
| import java.security.InvalidKeyException; |
| 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; |
| import sun.security.provider.JavaKeyStore.JKS; |
| import sun.security.util.KeyStoreDelegator; |
| |
| |
| /** |
| * 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 { |
| |
| // 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 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<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); |
| } |
| } |
| } |
| |
| /** |
| * 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 LinkedHashMap<X500Principal, X509Certificate> certsMap = |
| new LinkedHashMap<X500Principal, X509Certificate>(); |
| private ArrayList<CertEntry> certEntries = new ArrayList<CertEntry>(); |
| |
| /** |
| * 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 { |
| 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 <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. |
| * |
| * <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. |
| * |
| * @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. |
| * |
| * <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 (" + |
| 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. |
| * |
| * <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 + "'"); |
| } |
| |
| 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(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<String>() { |
| 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(pbes2_OID) && algParams != null) { |
| return algParams.toString(); |
| } |
| return algorithm.toString(); |
| } |
| |
| /** |
| * 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. |
| * |
| * @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<String> 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 |
| * <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 |
| * <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 {@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. |
| * |
| * <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. |
| * |
| * @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 <code>KeyStore.Entry</code> for the specified alias |
| * with the specified protection parameter. |
| * |
| * @param alias get the <code>KeyStore.Entry</code> for this alias |
| * @param protParam the <code>ProtectionParameter</code> |
| * used to protect the <code>Entry</code>, |
| * which may be <code>null</code> |
| * |
| * @return the <code>KeyStore.Entry</code> for the specified alias, |
| * or <code>null</code> if there is no such entry |
| * |
| * @exception KeyStoreException if the operation failed |
| * @exception NoSuchAlgorithmException if the algorithm for recovering the |
| * entry cannot be found |
| * @exception UnrecoverableEntryException if the specified |
| * <code>protParam</code> were insufficient or invalid |
| * @exception UnrecoverableKeyException if the entry is a |
| * <code>PrivateKeyEntry</code> or <code>SecretKeyEntry</code> |
| * and the specified <code>protParam</code> does not contain |
| * the information needed to recover the key (e.g. wrong password) |
| * |
| * @since 1.5 |
| */ |
| @Override |
| public KeyStore.Entry engineGetEntry(String alias, |
| KeyStore.ProtectionParameter protParam) |
| throws KeyStoreException, NoSuchAlgorithmException, |
| UnrecoverableEntryException { |
| |
| if (!engineContainsAlias(alias)) { |
| return null; |
| } |
| |
| Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
| if (protParam == null) { |
| if (engineIsCertificateEntry(alias)) { |
| if (entry instanceof CertEntry && |
| ((CertEntry) entry).trustedKeyUsage != null) { |
| |
| if (debug != null) { |
| debug.println("Retrieved a trusted certificate at " + |
| "alias '" + alias + "'"); |
| } |
| |
| return new KeyStore.TrustedCertificateEntry( |
| ((CertEntry)entry).cert, getAttributes(entry)); |
| } |
| } else { |
| throw new UnrecoverableKeyException |
| ("requested entry requires a password"); |
| } |
| } |
| |
| if (protParam instanceof KeyStore.PasswordProtection) { |
| if (engineIsCertificateEntry(alias)) { |
| throw new UnsupportedOperationException |
| ("trusted certificate entries are not password-protected"); |
| } else if (engineIsKeyEntry(alias)) { |
| KeyStore.PasswordProtection pp = |
| (KeyStore.PasswordProtection)protParam; |
| char[] password = pp.getPassword(); |
| |
| Key key = engineGetKey(alias, password); |
| if (key instanceof PrivateKey) { |
| Certificate[] chain = engineGetCertificateChain(alias); |
| |
| return new KeyStore.PrivateKeyEntry((PrivateKey)key, chain, |
| getAttributes(entry)); |
| |
| } else if (key instanceof SecretKey) { |
| |
| return new KeyStore.SecretKeyEntry((SecretKey)key, |
| getAttributes(entry)); |
| } |
| } else if (!engineIsKeyEntry(alias)) { |
| throw new UnsupportedOperationException |
| ("untrusted certificate entries are not " + |
| "password-protected"); |
| } |
| } |
| |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Saves a <code>KeyStore.Entry</code> under the specified alias. |
| * The specified protection parameter is used to protect the |
| * <code>Entry</code>. |
| * |
| * <p> If an entry already exists for the specified alias, |
| * it is overridden. |
| * |
| * @param alias save the <code>KeyStore.Entry</code> under this alias |
| * @param entry the <code>Entry</code> to save |
| * @param protParam the <code>ProtectionParameter</code> |
| * used to protect the <code>Entry</code>, |
| * which may be <code>null</code> |
| * |
| * @exception KeyStoreException if this operation fails |
| * |
| * @since 1.5 |
| */ |
| @Override |
| public synchronized void engineSetEntry(String alias, KeyStore.Entry entry, |
| KeyStore.ProtectionParameter protParam) throws KeyStoreException { |
| |
| // get password |
| if (protParam != null && |
| !(protParam instanceof KeyStore.PasswordProtection)) { |
| throw new KeyStoreException("unsupported protection parameter"); |
| } |
| KeyStore.PasswordProtection pProtect = null; |
| if (protParam != null) { |
| pProtect = (KeyStore.PasswordProtection)protParam; |
| } |
| |
| // set entry |
| if (entry instanceof KeyStore.TrustedCertificateEntry) { |
| if (protParam != null && pProtect.getPassword() != null) { |
| // pre-1.5 style setCertificateEntry did not allow password |
| throw new KeyStoreException |
| ("trusted certificate entries are not password-protected"); |
| } else { |
| KeyStore.TrustedCertificateEntry tce = |
| (KeyStore.TrustedCertificateEntry)entry; |
| setCertEntry(alias, tce.getTrustedCertificate(), |
| tce.getAttributes()); |
| |
| return; |
| } |
| } else if (entry instanceof KeyStore.PrivateKeyEntry) { |
| if (pProtect == null || pProtect.getPassword() == null) { |
| // pre-1.5 style setKeyEntry required password |
| throw new KeyStoreException |
| ("non-null password required to create PrivateKeyEntry"); |
| } else { |
| KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry)entry; |
| setKeyEntry(alias, pke.getPrivateKey(), pProtect, |
| pke.getCertificateChain(), pke.getAttributes()); |
| |
| return; |
| } |
| } else if (entry instanceof KeyStore.SecretKeyEntry) { |
| if (pProtect == null || pProtect.getPassword() == null) { |
| // pre-1.5 style setKeyEntry required password |
| throw new KeyStoreException |
| ("non-null password required to create SecretKeyEntry"); |
| } else { |
| KeyStore.SecretKeyEntry ske = (KeyStore.SecretKeyEntry)entry; |
| setKeyEntry(alias, ske.getSecretKey(), pProtect, |
| (Certificate[])null, ske.getAttributes()); |
| |
| return; |
| } |
| } |
| |
| throw new KeyStoreException |
| ("unsupported entry type: " + entry.getClass().getName()); |
| } |
| |
| /* |
| * Assemble the entry attributes |
| */ |
| private Set<KeyStore.Entry.Attribute> getAttributes(Entry entry) { |
| |
| if (entry.attributes == null) { |
| entry.attributes = new HashSet<>(); |
| } |
| |
| // friendlyName |
| entry.attributes.add(new PKCS12Attribute( |
| PKCS9FriendlyName_OID.toString(), entry.alias)); |
| |
| // localKeyID |
| byte[] keyIdValue = entry.keyId; |
| if (keyIdValue != null) { |
| entry.attributes.add(new PKCS12Attribute( |
| PKCS9LocalKeyId_OID.toString(), Debug.toString(keyIdValue))); |
| } |
| |
| // trustedKeyUsage |
| if (entry instanceof CertEntry) { |
| ObjectIdentifier[] trustedKeyUsageValue = |
| ((CertEntry) entry).trustedKeyUsage; |
| if (trustedKeyUsageValue != null) { |
| if (trustedKeyUsageValue.length == 1) { // omit brackets |
| entry.attributes.add(new PKCS12Attribute( |
| TrustedKeyUsage_OID.toString(), |
| trustedKeyUsageValue[0].toString())); |
| } else { // multi-valued |
| entry.attributes.add(new PKCS12Attribute( |
| TrustedKeyUsage_OID.toString(), |
| Arrays.toString(trustedKeyUsageValue))); |
| } |
| } |
| } |
| |
| return entry.attributes; |
| } |
| |
| /* |
| * Generate Hash. |
| */ |
| 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<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) |
| 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<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; |
| |
| // 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. |
| * |
| * <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 |
| { |
| 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(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(ContentInfo.DATA_OID)) { |
| |
| if (debug != null) { |
| debug.println("Loading PKCS#7 data content-type"); |
| } |
| |
| safeContentsData = safeContents.getData(); |
| } else if (contentType.equals(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<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); |
| 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(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); |
| 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); |
| 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); |
| 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 (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)); |
| } |
| |
| /* |
| * PKCS12 permitted first 24 bytes: |
| * |
| * 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 = { |
| { 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 = { |
| { 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. |
| */ |
| @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; |
| } |
| } |