Path: blob/master/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
41159 views
/*1* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.pkcs12;2627import java.io.*;28import java.security.AccessController;29import java.security.MessageDigest;30import java.security.NoSuchAlgorithmException;31import java.security.Key;32import java.security.KeyFactory;33import java.security.KeyStore;34import java.security.KeyStoreSpi;35import java.security.KeyStoreException;36import java.security.PKCS12Attribute;37import java.security.PrivateKey;38import java.security.PrivilegedAction;39import java.security.UnrecoverableEntryException;40import java.security.UnrecoverableKeyException;41import java.security.SecureRandom;42import java.security.Security;43import java.security.cert.Certificate;44import java.security.cert.CertificateFactory;45import java.security.cert.X509Certificate;46import java.security.cert.CertificateException;47import java.security.spec.AlgorithmParameterSpec;48import java.security.spec.InvalidParameterSpecException;49import java.security.spec.KeySpec;50import java.security.spec.PKCS8EncodedKeySpec;51import java.util.*;5253import static java.nio.charset.StandardCharsets.UTF_8;5455import java.security.AlgorithmParameters;56import java.security.InvalidAlgorithmParameterException;57import javax.crypto.spec.PBEParameterSpec;58import javax.crypto.spec.PBEKeySpec;59import javax.crypto.spec.SecretKeySpec;60import javax.crypto.SecretKeyFactory;61import javax.crypto.SecretKey;62import javax.crypto.Cipher;63import javax.crypto.Mac;64import javax.security.auth.DestroyFailedException;65import javax.security.auth.x500.X500Principal;6667import jdk.internal.access.SharedSecrets;68import sun.security.action.GetPropertyAction;69import sun.security.tools.KeyStoreUtil;70import sun.security.util.*;71import sun.security.pkcs.ContentInfo;72import sun.security.x509.AlgorithmId;73import sun.security.pkcs.EncryptedPrivateKeyInfo;74import sun.security.provider.JavaKeyStore.JKS;75import sun.security.x509.AuthorityKeyIdentifierExtension;767778/**79* This class provides the keystore implementation referred to as "PKCS12".80* Implements the PKCS#12 PFX protected using the Password privacy mode.81* The contents are protected using Password integrity mode.82*83* NOTE: In a PKCS12 keystore, entries are identified by the alias, and84* a localKeyId is required to match the private key with the certificate.85* Trusted certificate entries are identified by the presence of an86* trustedKeyUsage attribute.87*88* @author Seema Malkani89* @author Jeff Nisewanger90* @author Jan Luehe91*92* @see java.security.KeyStoreSpi93*/94public final class PKCS12KeyStore extends KeyStoreSpi {9596// Hardcoded defaults. They should be the same with commented out97// lines inside the java.security file.98private static final String DEFAULT_CERT_PBE_ALGORITHM99= "PBEWithHmacSHA256AndAES_256";100private static final String DEFAULT_KEY_PBE_ALGORITHM101= "PBEWithHmacSHA256AndAES_256";102private static final String DEFAULT_MAC_ALGORITHM = "HmacPBESHA256";103private static final int DEFAULT_CERT_PBE_ITERATION_COUNT = 10000;104private static final int DEFAULT_KEY_PBE_ITERATION_COUNT = 10000;105private static final int DEFAULT_MAC_ITERATION_COUNT = 10000;106107// Legacy settings. Used when "keystore.pkcs12.legacy" is set.108private static final String LEGACY_CERT_PBE_ALGORITHM109= "PBEWithSHA1AndRC2_40";110private static final String LEGACY_KEY_PBE_ALGORITHM111= "PBEWithSHA1AndDESede";112private static final String LEGACY_MAC_ALGORITHM = "HmacPBESHA1";113private static final int LEGACY_PBE_ITERATION_COUNT = 50000;114private static final int LEGACY_MAC_ITERATION_COUNT = 100000;115116// Big switch. When this system property is set. Legacy settings117// are used no matter what other keystore.pkcs12.* properties are set.118// Note: This is only a system property, there's no same-name119// security property defined.120private static final String USE_LEGACY_PROP = "keystore.pkcs12.legacy";121122// special PKCS12 keystore that supports PKCS12 and JKS file formats123public static final class DualFormatPKCS12 extends KeyStoreDelegator {124public DualFormatPKCS12() {125super("PKCS12", PKCS12KeyStore.class, "JKS", JKS.class);126}127}128129public static final int VERSION_3 = 3;130131private static final int MAX_ITERATION_COUNT = 5000000;132private static final int SALT_LEN = 20;133134private static final KnownOIDs[] CORE_ATTRIBUTES = {135KnownOIDs.FriendlyName,136KnownOIDs.LocalKeyID,137KnownOIDs.ORACLE_TrustedKeyUsage138};139140private static final Debug debug = Debug.getInstance("pkcs12");141142private static final ObjectIdentifier PKCS8ShroudedKeyBag_OID =143ObjectIdentifier.of(KnownOIDs.PKCS8ShroudedKeyBag);144private static final ObjectIdentifier CertBag_OID =145ObjectIdentifier.of(KnownOIDs.CertBag);146private static final ObjectIdentifier SecretBag_OID =147ObjectIdentifier.of(KnownOIDs.SecretBag);148149private static final ObjectIdentifier PKCS9FriendlyName_OID =150ObjectIdentifier.of(KnownOIDs.FriendlyName);151private static final ObjectIdentifier PKCS9LocalKeyId_OID =152ObjectIdentifier.of(KnownOIDs.LocalKeyID);153private static final ObjectIdentifier PKCS9CertType_OID =154ObjectIdentifier.of(KnownOIDs.CertTypeX509);155private static final ObjectIdentifier pbes2_OID =156ObjectIdentifier.of(KnownOIDs.PBES2);157158/*159* Temporary Oracle OID160*161* {joint-iso-itu-t(2) country(16) us(840) organization(1)162* oracle(113894) jdk(746875) crypto(1) id-at-trustedKeyUsage(1)}163*/164private static final ObjectIdentifier TrustedKeyUsage_OID =165ObjectIdentifier.of(KnownOIDs.ORACLE_TrustedKeyUsage);166167private static final ObjectIdentifier[] AnyUsage = new ObjectIdentifier[] {168ObjectIdentifier.of(KnownOIDs.anyExtendedKeyUsage)169};170171private int counter = 0;172173// private key count174// Note: This is a workaround to allow null localKeyID attribute175// in pkcs12 with one private key entry and associated cert-chain176private int privateKeyCount = 0;177178// secret key count179private int secretKeyCount = 0;180181// certificate count182private int certificateCount = 0;183184// Alg/params used for *this* keystore. Initialized as -1 for ic and185// null for algorithm names. When an existing file is read, they will be186// assigned inside engineLoad() so storing an existing keystore uses the187// old alg/params. This makes sure if a keystore is created password-less188// it will be password-less forever. Otherwise, engineStore() will read189// the default values. These fields are always reset when load() is called.190private String certProtectionAlgorithm = null;191private int certPbeIterationCount = -1;192private String macAlgorithm = null;193private int macIterationCount = -1;194195// the source of randomness196private SecureRandom random;197198// A keystore entry and associated attributes199private static class Entry {200Date date; // the creation date of this entry201String alias;202byte[] keyId;203Set<KeyStore.Entry.Attribute> attributes;204}205206// A key entry207private static class KeyEntry extends Entry {208}209210// A private key entry and its supporting certificate chain211private static class PrivateKeyEntry extends KeyEntry {212byte[] protectedPrivKey;213Certificate[] chain;214};215216// A secret key217private static class SecretKeyEntry extends KeyEntry {218byte[] protectedSecretKey;219};220221// A certificate entry222private static class CertEntry extends Entry {223final X509Certificate cert;224ObjectIdentifier[] trustedKeyUsage;225226CertEntry(X509Certificate cert, byte[] keyId, String alias) {227this(cert, keyId, alias, null, null);228}229230CertEntry(X509Certificate cert, byte[] keyId, String alias,231ObjectIdentifier[] trustedKeyUsage,232Set<? extends KeyStore.Entry.Attribute> attributes) {233this.date = new Date();234this.cert = cert;235this.keyId = keyId;236this.alias = alias;237this.trustedKeyUsage = trustedKeyUsage;238this.attributes = new HashSet<>();239if (attributes != null) {240this.attributes.addAll(attributes);241}242}243}244245/**246* Retries an action with password "\0" if "" fails.247* @param <T> the return type248*/249@FunctionalInterface250private interface RetryWithZero<T> {251252T tryOnce(char[] password) throws Exception;253254static <S> S run(RetryWithZero<S> f, char[] password) throws Exception {255try {256return f.tryOnce(password);257} catch (Exception e) {258if (password.length == 0) {259// Retry using an empty password with a NUL terminator.260if (debug != null) {261debug.println("Retry with a NUL password");262}263return f.tryOnce(new char[1]);264}265throw e;266}267}268}269270/**271* Private keys and certificates are stored in a map.272* Map entries are keyed by alias names.273*/274private Map<String, Entry> entries =275Collections.synchronizedMap(new LinkedHashMap<String, Entry>());276277private ArrayList<KeyEntry> keyList = new ArrayList<KeyEntry>();278private List<X509Certificate> allCerts = new ArrayList<>();279private ArrayList<CertEntry> certEntries = new ArrayList<CertEntry>();280281/**282* Returns the key associated with the given alias, using the given283* password to recover it.284*285* @param alias the alias name286* @param password the password for recovering the key287*288* @return the requested key, or null if the given alias does not exist289* or does not identify a <i>key entry</i>.290*291* @exception NoSuchAlgorithmException if the algorithm for recovering the292* key cannot be found293* @exception UnrecoverableKeyException if the key cannot be recovered294* (e.g., the given password is wrong).295*/296public Key engineGetKey(String alias, char[] password)297throws NoSuchAlgorithmException, UnrecoverableKeyException298{299Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));300Key key = null;301302if (entry == null || (!(entry instanceof KeyEntry))) {303return null;304}305306// get the encoded private key or secret key307byte[] encrBytes = null;308if (entry instanceof PrivateKeyEntry) {309encrBytes = ((PrivateKeyEntry) entry).protectedPrivKey;310} else if (entry instanceof SecretKeyEntry) {311encrBytes = ((SecretKeyEntry) entry).protectedSecretKey;312} else {313throw new UnrecoverableKeyException("Error locating key");314}315316byte[] encryptedKey;317AlgorithmParameters algParams;318ObjectIdentifier algOid;319320try {321// get the encrypted private key322EncryptedPrivateKeyInfo encrInfo =323new EncryptedPrivateKeyInfo(encrBytes);324encryptedKey = encrInfo.getEncryptedData();325326// parse Algorithm parameters327DerValue val = new DerValue(encrInfo.getAlgorithm().encode());328DerInputStream in = val.toDerInputStream();329algOid = in.getOID();330algParams = parseAlgParameters(algOid, in);331332} catch (IOException ioe) {333UnrecoverableKeyException uke =334new UnrecoverableKeyException("Private key not stored as "335+ "PKCS#8 EncryptedPrivateKeyInfo: " + ioe);336uke.initCause(ioe);337throw uke;338}339340try {341PBEParameterSpec pbeSpec;342int ic;343344if (algParams != null) {345try {346pbeSpec =347algParams.getParameterSpec(PBEParameterSpec.class);348} catch (InvalidParameterSpecException ipse) {349throw new IOException("Invalid PBE algorithm parameters");350}351ic = pbeSpec.getIterationCount();352353if (ic > MAX_ITERATION_COUNT) {354throw new IOException("key PBE iteration count too large");355}356} else {357ic = 0;358}359360key = RetryWithZero.run(pass -> {361// Use JCE362Cipher cipher = Cipher.getInstance(363mapPBEParamsToAlgorithm(algOid, algParams));364SecretKey skey = getPBEKey(pass);365try {366cipher.init(Cipher.DECRYPT_MODE, skey, algParams);367} finally {368destroyPBEKey(skey);369}370byte[] keyInfo = cipher.doFinal(encryptedKey);371/*372* Parse the key algorithm and then use a JCA key factory373* to re-create the key.374*/375DerValue val = new DerValue(keyInfo);376try {377DerInputStream in = val.toDerInputStream();378int i = in.getInteger();379DerValue[] value = in.getSequence(2);380if (value.length < 1 || value.length > 2) {381throw new IOException("Invalid length for AlgorithmIdentifier");382}383AlgorithmId algId = new AlgorithmId(value[0].getOID());384String keyAlgo = algId.getName();385386// decode private key387if (entry instanceof PrivateKeyEntry) {388KeyFactory kfac = KeyFactory.getInstance(keyAlgo);389PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(keyInfo);390try {391Key tmp = kfac.generatePrivate(kspec);392393if (debug != null) {394debug.println("Retrieved a protected private key at alias" +395" '" + alias + "' (" +396mapPBEParamsToAlgorithm(algOid, algParams) +397" iterations: " + ic + ")");398}399return tmp;400} finally {401SharedSecrets.getJavaSecuritySpecAccess()402.clearEncodedKeySpec(kspec);403}404// decode secret key405} else {406byte[] keyBytes = in.getOctetString();407SecretKeySpec secretKeySpec =408new SecretKeySpec(keyBytes, keyAlgo);409410try {411// Special handling required for PBE: needs a PBEKeySpec412Key tmp;413if (keyAlgo.startsWith("PBE")) {414SecretKeyFactory sKeyFactory =415SecretKeyFactory.getInstance(keyAlgo);416KeySpec pbeKeySpec =417sKeyFactory.getKeySpec(secretKeySpec, PBEKeySpec.class);418try {419tmp = sKeyFactory.generateSecret(pbeKeySpec);420} finally {421((PBEKeySpec)pbeKeySpec).clearPassword();422SharedSecrets.getJavaxCryptoSpecAccess()423.clearSecretKeySpec(secretKeySpec);424}425} else {426tmp = secretKeySpec;427}428429if (debug != null) {430debug.println("Retrieved a protected secret key at alias " +431"'" + alias + "' (" +432mapPBEParamsToAlgorithm(algOid, algParams) +433" iterations: " + ic + ")");434}435return tmp;436} finally {437Arrays.fill(keyBytes, (byte)0);438}439}440} finally {441val.clear();442Arrays.fill(keyInfo, (byte) 0);443}444}, password);445446} catch (Exception e) {447UnrecoverableKeyException uke =448new UnrecoverableKeyException("Get Key failed: " +449e.getMessage());450uke.initCause(e);451throw uke;452}453return key;454}455456/**457* Returns the certificate chain associated with the given alias.458*459* @param alias the alias name460*461* @return the certificate chain (ordered with the user's certificate first462* and the root certificate authority last), or null if the given alias463* does not exist or does not contain a certificate chain (i.e., the given464* alias identifies either a <i>trusted certificate entry</i> or a465* <i>key entry</i> without a certificate chain).466*/467public Certificate[] engineGetCertificateChain(String alias) {468Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));469if (entry != null && entry instanceof PrivateKeyEntry) {470if (((PrivateKeyEntry) entry).chain == null) {471return null;472} else {473474if (debug != null) {475debug.println("Retrieved a " +476((PrivateKeyEntry) entry).chain.length +477"-certificate chain at alias '" + alias + "'");478}479480return ((PrivateKeyEntry) entry).chain.clone();481}482} else {483return null;484}485}486487/**488* Returns the certificate associated with the given alias.489*490* <p>If the given alias name identifies a491* <i>trusted certificate entry</i>, the certificate associated with that492* entry is returned. If the given alias name identifies a493* <i>key entry</i>, the first element of the certificate chain of that494* entry is returned, or null if that entry does not have a certificate495* chain.496*497* @param alias the alias name498*499* @return the certificate, or null if the given alias does not exist or500* does not contain a certificate.501*/502public Certificate engineGetCertificate(String alias) {503Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));504if (entry == null) {505return null;506}507if (entry instanceof CertEntry &&508((CertEntry) entry).trustedKeyUsage != null) {509510if (debug != null) {511if (Arrays.equals(AnyUsage,512((CertEntry) entry).trustedKeyUsage)) {513debug.println("Retrieved a certificate at alias '" + alias +514"' (trusted for any purpose)");515} else {516debug.println("Retrieved a certificate at alias '" + alias +517"' (trusted for limited purposes)");518}519}520521return ((CertEntry) entry).cert;522523} else if (entry instanceof PrivateKeyEntry) {524if (((PrivateKeyEntry) entry).chain == null) {525return null;526} else {527528if (debug != null) {529debug.println("Retrieved a certificate at alias '" + alias +530"'");531}532533return ((PrivateKeyEntry) entry).chain[0];534}535536} else {537return null;538}539}540541/**542* Returns the creation date of the entry identified by the given alias.543*544* @param alias the alias name545*546* @return the creation date of this entry, or null if the given alias does547* not exist548*/549public Date engineGetCreationDate(String alias) {550Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));551if (entry != null) {552return new Date(entry.date.getTime());553} else {554return null;555}556}557558/**559* Assigns the given key to the given alias, protecting it with the given560* password.561*562* <p>If the given key is of type <code>java.security.PrivateKey</code>,563* it must be accompanied by a certificate chain certifying the564* corresponding public key.565*566* <p>If the given alias already exists, the keystore information567* associated with it is overridden by the given key (and possibly568* certificate chain).569*570* @param alias the alias name571* @param key the key to be associated with the alias572* @param password the password to protect the key573* @param chain the certificate chain for the corresponding public574* key (only required if the given key is of type575* <code>java.security.PrivateKey</code>).576*577* @exception KeyStoreException if the given key cannot be protected, or578* this operation fails for some other reason579*/580public synchronized void engineSetKeyEntry(String alias, Key key,581char[] password, Certificate[] chain)582throws KeyStoreException583{584KeyStore.PasswordProtection passwordProtection =585new KeyStore.PasswordProtection(password);586587try {588setKeyEntry(alias, key, passwordProtection, chain, null);589590} finally {591try {592passwordProtection.destroy();593} catch (DestroyFailedException dfe) {594// ignore595}596}597}598599/*600* Sets a key entry (with attributes, when present)601*/602private void setKeyEntry(String alias, Key key,603KeyStore.PasswordProtection passwordProtection, Certificate[] chain,604Set<KeyStore.Entry.Attribute> attributes)605throws KeyStoreException606{607try {608Entry entry;609610if (key instanceof PrivateKey) {611// Check that all the certs are X.509 certs612checkX509Certs(chain);613614PrivateKeyEntry keyEntry = new PrivateKeyEntry();615keyEntry.date = new Date();616617if ((key.getFormat().equals("PKCS#8")) ||618(key.getFormat().equals("PKCS8"))) {619620if (debug != null) {621debug.println(622"Setting a protected private key at alias '" +623alias + "'");624}625626// Encrypt the private key627byte[] encoded = key.getEncoded();628try {629keyEntry.protectedPrivKey =630encryptPrivateKey(encoded, passwordProtection);631} finally {632if (encoded != null) {633Arrays.fill(encoded, (byte) 0);634}635}636} else {637throw new KeyStoreException("Private key is not encoded" +638"as PKCS#8");639}640641// clone the chain642if (chain != null) {643// validate cert-chain644if ((chain.length > 1) && (!validateChain(chain)))645throw new KeyStoreException("Certificate chain is " +646"not valid");647keyEntry.chain = chain.clone();648certificateCount += chain.length;649650if (debug != null) {651debug.println("Setting a " + chain.length +652"-certificate chain at alias '" + alias + "'");653}654}655privateKeyCount++;656entry = keyEntry;657658} else if (key instanceof SecretKey) {659SecretKeyEntry keyEntry = new SecretKeyEntry();660keyEntry.date = new Date();661662// Encode secret key in a PKCS#8663DerOutputStream secretKeyInfo = new DerOutputStream();664secretKeyInfo.putInteger(0);665AlgorithmId algId = AlgorithmId.get(key.getAlgorithm());666algId.encode(secretKeyInfo);667668byte[] encoded = key.getEncoded();669secretKeyInfo.putOctetString(encoded);670Arrays.fill(encoded, (byte)0);671672DerValue pkcs8 = DerValue.wrap(DerValue.tag_Sequence, secretKeyInfo);673byte[] p8Array = pkcs8.toByteArray();674pkcs8.clear();675try {676// Encrypt the secret key (using same PBE as for private keys)677keyEntry.protectedSecretKey =678encryptPrivateKey(p8Array, passwordProtection);679} finally {680Arrays.fill(p8Array, (byte)0);681}682683if (debug != null) {684debug.println("Setting a protected secret key at alias '" +685alias + "'");686}687secretKeyCount++;688entry = keyEntry;689690} else {691throw new KeyStoreException("Unsupported Key type");692}693694entry.attributes = new HashSet<>();695if (attributes != null) {696entry.attributes.addAll(attributes);697}698// set the keyId to current date699entry.keyId = ("Time " + (entry.date).getTime()).getBytes(UTF_8);700// set the alias701entry.alias = alias.toLowerCase(Locale.ENGLISH);702// add the entry703entries.put(alias.toLowerCase(Locale.ENGLISH), entry);704705} catch (KeyStoreException kse) {706throw kse;707} catch (Exception nsae) {708throw new KeyStoreException("Key protection" +709" algorithm not found: " + nsae, nsae);710}711}712713/**714* Assigns the given key (that has already been protected) to the given715* alias.716*717* <p>If the protected key is of type718* <code>java.security.PrivateKey</code>, it must be accompanied by a719* certificate chain certifying the corresponding public key. If the720* underlying keystore implementation is of type <code>jks</code>,721* <code>key</code> must be encoded as an722* <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.723*724* <p>If the given alias already exists, the keystore information725* associated with it is overridden by the given key (and possibly726* certificate chain).727*728* @param alias the alias name729* @param key the key (in protected format) to be associated with the alias730* @param chain the certificate chain for the corresponding public731* key (only useful if the protected key is of type732* <code>java.security.PrivateKey</code>).733*734* @exception KeyStoreException if this operation fails.735*/736public synchronized void engineSetKeyEntry(String alias, byte[] key,737Certificate[] chain)738throws KeyStoreException739{740// Check that all the certs are X.509 certs741checkX509Certs(chain);742743// Private key must be encoded as EncryptedPrivateKeyInfo744// as defined in PKCS#8745try {746new EncryptedPrivateKeyInfo(key);747} catch (IOException ioe) {748throw new KeyStoreException("Private key is not stored"749+ " as PKCS#8 EncryptedPrivateKeyInfo: " + ioe, ioe);750}751752PrivateKeyEntry entry = new PrivateKeyEntry();753entry.date = new Date();754755if (debug != null) {756debug.println("Setting a protected private key at alias '" +757alias + "'");758}759760// set the keyId to current date761entry.keyId = ("Time " + (entry.date).getTime()).getBytes(UTF_8);762// set the alias763entry.alias = alias.toLowerCase(Locale.ENGLISH);764765entry.protectedPrivKey = key.clone();766if (chain != null) {767// validate cert-chain768if ((chain.length > 1) && (!validateChain(chain))) {769throw new KeyStoreException("Certificate chain is "770+ "not valid");771}772entry.chain = chain.clone();773certificateCount += chain.length;774775if (debug != null) {776debug.println("Setting a " + entry.chain.length +777"-certificate chain at alias '" + alias + "'");778}779}780781// add the entry782privateKeyCount++;783entries.put(alias.toLowerCase(Locale.ENGLISH), entry);784}785786787/*788* Generate random salt789*/790private byte[] getSalt()791{792// Generate a random salt.793byte[] salt = new byte[SALT_LEN];794if (random == null) {795random = new SecureRandom();796}797random.nextBytes(salt);798return salt;799}800801/*802* Generate PBE Algorithm Parameters803*/804private AlgorithmParameters getPBEAlgorithmParameters(805String algorithm, int iterationCount) throws IOException {806AlgorithmParameters algParams;807808byte[] salt = getSalt();809if (KnownOIDs.findMatch(algorithm) == KnownOIDs.PBEWithMD5AndDES) {810// PBES1 scheme such as PBEWithMD5AndDES requires a 8-byte salt811salt = Arrays.copyOf(salt, 8);812}813814// create PBE parameters from salt and iteration count815PBEParameterSpec paramSpec =816new PBEParameterSpec(salt, iterationCount);817try {818algParams = AlgorithmParameters.getInstance(algorithm);819algParams.init(paramSpec);820} catch (Exception e) {821throw new IOException("getPBEAlgorithmParameters failed: " +822e.getMessage(), e);823}824return algParams;825}826827/*828* parse Algorithm Parameters829*/830private AlgorithmParameters parseAlgParameters(ObjectIdentifier algorithm,831DerInputStream in) throws IOException832{833AlgorithmParameters algParams = null;834try {835DerValue params;836if (in.available() == 0) {837params = null;838} else {839params = in.getDerValue();840if (params.tag == DerValue.tag_Null) {841params = null;842}843}844if (params != null) {845if (algorithm.equals(pbes2_OID)) {846algParams = AlgorithmParameters.getInstance("PBES2");847} else {848algParams = AlgorithmParameters.getInstance("PBE");849}850algParams.init(params.toByteArray());851}852} catch (Exception e) {853throw new IOException("parseAlgParameters failed: " +854e.getMessage(), e);855}856return algParams;857}858859/*860* Generate PBE key861*/862private SecretKey getPBEKey(char[] password) throws IOException863{864SecretKey skey = null;865866try {867PBEKeySpec keySpec = new PBEKeySpec(password);868SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE");869skey = skFac.generateSecret(keySpec);870keySpec.clearPassword();871} catch (Exception e) {872throw new IOException("getSecretKey failed: " +873e.getMessage(), e);874}875return skey;876}877878/*879* Destroy the key obtained from getPBEKey().880*/881private void destroyPBEKey(SecretKey key) {882try {883key.destroy();884} catch (DestroyFailedException e) {885// Accept this886}887}888889/*890* Encrypt private key or secret key using Password-based encryption (PBE)891* as defined in PKCS#5.892*893* @return encrypted private key or secret key encoded as894* EncryptedPrivateKeyInfo895*/896private byte[] encryptPrivateKey(byte[] data,897KeyStore.PasswordProtection passwordProtection)898throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException899{900byte[] key = null;901902try {903String algorithm;904AlgorithmParameters algParams;905AlgorithmId algid;906907// Initialize PBE algorithm and parameters908algorithm = passwordProtection.getProtectionAlgorithm();909if (algorithm != null) {910AlgorithmParameterSpec algParamSpec =911passwordProtection.getProtectionParameters();912if (algParamSpec != null) {913algParams = AlgorithmParameters.getInstance(algorithm);914algParams.init(algParamSpec);915} else {916algParams = getPBEAlgorithmParameters(algorithm,917defaultKeyPbeIterationCount());918}919} else {920// Check default key protection algorithm for PKCS12 keystores921algorithm = defaultKeyProtectionAlgorithm();922algParams = getPBEAlgorithmParameters(algorithm,923defaultKeyPbeIterationCount());924}925926ObjectIdentifier pbeOID = mapPBEAlgorithmToOID(algorithm);927if (pbeOID == null) {928throw new IOException("PBE algorithm '" + algorithm +929" 'is not supported for key entry protection");930}931932// Use JCE933Cipher cipher = Cipher.getInstance(algorithm);934SecretKey skey = getPBEKey(passwordProtection.getPassword());935try {936cipher.init(Cipher.ENCRYPT_MODE, skey, algParams);937} finally {938destroyPBEKey(skey);939}940byte[] encryptedKey = cipher.doFinal(data);941algid = new AlgorithmId(pbeOID, cipher.getParameters());942943if (debug != null) {944debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() +945")");946}947948// wrap encrypted private key in EncryptedPrivateKeyInfo949// as defined in PKCS#8950EncryptedPrivateKeyInfo encrInfo =951new EncryptedPrivateKeyInfo(algid, encryptedKey);952key = encrInfo.getEncoded();953} catch (Exception e) {954UnrecoverableKeyException uke =955new UnrecoverableKeyException("Encrypt Private Key failed: "956+ e.getMessage());957uke.initCause(e);958throw uke;959}960961return key;962}963964/*965* Map a PBE algorithm name onto its object identifier966*/967private static ObjectIdentifier mapPBEAlgorithmToOID(String algorithm)968throws NoSuchAlgorithmException {969// Check for PBES2 algorithms970if (algorithm.toLowerCase(Locale.ENGLISH).startsWith("pbewithhmacsha")) {971return pbes2_OID;972}973return AlgorithmId.get(algorithm).getOID();974}975976/*977* Map a PBE algorithm parameters onto its algorithm name978*/979private static String mapPBEParamsToAlgorithm(ObjectIdentifier algorithm,980AlgorithmParameters algParams) throws NoSuchAlgorithmException {981// Check for PBES2 algorithms982if (algorithm.equals(pbes2_OID) && algParams != null) {983return algParams.toString();984}985return new AlgorithmId(algorithm).getName();986}987988/**989* Assigns the given certificate to the given alias.990*991* <p>If the given alias already exists in this keystore and identifies a992* <i>trusted certificate entry</i>, the certificate associated with it is993* overridden by the given certificate.994*995* @param alias the alias name996* @param cert the certificate997*998* @exception KeyStoreException if the given alias already exists and does999* not identify a <i>trusted certificate entry</i>, or this operation fails1000* for some other reason.1001*/1002public synchronized void engineSetCertificateEntry(String alias,1003Certificate cert) throws KeyStoreException1004{1005setCertEntry(alias, cert, null);1006}10071008/*1009* Sets a trusted cert entry (with attributes, when present)1010*/1011private void setCertEntry(String alias, Certificate cert,1012Set<KeyStore.Entry.Attribute> attributes) throws KeyStoreException {10131014// Check that the cert is an X.509 cert1015if (cert != null && (!(cert instanceof X509Certificate))) {1016throw new KeyStoreException(1017"Only X.509 certificates are supported - rejecting class: " +1018cert.getClass().getName());1019}10201021Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));1022if (entry != null && entry instanceof KeyEntry) {1023throw new KeyStoreException("Cannot overwrite own certificate");1024}10251026CertEntry certEntry =1027new CertEntry((X509Certificate) cert, null, alias, AnyUsage,1028attributes);1029certificateCount++;1030entries.put(alias.toLowerCase(Locale.ENGLISH), certEntry);10311032if (debug != null) {1033debug.println("Setting a trusted certificate at alias '" + alias +1034"'");1035}1036}10371038/**1039* Deletes the entry identified by the given alias from this keystore.1040*1041* @param alias the alias name1042*1043* @exception KeyStoreException if the entry cannot be removed.1044*/1045public synchronized void engineDeleteEntry(String alias)1046throws KeyStoreException1047{1048if (debug != null) {1049debug.println("Removing entry at alias '" + alias + "'");1050}10511052Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));1053if (entry instanceof PrivateKeyEntry) {1054PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;1055if (keyEntry.chain != null) {1056certificateCount -= keyEntry.chain.length;1057}1058privateKeyCount--;1059} else if (entry instanceof CertEntry) {1060certificateCount--;1061} else if (entry instanceof SecretKeyEntry) {1062secretKeyCount--;1063}1064entries.remove(alias.toLowerCase(Locale.ENGLISH));1065}10661067/**1068* Lists all the alias names of this keystore.1069*1070* @return enumeration of the alias names1071*/1072public Enumeration<String> engineAliases() {1073return Collections.enumeration(entries.keySet());1074}10751076/**1077* Checks if the given alias exists in this keystore.1078*1079* @param alias the alias name1080*1081* @return true if the alias exists, false otherwise1082*/1083public boolean engineContainsAlias(String alias) {1084return entries.containsKey(alias.toLowerCase(Locale.ENGLISH));1085}10861087/**1088* Retrieves the number of entries in this keystore.1089*1090* @return the number of entries in this keystore1091*/1092public int engineSize() {1093return entries.size();1094}10951096/**1097* Returns true if the entry identified by the given alias is a1098* <i>key entry</i>, and false otherwise.1099*1100* @return true if the entry identified by the given alias is a1101* <i>key entry</i>, false otherwise.1102*/1103public boolean engineIsKeyEntry(String alias) {1104Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));1105if (entry != null && entry instanceof KeyEntry) {1106return true;1107} else {1108return false;1109}1110}11111112/**1113* Returns true if the entry identified by the given alias is a1114* <i>trusted certificate entry</i>, and false otherwise.1115*1116* @return true if the entry identified by the given alias is a1117* <i>trusted certificate entry</i>, false otherwise.1118*/1119public boolean engineIsCertificateEntry(String alias) {1120Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));1121if (entry != null && entry instanceof CertEntry &&1122((CertEntry) entry).trustedKeyUsage != null) {1123return true;1124} else {1125return false;1126}1127}11281129/**1130* Determines if the keystore {@code Entry} for the specified1131* {@code alias} is an instance or subclass of the specified1132* {@code entryClass}.1133*1134* @param alias the alias name1135* @param entryClass the entry class1136*1137* @return true if the keystore {@code Entry} for the specified1138* {@code alias} is an instance or subclass of the1139* specified {@code entryClass}, false otherwise1140*1141* @since 1.51142*/1143@Override1144public boolean1145engineEntryInstanceOf(String alias,1146Class<? extends KeyStore.Entry> entryClass)1147{1148if (entryClass == KeyStore.TrustedCertificateEntry.class) {1149return engineIsCertificateEntry(alias);1150}11511152Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));1153if (entryClass == KeyStore.PrivateKeyEntry.class) {1154return (entry != null && entry instanceof PrivateKeyEntry);1155}1156if (entryClass == KeyStore.SecretKeyEntry.class) {1157return (entry != null && entry instanceof SecretKeyEntry);1158}1159return false;1160}11611162/**1163* Returns the (alias) name of the first keystore entry whose certificate1164* matches the given certificate.1165*1166* <p>This method attempts to match the given certificate with each1167* keystore entry. If the entry being considered1168* is a <i>trusted certificate entry</i>, the given certificate is1169* compared to that entry's certificate. If the entry being considered is1170* a <i>key entry</i>, the given certificate is compared to the first1171* element of that entry's certificate chain (if a chain exists).1172*1173* @param cert the certificate to match with.1174*1175* @return the (alias) name of the first entry with matching certificate,1176* or null if no such entry exists in this keystore.1177*/1178public String engineGetCertificateAlias(Certificate cert) {1179Certificate certElem = null;11801181for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) {1182String alias = e.nextElement();1183Entry entry = entries.get(alias);1184if (entry instanceof PrivateKeyEntry) {1185if (((PrivateKeyEntry) entry).chain != null) {1186certElem = ((PrivateKeyEntry) entry).chain[0];1187}1188} else if (entry instanceof CertEntry &&1189((CertEntry) entry).trustedKeyUsage != null) {1190certElem = ((CertEntry) entry).cert;1191} else {1192continue;1193}1194if (certElem != null && certElem.equals(cert)) {1195return alias;1196}1197}1198return null;1199}12001201/**1202* Stores this keystore to the given output stream, and protects its1203* integrity with the given password.1204*1205* @param stream the output stream to which this keystore is written.1206* @param password the password to generate the keystore integrity check1207*1208* @exception IOException if there was an I/O problem with data1209* @exception NoSuchAlgorithmException if the appropriate data integrity1210* algorithm could not be found1211* @exception CertificateException if any of the certificates included in1212* the keystore data could not be stored1213*/1214public synchronized void engineStore(OutputStream stream, char[] password)1215throws IOException, NoSuchAlgorithmException, CertificateException1216{12171218// -- Create PFX1219DerOutputStream pfx = new DerOutputStream();12201221// PFX version (always write the latest version)1222DerOutputStream version = new DerOutputStream();1223version.putInteger(VERSION_3);1224byte[] pfxVersion = version.toByteArray();1225pfx.write(pfxVersion);12261227// -- Create AuthSafe1228DerOutputStream authSafe = new DerOutputStream();12291230// -- Create ContentInfos1231DerOutputStream authSafeContentInfo = new DerOutputStream();12321233// -- create safeContent Data ContentInfo1234if (privateKeyCount > 0 || secretKeyCount > 0) {12351236if (debug != null) {1237debug.println("Storing " + (privateKeyCount + secretKeyCount) +1238" protected key(s) in a PKCS#7 data");1239}12401241byte[] safeContentData = createSafeContent();1242ContentInfo dataContentInfo = new ContentInfo(safeContentData);1243dataContentInfo.encode(authSafeContentInfo);1244}12451246// -- create EncryptedContentInfo1247if (certificateCount > 0) {12481249if (certProtectionAlgorithm == null) {1250certProtectionAlgorithm = defaultCertProtectionAlgorithm();1251}1252if (certPbeIterationCount < 0) {1253certPbeIterationCount = defaultCertPbeIterationCount();1254}12551256if (debug != null) {1257debug.println("Storing " + certificateCount +1258" certificate(s) in a PKCS#7 encryptedData");1259}12601261byte[] encrData = createEncryptedData(password);1262if (!certProtectionAlgorithm.equalsIgnoreCase("NONE")) {1263ContentInfo encrContentInfo =1264new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID,1265new DerValue(encrData));1266encrContentInfo.encode(authSafeContentInfo);1267} else {1268ContentInfo dataContentInfo = new ContentInfo(encrData);1269dataContentInfo.encode(authSafeContentInfo);1270}1271}12721273// wrap as SequenceOf ContentInfos1274DerOutputStream cInfo = new DerOutputStream();1275cInfo.write(DerValue.tag_SequenceOf, authSafeContentInfo);1276byte[] authenticatedSafe = cInfo.toByteArray();12771278// Create Encapsulated ContentInfo1279ContentInfo contentInfo = new ContentInfo(authenticatedSafe);1280contentInfo.encode(authSafe);1281byte[] authSafeData = authSafe.toByteArray();1282pfx.write(authSafeData);12831284// -- MAC1285if (macAlgorithm == null) {1286macAlgorithm = defaultMacAlgorithm();1287}1288if (macIterationCount < 0) {1289macIterationCount = defaultMacIterationCount();1290}1291if (!macAlgorithm.equalsIgnoreCase("NONE")) {1292byte[] macData = calculateMac(password, authenticatedSafe);1293pfx.write(macData);1294}1295// write PFX to output stream1296DerOutputStream pfxout = new DerOutputStream();1297pfxout.write(DerValue.tag_Sequence, pfx);1298byte[] pfxData = pfxout.toByteArray();1299stream.write(pfxData);1300stream.flush();1301}13021303/**1304* Gets a <code>KeyStore.Entry</code> for the specified alias1305* with the specified protection parameter.1306*1307* @param alias get the <code>KeyStore.Entry</code> for this alias1308* @param protParam the <code>ProtectionParameter</code>1309* used to protect the <code>Entry</code>,1310* which may be <code>null</code>1311*1312* @return the <code>KeyStore.Entry</code> for the specified alias,1313* or <code>null</code> if there is no such entry1314*1315* @exception KeyStoreException if the operation failed1316* @exception NoSuchAlgorithmException if the algorithm for recovering the1317* entry cannot be found1318* @exception UnrecoverableEntryException if the specified1319* <code>protParam</code> were insufficient or invalid1320* @exception UnrecoverableKeyException if the entry is a1321* <code>PrivateKeyEntry</code> or <code>SecretKeyEntry</code>1322* and the specified <code>protParam</code> does not contain1323* the information needed to recover the key (e.g. wrong password)1324*1325* @since 1.51326*/1327@Override1328public KeyStore.Entry engineGetEntry(String alias,1329KeyStore.ProtectionParameter protParam)1330throws KeyStoreException, NoSuchAlgorithmException,1331UnrecoverableEntryException {13321333if (!engineContainsAlias(alias)) {1334return null;1335}13361337Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH));1338if (protParam == null) {1339if (engineIsCertificateEntry(alias)) {1340if (entry instanceof CertEntry &&1341((CertEntry) entry).trustedKeyUsage != null) {13421343if (debug != null) {1344debug.println("Retrieved a trusted certificate at " +1345"alias '" + alias + "'");1346}13471348return new KeyStore.TrustedCertificateEntry(1349((CertEntry)entry).cert, getAttributes(entry));1350}1351} else {1352throw new UnrecoverableKeyException1353("requested entry requires a password");1354}1355}13561357if (protParam instanceof KeyStore.PasswordProtection) {1358if (engineIsCertificateEntry(alias)) {1359throw new UnsupportedOperationException1360("trusted certificate entries are not password-protected");1361} else if (engineIsKeyEntry(alias)) {1362KeyStore.PasswordProtection pp =1363(KeyStore.PasswordProtection)protParam;1364char[] password = pp.getPassword();13651366Key key = engineGetKey(alias, password);1367if (key instanceof PrivateKey) {1368Certificate[] chain = engineGetCertificateChain(alias);13691370return new KeyStore.PrivateKeyEntry((PrivateKey)key, chain,1371getAttributes(entry));13721373} else if (key instanceof SecretKey) {13741375return new KeyStore.SecretKeyEntry((SecretKey)key,1376getAttributes(entry));1377}1378} else if (!engineIsKeyEntry(alias)) {1379throw new UnsupportedOperationException1380("untrusted certificate entries are not " +1381"password-protected");1382}1383}13841385throw new UnsupportedOperationException();1386}13871388/**1389* Saves a <code>KeyStore.Entry</code> under the specified alias.1390* The specified protection parameter is used to protect the1391* <code>Entry</code>.1392*1393* <p> If an entry already exists for the specified alias,1394* it is overridden.1395*1396* @param alias save the <code>KeyStore.Entry</code> under this alias1397* @param entry the <code>Entry</code> to save1398* @param protParam the <code>ProtectionParameter</code>1399* used to protect the <code>Entry</code>,1400* which may be <code>null</code>1401*1402* @exception KeyStoreException if this operation fails1403*1404* @since 1.51405*/1406@Override1407public synchronized void engineSetEntry(String alias, KeyStore.Entry entry,1408KeyStore.ProtectionParameter protParam) throws KeyStoreException {14091410// get password1411if (protParam != null &&1412!(protParam instanceof KeyStore.PasswordProtection)) {1413throw new KeyStoreException("unsupported protection parameter");1414}1415KeyStore.PasswordProtection pProtect = null;1416if (protParam != null) {1417pProtect = (KeyStore.PasswordProtection)protParam;1418}14191420// set entry1421if (entry instanceof KeyStore.TrustedCertificateEntry) {1422if (protParam != null && pProtect.getPassword() != null) {1423// pre-1.5 style setCertificateEntry did not allow password1424throw new KeyStoreException1425("trusted certificate entries are not password-protected");1426} else {1427KeyStore.TrustedCertificateEntry tce =1428(KeyStore.TrustedCertificateEntry)entry;1429setCertEntry(alias, tce.getTrustedCertificate(),1430tce.getAttributes());14311432return;1433}1434} else if (entry instanceof KeyStore.PrivateKeyEntry) {1435if (pProtect == null || pProtect.getPassword() == null) {1436// pre-1.5 style setKeyEntry required password1437throw new KeyStoreException1438("non-null password required to create PrivateKeyEntry");1439} else {1440KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry)entry;1441setKeyEntry(alias, pke.getPrivateKey(), pProtect,1442pke.getCertificateChain(), pke.getAttributes());14431444return;1445}1446} else if (entry instanceof KeyStore.SecretKeyEntry) {1447if (pProtect == null || pProtect.getPassword() == null) {1448// pre-1.5 style setKeyEntry required password1449throw new KeyStoreException1450("non-null password required to create SecretKeyEntry");1451} else {1452KeyStore.SecretKeyEntry ske = (KeyStore.SecretKeyEntry)entry;1453setKeyEntry(alias, ske.getSecretKey(), pProtect,1454(Certificate[])null, ske.getAttributes());14551456return;1457}1458}14591460throw new KeyStoreException1461("unsupported entry type: " + entry.getClass().getName());1462}14631464/*1465* Assemble the entry attributes1466*/1467private Set<KeyStore.Entry.Attribute> getAttributes(Entry entry) {14681469if (entry.attributes == null) {1470entry.attributes = new HashSet<>();1471}14721473// friendlyName1474entry.attributes.add(new PKCS12Attribute(1475PKCS9FriendlyName_OID.toString(), entry.alias));14761477// localKeyID1478byte[] keyIdValue = entry.keyId;1479if (keyIdValue != null) {1480entry.attributes.add(new PKCS12Attribute(1481PKCS9LocalKeyId_OID.toString(), Debug.toString(keyIdValue)));1482}14831484// trustedKeyUsage1485if (entry instanceof CertEntry) {1486ObjectIdentifier[] trustedKeyUsageValue =1487((CertEntry) entry).trustedKeyUsage;1488if (trustedKeyUsageValue != null) {1489if (trustedKeyUsageValue.length == 1) { // omit brackets1490entry.attributes.add(new PKCS12Attribute(1491TrustedKeyUsage_OID.toString(),1492trustedKeyUsageValue[0].toString()));1493} else { // multi-valued1494entry.attributes.add(new PKCS12Attribute(1495TrustedKeyUsage_OID.toString(),1496Arrays.toString(trustedKeyUsageValue)));1497}1498}1499}15001501return entry.attributes;1502}15031504/*1505* Calculate MAC using HMAC algorithm (required for password integrity)1506*1507* Hash-based MAC algorithm combines secret key with message digest to1508* create a message authentication code (MAC)1509*/1510private byte[] calculateMac(char[] passwd, byte[] data)1511throws IOException1512{1513byte[] mData = null;1514String algName = macAlgorithm.substring(7);15151516try {1517// Generate a random salt.1518byte[] salt = getSalt();15191520// generate MAC (MAC key is generated within JCE)1521Mac m = Mac.getInstance(macAlgorithm);1522PBEParameterSpec params =1523new PBEParameterSpec(salt, macIterationCount);1524SecretKey key = getPBEKey(passwd);1525try {1526m.init(key, params);1527} finally {1528destroyPBEKey(key);1529}1530m.update(data);1531byte[] macResult = m.doFinal();15321533// encode as MacData1534MacData macData = new MacData(algName, macResult, salt,1535macIterationCount);1536DerOutputStream bytes = new DerOutputStream();1537bytes.write(macData.getEncoded());1538mData = bytes.toByteArray();1539} catch (Exception e) {1540throw new IOException("calculateMac failed: " + e, e);1541}1542return mData;1543}154415451546/*1547* Validate Certificate Chain1548*/1549private boolean validateChain(Certificate[] certChain)1550{1551for (int i = 0; i < certChain.length-1; i++) {1552X500Principal issuerDN =1553((X509Certificate)certChain[i]).getIssuerX500Principal();1554X500Principal subjectDN =1555((X509Certificate)certChain[i+1]).getSubjectX500Principal();1556if (!(issuerDN.equals(subjectDN)))1557return false;1558}15591560// Check for loops in the chain. If there are repeated certs,1561// the Set of certs in the chain will contain fewer certs than1562// the chain1563Set<Certificate> set = new HashSet<>(Arrays.asList(certChain));1564return set.size() == certChain.length;1565}15661567/*1568* Check that all the certificates are X.509 certificates1569*/1570private static void checkX509Certs(Certificate[] certs)1571throws KeyStoreException {1572if (certs != null) {1573for (Certificate cert : certs) {1574if (!(cert instanceof X509Certificate)) {1575throw new KeyStoreException(1576"Only X.509 certificates are supported - " +1577"rejecting class: " + cert.getClass().getName());1578}1579}1580}1581}15821583/*1584* Create PKCS#12 Attributes, friendlyName, localKeyId and trustedKeyUsage.1585*1586* Although attributes are optional, they could be required.1587* For e.g. localKeyId attribute is required to match the1588* private key with the associated end-entity certificate.1589* The trustedKeyUsage attribute is used to denote a trusted certificate.1590*1591* PKCS8ShroudedKeyBags include unique localKeyID and friendlyName.1592* CertBags may or may not include attributes depending on the type1593* of Certificate. In end-entity certificates, localKeyID should be1594* unique, and the corresponding private key should have the same1595* localKeyID. For trusted CA certs in the cert-chain, localKeyID1596* attribute is not required, hence most vendors don't include it.1597* NSS/Netscape require it to be unique or null, where as IE/OpenSSL1598* ignore it.1599*1600* Here is a list of pkcs12 attribute values in CertBags.1601*1602* PKCS12 Attribute NSS/Netscape IE OpenSSL J2SE1603* --------------------------------------------------------------1604* LocalKeyId1605* (In EE cert only,1606* NULL in CA certs) true true true true1607*1608* friendlyName unique same/ same/ unique1609* unique unique/1610* null1611* trustedKeyUsage - - - true1612*1613* Note: OpenSSL adds friendlyName for end-entity cert only, and1614* removes the localKeyID and friendlyName for CA certs.1615* If the CertBag did not have a friendlyName, most vendors will1616* add it, and assign it to the DN of the cert.1617*/1618private byte[] getBagAttributes(String alias, byte[] keyId,1619Set<KeyStore.Entry.Attribute> attributes) throws IOException {1620return getBagAttributes(alias, keyId, null, attributes);1621}16221623private byte[] getBagAttributes(String alias, byte[] keyId,1624ObjectIdentifier[] trustedUsage,1625Set<KeyStore.Entry.Attribute> attributes) throws IOException {16261627byte[] localKeyID = null;1628byte[] friendlyName = null;1629byte[] trustedKeyUsage = null;16301631// return null if all three attributes are null1632if ((alias == null) && (keyId == null) && (trustedKeyUsage == null)) {1633return null;1634}16351636// SafeBag Attributes1637DerOutputStream bagAttrs = new DerOutputStream();16381639// Encode the friendlyname oid.1640if (alias != null) {1641DerOutputStream bagAttr1 = new DerOutputStream();1642bagAttr1.putOID(PKCS9FriendlyName_OID);1643DerOutputStream bagAttrContent1 = new DerOutputStream();1644DerOutputStream bagAttrValue1 = new DerOutputStream();1645bagAttrContent1.putBMPString(alias);1646bagAttr1.write(DerValue.tag_Set, bagAttrContent1);1647bagAttrValue1.write(DerValue.tag_Sequence, bagAttr1);1648friendlyName = bagAttrValue1.toByteArray();1649}16501651// Encode the localkeyId oid.1652if (keyId != null) {1653DerOutputStream bagAttr2 = new DerOutputStream();1654bagAttr2.putOID(PKCS9LocalKeyId_OID);1655DerOutputStream bagAttrContent2 = new DerOutputStream();1656DerOutputStream bagAttrValue2 = new DerOutputStream();1657bagAttrContent2.putOctetString(keyId);1658bagAttr2.write(DerValue.tag_Set, bagAttrContent2);1659bagAttrValue2.write(DerValue.tag_Sequence, bagAttr2);1660localKeyID = bagAttrValue2.toByteArray();1661}16621663// Encode the trustedKeyUsage oid.1664if (trustedUsage != null) {1665DerOutputStream bagAttr3 = new DerOutputStream();1666bagAttr3.putOID(TrustedKeyUsage_OID);1667DerOutputStream bagAttrContent3 = new DerOutputStream();1668DerOutputStream bagAttrValue3 = new DerOutputStream();1669for (ObjectIdentifier usage : trustedUsage) {1670bagAttrContent3.putOID(usage);1671}1672bagAttr3.write(DerValue.tag_Set, bagAttrContent3);1673bagAttrValue3.write(DerValue.tag_Sequence, bagAttr3);1674trustedKeyUsage = bagAttrValue3.toByteArray();1675}16761677DerOutputStream attrs = new DerOutputStream();1678if (friendlyName != null) {1679attrs.write(friendlyName);1680}1681if (localKeyID != null) {1682attrs.write(localKeyID);1683}1684if (trustedKeyUsage != null) {1685attrs.write(trustedKeyUsage);1686}16871688if (attributes != null) {1689for (KeyStore.Entry.Attribute attribute : attributes) {1690String attributeName = attribute.getName();1691// skip friendlyName, localKeyId and trustedKeyUsage1692if (CORE_ATTRIBUTES[0].value().equals(attributeName) ||1693CORE_ATTRIBUTES[1].value().equals(attributeName) ||1694CORE_ATTRIBUTES[2].value().equals(attributeName)) {1695continue;1696}1697attrs.write(((PKCS12Attribute) attribute).getEncoded());1698}1699}17001701bagAttrs.write(DerValue.tag_Set, attrs);1702return bagAttrs.toByteArray();1703}17041705/*1706* Create EncryptedData content type, that contains EncryptedContentInfo.1707* Includes certificates in individual SafeBags of type CertBag.1708* Each CertBag may include pkcs12 attributes1709* (see comments in getBagAttributes)1710*/1711private byte[] createEncryptedData(char[] password)1712throws CertificateException, IOException1713{1714DerOutputStream out = new DerOutputStream();1715for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) {17161717String alias = e.nextElement();1718Entry entry = entries.get(alias);17191720// certificate chain1721Certificate[] certs;17221723if (entry instanceof PrivateKeyEntry) {1724PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;1725if (keyEntry.chain != null) {1726certs = keyEntry.chain;1727} else {1728certs = new Certificate[0];1729}1730} else if (entry instanceof CertEntry) {1731certs = new Certificate[]{((CertEntry) entry).cert};1732} else {1733certs = new Certificate[0];1734}17351736for (int i = 0; i < certs.length; i++) {1737// create SafeBag of Type CertBag1738DerOutputStream safeBag = new DerOutputStream();1739safeBag.putOID(CertBag_OID);17401741// create a CertBag1742DerOutputStream certBag = new DerOutputStream();1743certBag.putOID(PKCS9CertType_OID);17441745// write encoded certs in a context-specific tag1746DerOutputStream certValue = new DerOutputStream();1747X509Certificate cert = (X509Certificate) certs[i];1748certValue.putOctetString(cert.getEncoded());1749certBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,1750true, (byte) 0), certValue);17511752// wrap CertBag in a Sequence1753DerOutputStream certout = new DerOutputStream();1754certout.write(DerValue.tag_Sequence, certBag);1755byte[] certBagValue = certout.toByteArray();17561757// Wrap the CertBag encoding in a context-specific tag.1758DerOutputStream bagValue = new DerOutputStream();1759bagValue.write(certBagValue);1760// write SafeBag Value1761safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,1762true, (byte) 0), bagValue);17631764// write SafeBag Attributes1765// All Certs should have a unique friendlyName.1766// This change is made to meet NSS requirements.1767byte[] bagAttrs = null;1768if (i == 0) {1769// Only End-Entity Cert should have a localKeyId.1770if (entry instanceof KeyEntry) {1771KeyEntry keyEntry = (KeyEntry) entry;1772bagAttrs =1773getBagAttributes(keyEntry.alias, keyEntry.keyId,1774keyEntry.attributes);1775} else {1776CertEntry certEntry = (CertEntry) entry;1777bagAttrs =1778getBagAttributes(certEntry.alias, certEntry.keyId,1779certEntry.trustedKeyUsage,1780certEntry.attributes);1781}1782} else {1783// Trusted root CA certs and Intermediate CA certs do not1784// need to have a localKeyId, and hence localKeyId is null1785// This change is made to meet NSS/Netscape requirements.1786// NSS pkcs12 library requires trusted CA certs in the1787// certificate chain to have unique or null localKeyID.1788// However, IE/OpenSSL do not impose this restriction.1789bagAttrs = getBagAttributes(1790cert.getSubjectX500Principal().getName(), null,1791entry.attributes);1792}1793if (bagAttrs != null) {1794safeBag.write(bagAttrs);1795}17961797// wrap as Sequence1798out.write(DerValue.tag_Sequence, safeBag);1799} // for cert-chain1800}18011802// wrap as SequenceOf SafeBag1803DerOutputStream safeBagValue = new DerOutputStream();1804safeBagValue.write(DerValue.tag_SequenceOf, out);1805byte[] safeBagData = safeBagValue.toByteArray();18061807// encrypt the content (EncryptedContentInfo)1808if (!certProtectionAlgorithm.equalsIgnoreCase("NONE")) {1809byte[] encrContentInfo = encryptContent(safeBagData, password);18101811// -- SEQUENCE of EncryptedData1812DerOutputStream encrData = new DerOutputStream();1813DerOutputStream encrDataContent = new DerOutputStream();1814encrData.putInteger(0);1815encrData.write(encrContentInfo);1816encrDataContent.write(DerValue.tag_Sequence, encrData);1817return encrDataContent.toByteArray();1818} else {1819return safeBagData;1820}1821}18221823/*1824* Create SafeContent Data content type.1825* Includes encrypted secret key in a SafeBag of type SecretBag.1826* Includes encrypted private key in a SafeBag of type PKCS8ShroudedKeyBag.1827* Each PKCS8ShroudedKeyBag includes pkcs12 attributes1828* (see comments in getBagAttributes)1829*/1830private byte[] createSafeContent()1831throws CertificateException, IOException {18321833DerOutputStream out = new DerOutputStream();1834for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) {18351836String alias = e.nextElement();1837Entry entry = entries.get(alias);1838if (entry == null || (!(entry instanceof KeyEntry))) {1839continue;1840}1841DerOutputStream safeBag = new DerOutputStream();1842KeyEntry keyEntry = (KeyEntry) entry;18431844// DER encode the private key1845if (keyEntry instanceof PrivateKeyEntry) {1846// Create SafeBag of type pkcs8ShroudedKeyBag1847safeBag.putOID(PKCS8ShroudedKeyBag_OID);18481849// get the encrypted private key1850byte[] encrBytes = ((PrivateKeyEntry)keyEntry).protectedPrivKey;1851EncryptedPrivateKeyInfo encrInfo = null;1852try {1853encrInfo = new EncryptedPrivateKeyInfo(encrBytes);18541855} catch (IOException ioe) {1856throw new IOException("Private key not stored as "1857+ "PKCS#8 EncryptedPrivateKeyInfo"1858+ ioe.getMessage());1859}18601861// Wrap the EncryptedPrivateKeyInfo in a context-specific tag.1862DerOutputStream bagValue = new DerOutputStream();1863bagValue.write(encrInfo.getEncoded());1864safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,1865true, (byte) 0), bagValue);18661867// DER encode the secret key1868} else if (keyEntry instanceof SecretKeyEntry) {1869// Create SafeBag of type SecretBag1870safeBag.putOID(SecretBag_OID);18711872// Create a SecretBag1873DerOutputStream secretBag = new DerOutputStream();1874secretBag.putOID(PKCS8ShroudedKeyBag_OID);18751876// Write secret key in a context-specific tag1877DerOutputStream secretKeyValue = new DerOutputStream();1878secretKeyValue.putOctetString(1879((SecretKeyEntry) keyEntry).protectedSecretKey);1880secretBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,1881true, (byte) 0), secretKeyValue);18821883// Wrap SecretBag in a Sequence1884DerOutputStream secretBagSeq = new DerOutputStream();1885secretBagSeq.write(DerValue.tag_Sequence, secretBag);1886byte[] secretBagValue = secretBagSeq.toByteArray();18871888// Wrap the secret bag in a context-specific tag.1889DerOutputStream bagValue = new DerOutputStream();1890bagValue.write(secretBagValue);18911892// Write SafeBag value1893safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT,1894true, (byte) 0), bagValue);1895} else {1896continue; // skip this entry1897}18981899// write SafeBag Attributes1900byte[] bagAttrs =1901getBagAttributes(alias, entry.keyId, entry.attributes);1902safeBag.write(bagAttrs);19031904// wrap as Sequence1905out.write(DerValue.tag_Sequence, safeBag);1906}19071908// wrap as Sequence1909DerOutputStream safeBagValue = new DerOutputStream();1910safeBagValue.write(DerValue.tag_Sequence, out);1911return safeBagValue.toByteArray();1912}191319141915/*1916* Encrypt the contents using Password-based (PBE) encryption1917* as defined in PKCS #5.1918*1919* @return encrypted contents encoded as EncryptedContentInfo1920*/1921private byte[] encryptContent(byte[] data, char[] password)1922throws IOException {19231924byte[] encryptedData = null;192519261927try {1928// create AlgorithmParameters1929AlgorithmParameters algParams = getPBEAlgorithmParameters(1930certProtectionAlgorithm, certPbeIterationCount);1931DerOutputStream bytes = new DerOutputStream();19321933// Use JCE1934Cipher cipher = Cipher.getInstance(certProtectionAlgorithm);1935SecretKey skey = getPBEKey(password);1936try {1937cipher.init(Cipher.ENCRYPT_MODE, skey, algParams);1938} finally {1939destroyPBEKey(skey);1940}1941encryptedData = cipher.doFinal(data);19421943AlgorithmId algId = new AlgorithmId(1944mapPBEAlgorithmToOID(certProtectionAlgorithm),1945cipher.getParameters());1946// cipher.getParameters() now has IV1947algId.encode(bytes);1948byte[] encodedAlgId = bytes.toByteArray();19491950if (debug != null) {1951debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() +1952")");1953}19541955// create EncryptedContentInfo1956DerOutputStream bytes2 = new DerOutputStream();1957bytes2.putOID(ContentInfo.DATA_OID);1958bytes2.write(encodedAlgId);19591960// Wrap encrypted data in a context-specific tag.1961DerOutputStream tmpout2 = new DerOutputStream();1962tmpout2.putOctetString(encryptedData);1963bytes2.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT,1964false, (byte) 0), tmpout2);19651966// wrap EncryptedContentInfo in a Sequence1967DerOutputStream out = new DerOutputStream();1968out.write(DerValue.tag_Sequence, bytes2);1969return out.toByteArray();1970} catch (IOException ioe) {1971throw ioe;1972} catch (Exception e) {1973throw new IOException("Failed to encrypt" +1974" safe contents entry: " + e, e);1975}1976}19771978/**1979* Loads the keystore from the given input stream.1980*1981* <p>If a password is given, it is used to check the integrity of the1982* keystore data. Otherwise, the integrity of the keystore is not checked.1983*1984* @param stream the input stream from which the keystore is loaded1985* @param password the (optional) password used to check the integrity of1986* the keystore.1987*1988* @exception IOException if there is an I/O or format problem with the1989* keystore data1990* @exception NoSuchAlgorithmException if the algorithm used to check1991* the integrity of the keystore cannot be found1992* @exception CertificateException if any of the certificates in the1993* keystore could not be loaded1994*/1995public synchronized void engineLoad(InputStream stream, char[] password)1996throws IOException, NoSuchAlgorithmException, CertificateException1997{19981999// Reset config when loading a different keystore.2000certProtectionAlgorithm = null;2001certPbeIterationCount = -1;2002macAlgorithm = null;2003macIterationCount = -1;20042005if (stream == null)2006return;20072008// reset the counter2009counter = 0;20102011DerValue val = new DerValue(stream);2012DerInputStream s = val.toDerInputStream();2013int version = s.getInteger();20142015if (version != VERSION_3) {2016throw new IOException("PKCS12 keystore not in version 3 format");2017}20182019entries.clear();20202021/*2022* Read the authSafe.2023*/2024byte[] authSafeData;2025ContentInfo authSafe = new ContentInfo(s);2026ObjectIdentifier contentType = authSafe.getContentType();20272028if (contentType.equals(ContentInfo.DATA_OID)) {2029authSafeData = authSafe.getData();2030} else /* signed data */ {2031throw new IOException("public key protected PKCS12 not supported");2032}20332034DerInputStream as = new DerInputStream(authSafeData);2035DerValue[] safeContentsArray = as.getSequence(2);2036int count = safeContentsArray.length;20372038// reset the counters at the start2039privateKeyCount = 0;2040secretKeyCount = 0;2041certificateCount = 0;20422043boolean seeEncBag = false;20442045/*2046* Spin over the ContentInfos.2047*/2048for (int i = 0; i < count; i++) {2049ContentInfo safeContents;2050DerInputStream sci;2051byte[] eAlgId = null;20522053sci = new DerInputStream(safeContentsArray[i].toByteArray());2054safeContents = new ContentInfo(sci);2055contentType = safeContents.getContentType();2056if (contentType.equals(ContentInfo.DATA_OID)) {20572058if (debug != null) {2059debug.println("Loading PKCS#7 data");2060}20612062loadSafeContents(new DerInputStream(safeContents.getData()));2063} else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) {2064if (password == null) {20652066if (debug != null) {2067debug.println("Warning: skipping PKCS#7 encryptedData" +2068" - no password was supplied");2069}2070// No password to decrypt ENCRYPTED_DATA_OID. *Skip it*.2071// This means user will see a PrivateKeyEntry without2072// certificates and a whole TrustedCertificateEntry will2073// be lost. This is not a perfect solution but alternative2074// solutions are more disruptive:2075//2076// We cannot just fail, since KeyStore.load(is, null)2077// has been known to never fail because of a null password.2078//2079// We cannot just throw away the whole PrivateKeyEntry,2080// this is too silent and no one will notice anything.2081//2082// We also cannot fail when getCertificate() on such a2083// PrivateKeyEntry is called, since the method has not2084// specified this behavior.2085continue;2086}20872088DerInputStream edi =2089safeContents.getContent().toDerInputStream();2090int edVersion = edi.getInteger();2091DerValue[] seq = edi.getSequence(3);2092if (seq.length != 3) {2093// We require the encryptedContent field, even though2094// it is optional2095throw new IOException("Invalid length for EncryptedContentInfo");2096}2097ObjectIdentifier edContentType = seq[0].getOID();2098eAlgId = seq[1].toByteArray();2099if (!seq[2].isContextSpecific((byte)0)) {2100throw new IOException("unsupported encrypted content type "2101+ seq[2].tag);2102}2103byte newTag = DerValue.tag_OctetString;2104if (seq[2].isConstructed())2105newTag |= 0x20;2106seq[2].resetTag(newTag);2107byte[] rawData = seq[2].getOctetString();21082109// parse Algorithm parameters2110DerInputStream in = seq[1].toDerInputStream();2111ObjectIdentifier algOid = in.getOID();2112AlgorithmParameters algParams = parseAlgParameters(algOid, in);21132114PBEParameterSpec pbeSpec;2115int ic = 0;21162117if (algParams != null) {2118try {2119pbeSpec =2120algParams.getParameterSpec(PBEParameterSpec.class);2121} catch (InvalidParameterSpecException ipse) {2122throw new IOException(2123"Invalid PBE algorithm parameters");2124}2125ic = pbeSpec.getIterationCount();21262127if (ic > MAX_ITERATION_COUNT) {2128throw new IOException("cert PBE iteration count too large");2129}21302131certProtectionAlgorithm2132= mapPBEParamsToAlgorithm(algOid, algParams);2133certPbeIterationCount = ic;2134seeEncBag = true;2135}21362137if (debug != null) {2138debug.println("Loading PKCS#7 encryptedData " +2139"(" + mapPBEParamsToAlgorithm(algOid, algParams) +2140" iterations: " + ic + ")");2141}21422143try {2144RetryWithZero.run(pass -> {2145// Use JCE2146Cipher cipher = Cipher.getInstance(2147mapPBEParamsToAlgorithm(algOid, algParams));2148SecretKey skey = getPBEKey(pass);2149try {2150cipher.init(Cipher.DECRYPT_MODE, skey, algParams);2151} finally {2152destroyPBEKey(skey);2153}2154loadSafeContents(new DerInputStream(cipher.doFinal(rawData)));2155return null;2156}, password);2157} catch (Exception e) {2158throw new IOException("keystore password was incorrect",2159new UnrecoverableKeyException(2160"failed to decrypt safe contents entry: " + e));2161}2162} else {2163throw new IOException("public key protected PKCS12" +2164" not supported");2165}2166}21672168// No ENCRYPTED_DATA_OID but see certificate. Must be passwordless.2169if (!seeEncBag && certificateCount > 0) {2170certProtectionAlgorithm = "NONE";2171}21722173// The MacData is optional.2174if (s.available() > 0) {2175// If there is no password, we cannot fail. KeyStore.load(is, null)2176// has been known to never fail because of a null password.2177if (password != null) {2178MacData macData = new MacData(s);2179int ic = macData.getIterations();21802181try {2182if (ic > MAX_ITERATION_COUNT) {2183throw new InvalidAlgorithmParameterException(2184"MAC iteration count too large: " + ic);2185}21862187String algName =2188macData.getDigestAlgName().toUpperCase(Locale.ENGLISH);21892190// Change SHA-1 to SHA12191algName = algName.replace("-", "");21922193macAlgorithm = "HmacPBE" + algName;2194macIterationCount = ic;21952196// generate MAC (MAC key is created within JCE)2197Mac m = Mac.getInstance(macAlgorithm);2198PBEParameterSpec params =2199new PBEParameterSpec(macData.getSalt(), ic);22002201RetryWithZero.run(pass -> {2202SecretKey key = getPBEKey(pass);2203try {2204m.init(key, params);2205} finally {2206destroyPBEKey(key);2207}2208m.update(authSafeData);2209byte[] macResult = m.doFinal();22102211if (debug != null) {2212debug.println("Checking keystore integrity " +2213"(" + m.getAlgorithm() + " iterations: " + ic + ")");2214}22152216if (!MessageDigest.isEqual(macData.getDigest(), macResult)) {2217throw new UnrecoverableKeyException("Failed PKCS12" +2218" integrity checking");2219}2220return (Void) null;2221}, password);2222} catch (Exception e) {2223throw new IOException("Integrity check failed: " + e, e);2224}2225}2226} else {2227macAlgorithm = "NONE";2228}22292230/*2231* Match up private keys with certificate chains.2232*/2233PrivateKeyEntry[] list =2234keyList.toArray(new PrivateKeyEntry[keyList.size()]);2235for (int m = 0; m < list.length; m++) {2236PrivateKeyEntry entry = list[m];2237if (entry.keyId != null) {2238ArrayList<X509Certificate> chain =2239new ArrayList<X509Certificate>();2240X509Certificate cert = findMatchedCertificate(entry);22412242mainloop:2243while (cert != null) {2244// Check for loops in the certificate chain2245if (!chain.isEmpty()) {2246for (X509Certificate chainCert : chain) {2247if (cert.equals(chainCert)) {2248if (debug != null) {2249debug.println("Loop detected in " +2250"certificate chain. Skip adding " +2251"repeated cert to chain. Subject: " +2252cert.getSubjectX500Principal()2253.toString());2254}2255break mainloop;2256}2257}2258}2259chain.add(cert);2260if (KeyStoreUtil.isSelfSigned(cert)) {2261break;2262}2263cert = findIssuer(cert);2264}2265/* Update existing KeyEntry in entries table */2266if (chain.size() > 0) {2267entry.chain = chain.toArray(new Certificate[chain.size()]);2268} else {2269// Remove private key entries where there is no associated2270// certs. Most likely the keystore is loaded with a null2271// password.2272entries.remove(entry);2273}2274}2275}22762277if (debug != null) {2278debug.println("PKCS12KeyStore load: private key count: " +2279privateKeyCount + ". secret key count: " + secretKeyCount +2280". certificate count: " + certificateCount);2281}22822283certEntries.clear();2284allCerts.clear();2285keyList.clear();2286}22872288/**2289* Find the issuer of input in allCerts. If the input has an2290* AuthorityKeyIdentifier extension and the keyId inside matches2291* the keyId of the SubjectKeyIdentifier of a cert. This cert is2292* returned. Otherwise, a cert whose subjectDN is the same as the2293* input's issuerDN is returned.2294*2295* @param input the input certificate2296* @return the isssuer, or null if none matches2297*/2298private X509Certificate findIssuer(X509Certificate input) {22992300X509Certificate fallback = null; // the DN match2301X500Principal issuerPrinc = input.getIssuerX500Principal();23022303// AuthorityKeyIdentifier value encoded as an OCTET STRING2304byte[] issuerIdExtension = input.getExtensionValue(2305KnownOIDs.AuthorityKeyID.value());2306byte[] issuerId = null;23072308if (issuerIdExtension != null) {2309try {2310issuerId = new AuthorityKeyIdentifierExtension(2311false,2312new DerValue(issuerIdExtension).getOctetString())2313.getEncodedKeyIdentifier();2314} catch (IOException e) {2315// ignored. issuerId is still null2316}2317}23182319for (X509Certificate cert : allCerts) {2320if (cert.getSubjectX500Principal().equals(issuerPrinc)) {2321if (issuerId != null) {2322// SubjectKeyIdentifier value encoded as an OCTET STRING2323byte[] subjectIdExtension = cert.getExtensionValue(2324KnownOIDs.SubjectKeyID.value());2325byte[] subjectId = null;2326if (subjectIdExtension != null) {2327try {2328subjectId = new DerValue(subjectIdExtension)2329.getOctetString();2330} catch (IOException e) {2331// ignored. issuerId is still null2332}2333}2334if (subjectId != null) {2335if (Arrays.equals(issuerId, subjectId)) {2336// keyId exact match!2337return cert;2338} else {2339// Different keyId. Not a fallback.2340continue;2341}2342} else {2343// A DN match with no subjectId2344fallback = cert;2345}2346} else { // if there is no issuerId, return the 1st DN match2347return cert;2348}2349}2350}2351return fallback;2352}23532354/**2355* Returns if a pkcs12 file is password-less. This means no cert is2356* encrypted and there is no Mac. Please note that the private key2357* can be encrypted.2358*2359* This is a simplified version of {@link #engineLoad} that only looks2360* at the ContentInfo types.2361*2362* @param f the pkcs12 file2363* @return if it's password-less2364* @throws IOException2365*/2366public static boolean isPasswordless(File f) throws IOException {23672368try (FileInputStream stream = new FileInputStream(f)) {2369DerValue val = new DerValue(stream);2370DerInputStream s = val.toDerInputStream();23712372s.getInteger(); // skip version23732374ContentInfo authSafe = new ContentInfo(s);2375DerInputStream as = new DerInputStream(authSafe.getData());2376for (DerValue seq : as.getSequence(2)) {2377DerInputStream sci = new DerInputStream(seq.toByteArray());2378ContentInfo safeContents = new ContentInfo(sci);2379if (safeContents.getContentType()2380.equals(ContentInfo.ENCRYPTED_DATA_OID)) {2381// Certificate encrypted2382return false;2383}2384}23852386if (s.available() > 0) {2387// The MacData exists.2388return false;2389}2390}2391return true;2392}23932394/**2395* Locates a matched CertEntry from certEntries, and returns its cert.2396* @param entry the KeyEntry to match2397* @return a certificate, null if not found2398*/2399private X509Certificate findMatchedCertificate(PrivateKeyEntry entry) {2400CertEntry keyIdMatch = null;2401CertEntry aliasMatch = null;2402for (CertEntry ce: certEntries) {2403if (Arrays.equals(entry.keyId, ce.keyId)) {2404keyIdMatch = ce;2405if (entry.alias.equalsIgnoreCase(ce.alias)) {2406// Full match!2407return ce.cert;2408}2409} else if (entry.alias.equalsIgnoreCase(ce.alias)) {2410aliasMatch = ce;2411}2412}2413// keyId match first, for compatibility2414if (keyIdMatch != null) return keyIdMatch.cert;2415else if (aliasMatch != null) return aliasMatch.cert;2416else return null;2417}24182419private void loadSafeContents(DerInputStream stream)2420throws IOException, NoSuchAlgorithmException, CertificateException2421{2422DerValue[] safeBags = stream.getSequence(2);2423int count = safeBags.length;24242425/*2426* Spin over the SafeBags.2427*/2428for (int i = 0; i < count; i++) {2429ObjectIdentifier bagId;2430DerInputStream sbi;2431DerValue bagValue;2432Object bagItem = null;24332434sbi = safeBags[i].toDerInputStream();2435bagId = sbi.getOID();2436bagValue = sbi.getDerValue();2437if (!bagValue.isContextSpecific((byte)0)) {2438throw new IOException("unsupported PKCS12 bag value type "2439+ bagValue.tag);2440}2441bagValue = bagValue.data.getDerValue();2442if (bagId.equals(PKCS8ShroudedKeyBag_OID)) {2443PrivateKeyEntry kEntry = new PrivateKeyEntry();2444kEntry.protectedPrivKey = bagValue.toByteArray();2445bagItem = kEntry;2446privateKeyCount++;2447} else if (bagId.equals(CertBag_OID)) {2448DerInputStream cs = new DerInputStream(bagValue.toByteArray());2449DerValue[] certValues = cs.getSequence(2);2450if (certValues.length != 2) {2451throw new IOException("Invalid length for CertBag");2452}2453ObjectIdentifier certId = certValues[0].getOID();2454if (!certValues[1].isContextSpecific((byte)0)) {2455throw new IOException("unsupported PKCS12 cert value type "2456+ certValues[1].tag);2457}2458DerValue certValue = certValues[1].data.getDerValue();2459CertificateFactory cf = CertificateFactory.getInstance("X509");2460X509Certificate cert;2461cert = (X509Certificate)cf.generateCertificate2462(new ByteArrayInputStream(certValue.getOctetString()));2463bagItem = cert;2464certificateCount++;2465} else if (bagId.equals(SecretBag_OID)) {2466DerInputStream ss = new DerInputStream(bagValue.toByteArray());2467DerValue[] secretValues = ss.getSequence(2);2468if (secretValues.length != 2) {2469throw new IOException("Invalid length for SecretBag");2470}2471ObjectIdentifier secretId = secretValues[0].getOID();2472if (!secretValues[1].isContextSpecific((byte)0)) {2473throw new IOException(2474"unsupported PKCS12 secret value type "2475+ secretValues[1].tag);2476}2477DerValue secretValue = secretValues[1].data.getDerValue();2478SecretKeyEntry kEntry = new SecretKeyEntry();2479kEntry.protectedSecretKey = secretValue.getOctetString();2480bagItem = kEntry;2481secretKeyCount++;2482} else {24832484if (debug != null) {2485debug.println("Unsupported PKCS12 bag type: " + bagId);2486}2487}24882489DerValue[] attrSet;2490try {2491attrSet = sbi.getSet(3);2492} catch (IOException e) {2493// entry does not have attributes2494// Note: CA certs can have no attributes2495// OpenSSL generates pkcs12 with no attr for CA certs.2496attrSet = null;2497}24982499String alias = null;2500byte[] keyId = null;2501ObjectIdentifier[] trustedKeyUsage = null;2502Set<PKCS12Attribute> attributes = new HashSet<>();25032504if (attrSet != null) {2505for (int j = 0; j < attrSet.length; j++) {2506byte[] encoded = attrSet[j].toByteArray();2507DerInputStream as = new DerInputStream(encoded);2508DerValue[] attrSeq = as.getSequence(2);2509if (attrSeq.length != 2) {2510throw new IOException("Invalid length for Attribute");2511}2512ObjectIdentifier attrId = attrSeq[0].getOID();2513DerInputStream vs =2514new DerInputStream(attrSeq[1].toByteArray());2515DerValue[] valSet;2516try {2517valSet = vs.getSet(1);2518} catch (IOException e) {2519throw new IOException("Attribute " + attrId +2520" should have a value " + e.getMessage());2521}2522if (attrId.equals(PKCS9FriendlyName_OID)) {2523alias = valSet[0].getBMPString();2524} else if (attrId.equals(PKCS9LocalKeyId_OID)) {2525keyId = valSet[0].getOctetString();2526} else if2527(attrId.equals(TrustedKeyUsage_OID)) {2528trustedKeyUsage = new ObjectIdentifier[valSet.length];2529for (int k = 0; k < valSet.length; k++) {2530trustedKeyUsage[k] = valSet[k].getOID();2531}2532} else {2533attributes.add(new PKCS12Attribute(encoded));2534}2535}2536}25372538/*2539* As per PKCS12 v1.0 friendlyname (alias) and localKeyId (keyId)2540* are optional PKCS12 bagAttributes. But entries in the keyStore2541* are identified by their alias. Hence we need to have an2542* Unfriendlyname in the alias, if alias is null. The keyId2543* attribute is required to match the private key with the2544* certificate. If we get a bagItem of type KeyEntry with a2545* null keyId, we should skip it entirely.2546*/2547if (bagItem instanceof KeyEntry) {2548KeyEntry entry = (KeyEntry)bagItem;25492550if (keyId == null) {2551if (bagItem instanceof PrivateKeyEntry) {2552// Insert a localKeyID for the privateKey2553// Note: This is a workaround to allow null localKeyID2554// attribute in pkcs12 with one private key entry and2555// associated cert-chain2556if (privateKeyCount == 1) {2557keyId = "01".getBytes(UTF_8);2558} else {2559continue;2560}2561} else {2562// keyId in a SecretKeyEntry is not significant2563keyId = "00".getBytes(UTF_8);2564}2565}2566entry.keyId = keyId;2567// restore date if it exists2568String keyIdStr = new String(keyId, UTF_8);2569Date date = null;2570if (keyIdStr.startsWith("Time ")) {2571try {2572date = new Date(2573Long.parseLong(keyIdStr.substring(5)));2574} catch (Exception e) {2575date = null;2576}2577}2578if (date == null) {2579date = new Date();2580}2581entry.date = date;25822583if (bagItem instanceof PrivateKeyEntry) {2584keyList.add((PrivateKeyEntry) entry);2585}2586if (entry.attributes == null) {2587entry.attributes = new HashSet<>();2588}2589entry.attributes.addAll(attributes);2590if (alias == null) {2591alias = getUnfriendlyName();2592}2593entry.alias = alias;2594entries.put(alias.toLowerCase(Locale.ENGLISH), entry);25952596} else if (bagItem instanceof X509Certificate) {2597X509Certificate cert = (X509Certificate)bagItem;2598// Insert a localKeyID for the corresponding cert2599// Note: This is a workaround to allow null localKeyID2600// attribute in pkcs12 with one private key entry and2601// associated cert-chain2602if ((keyId == null) && (privateKeyCount == 1)) {2603// insert localKeyID only for EE cert or self-signed cert2604if (i == 0) {2605keyId = "01".getBytes(UTF_8);2606}2607}2608// Trusted certificate2609if (trustedKeyUsage != null) {2610if (alias == null) {2611alias = getUnfriendlyName();2612}2613CertEntry certEntry =2614new CertEntry(cert, keyId, alias, trustedKeyUsage,2615attributes);2616entries.put(alias.toLowerCase(Locale.ENGLISH), certEntry);2617} else {2618certEntries.add(new CertEntry(cert, keyId, alias));2619}2620allCerts.add(cert);2621}2622}2623}26242625private String getUnfriendlyName() {2626counter++;2627return (String.valueOf(counter));2628}26292630/*2631* PKCS12 permitted first 24 bytes:2632*2633* 30 80 02 01 03 30 80 06 09 2A 86 48 86 F7 0D 01 07 01 A0 80 24 80 04 --2634* 30 82 -- -- 02 01 03 30 82 -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 8-2635* 30 -- 02 01 03 30 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 -- 04 -- -- --2636* 30 81 -- 02 01 03 30 81 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 81 -- 042637* 30 82 -- -- 02 01 03 30 81 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 81 --2638* 30 83 -- -- -- 02 01 03 30 82 -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 A02639* 30 83 -- -- -- 02 01 03 30 83 -- -- -- 06 09 2A 86 48 86 F7 0D 01 07 012640* 30 84 -- -- -- -- 02 01 03 30 83 -- -- -- 06 09 2A 86 48 86 F7 0D 01 072641* 30 84 -- -- -- -- 02 01 03 30 84 -- -- -- -- 06 09 2A 86 48 86 F7 0D 012642*/26432644private static final long[][] PKCS12_HEADER_PATTERNS = {2645{ 0x3080020103308006L, 0x092A864886F70D01L, 0x0701A08024800400L },2646{ 0x3082000002010330L, 0x82000006092A8648L, 0x86F70D010701A080L },2647{ 0x3000020103300006L, 0x092A864886F70D01L, 0x0701A00004000000L },2648{ 0x3081000201033081L, 0x0006092A864886F7L, 0x0D010701A0810004L },2649{ 0x3082000002010330L, 0x810006092A864886L, 0xF70D010701A08100L },2650{ 0x3083000000020103L, 0x3082000006092A86L, 0x4886F70D010701A0L },2651{ 0x3083000000020103L, 0x308300000006092AL, 0x864886F70D010701L },2652{ 0x3084000000000201L, 0x0330830000000609L, 0x2A864886F70D0107L },2653{ 0x3084000000000201L, 0x0330840000000006L, 0x092A864886F70D01L }2654};26552656private static final long[][] PKCS12_HEADER_MASKS = {2657{ 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFF00L },2658{ 0xFFFF0000FFFFFFFFL, 0xFF0000FFFFFFFFFFL, 0xFFFFFFFFFFFFFFF0L },2659{ 0xFF00FFFFFFFF00FFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFF00FF000000L },2660{ 0xFFFF00FFFFFFFFFFL, 0x00FFFFFFFFFFFFFFL, 0xFFFFFFFFFFFF00FFL },2661{ 0xFFFF0000FFFFFFFFL, 0xFF00FFFFFFFFFFFFL, 0xFFFFFFFFFFFFFF00L },2662{ 0xFFFF000000FFFFFFL, 0xFFFF0000FFFFFFFFL, 0xFFFFFFFFFFFFFFFFL },2663{ 0xFFFF000000FFFFFFL, 0xFFFF000000FFFFFFL, 0xFFFFFFFFFFFFFFFFL },2664{ 0xFFFF00000000FFFFL, 0xFFFFFF000000FFFFL, 0xFFFFFFFFFFFFFFFFL },2665{ 0xFFFF00000000FFFFL, 0xFFFFFF00000000FFL, 0xFFFFFFFFFFFFFFFFL }2666};26672668/**2669* Probe the first few bytes of the keystore data stream for a valid2670* PKCS12 keystore encoding.2671*/2672@Override2673public boolean engineProbe(InputStream stream) throws IOException {26742675DataInputStream dataStream;2676if (stream instanceof DataInputStream) {2677dataStream = (DataInputStream)stream;2678} else {2679dataStream = new DataInputStream(stream);2680}26812682long firstPeek = dataStream.readLong();2683long nextPeek = dataStream.readLong();2684long finalPeek = dataStream.readLong();2685boolean result = false;26862687for (int i = 0; i < PKCS12_HEADER_PATTERNS.length; i++) {2688if (PKCS12_HEADER_PATTERNS[i][0] ==2689(firstPeek & PKCS12_HEADER_MASKS[i][0]) &&2690(PKCS12_HEADER_PATTERNS[i][1] ==2691(nextPeek & PKCS12_HEADER_MASKS[i][1])) &&2692(PKCS12_HEADER_PATTERNS[i][2] ==2693(finalPeek & PKCS12_HEADER_MASKS[i][2]))) {2694result = true;2695break;2696}2697}26982699return result;2700}27012702// The following methods are related to customizing2703// the generation of a PKCS12 keystore or private/secret2704// key entries.27052706private static boolean useLegacy() {2707return GetPropertyAction.privilegedGetProperty(2708USE_LEGACY_PROP) != null;2709}27102711private static String defaultCertProtectionAlgorithm() {2712if (useLegacy()) {2713return LEGACY_CERT_PBE_ALGORITHM;2714}2715String result = SecurityProperties.privilegedGetOverridable(2716"keystore.pkcs12.certProtectionAlgorithm");2717return (result != null && !result.isEmpty())2718? result : DEFAULT_CERT_PBE_ALGORITHM;2719}27202721private static int defaultCertPbeIterationCount() {2722if (useLegacy()) {2723return LEGACY_PBE_ITERATION_COUNT;2724}2725String result = SecurityProperties.privilegedGetOverridable(2726"keystore.pkcs12.certPbeIterationCount");2727return (result != null && !result.isEmpty())2728? string2IC("certPbeIterationCount", result)2729: DEFAULT_CERT_PBE_ITERATION_COUNT;2730}27312732// Read both "keystore.pkcs12.keyProtectionAlgorithm" and2733// "keystore.PKCS12.keyProtectionAlgorithm" for compatibility.2734private static String defaultKeyProtectionAlgorithm() {2735if (useLegacy()) {2736return LEGACY_KEY_PBE_ALGORITHM;2737}2738@SuppressWarnings("removal")2739String result = AccessController.doPrivileged(new PrivilegedAction<String>() {2740public String run() {2741String result;2742String name1 = "keystore.pkcs12.keyProtectionAlgorithm";2743String name2 = "keystore.PKCS12.keyProtectionAlgorithm";2744result = System.getProperty(name1);2745if (result != null) {2746return result;2747}2748result = System.getProperty(name2);2749if (result != null) {2750return result;2751}2752result = Security.getProperty(name1);2753if (result != null) {2754return result;2755}2756return Security.getProperty(name2);2757}2758});2759return (result != null && !result.isEmpty())2760? result : DEFAULT_KEY_PBE_ALGORITHM;2761}27622763private static int defaultKeyPbeIterationCount() {2764if (useLegacy()) {2765return LEGACY_PBE_ITERATION_COUNT;2766}2767String result = SecurityProperties.privilegedGetOverridable(2768"keystore.pkcs12.keyPbeIterationCount");2769return (result != null && !result.isEmpty())2770? string2IC("keyPbeIterationCount", result)2771: DEFAULT_KEY_PBE_ITERATION_COUNT;2772}27732774private static String defaultMacAlgorithm() {2775if (useLegacy()) {2776return LEGACY_MAC_ALGORITHM;2777}2778String result = SecurityProperties.privilegedGetOverridable(2779"keystore.pkcs12.macAlgorithm");2780return (result != null && !result.isEmpty())2781? result : DEFAULT_MAC_ALGORITHM;2782}27832784private static int defaultMacIterationCount() {2785if (useLegacy()) {2786return LEGACY_MAC_ITERATION_COUNT;2787}2788String result = SecurityProperties.privilegedGetOverridable(2789"keystore.pkcs12.macIterationCount");2790return (result != null && !result.isEmpty())2791? string2IC("macIterationCount", result)2792: DEFAULT_MAC_ITERATION_COUNT;2793}27942795private static int string2IC(String type, String value) {2796int number;2797try {2798number = Integer.parseInt(value);2799} catch (NumberFormatException e) {2800throw new IllegalArgumentException("keystore.pkcs12." + type2801+ " is not a number: " + value);2802}2803if (number <= 0 || number > MAX_ITERATION_COUNT) {2804throw new IllegalArgumentException("Invalid keystore.pkcs12."2805+ type + ": " + value);2806}2807return number;2808}2809}281028112812