Path: blob/master/src/java.base/macosx/classes/apple/security/KeychainStore.java
67770 views
/*1* Copyright (c) 2011, 2022, 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 apple.security;2627import java.io.*;28import java.security.*;29import java.security.cert.*;30import java.security.cert.Certificate;31import java.security.spec.*;32import java.util.*;3334import javax.crypto.*;35import javax.crypto.spec.*;36import javax.security.auth.x500.*;3738import sun.security.pkcs.*;39import sun.security.pkcs.EncryptedPrivateKeyInfo;40import sun.security.util.*;41import sun.security.x509.*;4243/**44* This class provides the keystore implementation referred to as "KeychainStore".45* It uses the current user's keychain as its backing storage, and does NOT support46* a file-based implementation.47*/4849public final class KeychainStore extends KeyStoreSpi {5051// Private keys and their supporting certificate chains52// If a key came from the keychain it has a SecKeyRef and one or more53// SecCertificateRef. When we delete the key we have to delete all of the corresponding54// native objects.55static class KeyEntry {56Date date; // the creation date of this entry57byte[] protectedPrivKey;58char[] password;59long keyRef; // SecKeyRef for this key60Certificate chain[];61long chainRefs[]; // SecCertificateRefs for this key's chain.62};6364// Trusted certificates65static class TrustedCertEntry {66Date date; // the creation date of this entry6768Certificate cert;69long certRef; // SecCertificateRef for this key7071// Each KeyStore.TrustedCertificateEntry have 2 attributes:72// 1. "trustSettings" -> trustSettings.toString()73// 2. "2.16.840.1.113894.746875.1.1" -> trustedKeyUsageValue74// The 1st one is mainly for debugging use. The 2nd one is similar75// to the attribute with the same key in a PKCS12KeyStore.7677// The SecTrustSettingsCopyTrustSettings() output for this certificate78// inside the KeyChain in its original array of CFDictionaryRef objects79// structure with values dumped as strings. For each trust, an extra80// entry "SecPolicyOid" is added whose value is the OID for this trust.81// The extra entries are used to construct trustedKeyUsageValue.82List<Map<String, String>> trustSettings;8384// One or more OIDs defined in http://oidref.com/1.2.840.113635.100.1.85// It can also be "2.5.29.37.0" for a self-signed certificate with86// an empty trust settings. This value is never empty. When there are87// multiple OID values, it takes the form of "[1.1.1, 1.1.2]".88String trustedKeyUsageValue;89};9091/**92* Entries that have been deleted. When something calls engineStore we'll93* remove them from the keychain.94*/95private Hashtable<String, Object> deletedEntries = new Hashtable<>();9697/**98* Entries that have been added. When something calls engineStore we'll99* add them to the keychain.100*/101private Hashtable<String, Object> addedEntries = new Hashtable<>();102103/**104* Private keys and certificates are stored in a hashtable.105* Hash entries are keyed by alias names.106*/107private Hashtable<String, Object> entries = new Hashtable<>();108109/**110* Algorithm identifiers and corresponding OIDs for the contents of the111* PKCS12 bag we get from the Keychain.112*/113private static ObjectIdentifier PKCS8ShroudedKeyBag_OID =114ObjectIdentifier.of(KnownOIDs.PKCS8ShroudedKeyBag);115private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID =116ObjectIdentifier.of(KnownOIDs.PBEWithSHA1AndDESede);117118/**119* Constnats used in PBE decryption.120*/121private static final int iterationCount = 1024;122private static final int SALT_LEN = 20;123124private static final Debug debug = Debug.getInstance("keystore");125126static {127jdk.internal.loader.BootLoader.loadLibrary("osxsecurity");128}129130private static void permissionCheck() {131@SuppressWarnings("removal")132SecurityManager sec = System.getSecurityManager();133134if (sec != null) {135sec.checkPermission(new RuntimePermission("useKeychainStore"));136}137}138139140/**141* Verify the Apple provider in the constructor.142*143* @exception SecurityException if fails to verify144* its own integrity145*/146public KeychainStore() { }147148/**149* Returns the key associated with the given alias, using the given150* password to recover it.151*152* @param alias the alias name153* @param password the password for recovering the key. This password is154* used internally as the key is exported in a PKCS12 format.155*156* @return the requested key, or null if the given alias does not exist157* or does not identify a <i>key entry</i>.158*159* @exception NoSuchAlgorithmException if the algorithm for recovering the160* key cannot be found161* @exception UnrecoverableKeyException if the key cannot be recovered162* (e.g., the given password is wrong).163*/164public Key engineGetKey(String alias, char[] password)165throws NoSuchAlgorithmException, UnrecoverableKeyException166{167permissionCheck();168169// An empty password is rejected by MacOS API, no private key data170// is exported. If no password is passed (as is the case when171// this implementation is used as browser keystore in various172// deployment scenarios like Webstart, JFX and applets), create173// a dummy password so MacOS API is happy.174if (password == null || password.length == 0) {175// Must not be a char array with only a 0, as this is an empty176// string.177if (random == null) {178random = new SecureRandom();179}180password = Long.toString(random.nextLong()).toCharArray();181}182183Object entry = entries.get(alias.toLowerCase());184185if (entry == null || !(entry instanceof KeyEntry)) {186return null;187}188189// This call gives us a PKCS12 bag, with the key inside it.190byte[] exportedKeyInfo = _getEncodedKeyData(((KeyEntry)entry).keyRef, password);191if (exportedKeyInfo == null) {192return null;193}194195PrivateKey returnValue = null;196197try {198byte[] pkcs8KeyData = fetchPrivateKeyFromBag(exportedKeyInfo);199byte[] encryptedKey;200AlgorithmParameters algParams;201ObjectIdentifier algOid;202try {203// get the encrypted private key204EncryptedPrivateKeyInfo encrInfo = new EncryptedPrivateKeyInfo(pkcs8KeyData);205encryptedKey = encrInfo.getEncryptedData();206207// parse Algorithm parameters208DerValue val = new DerValue(encrInfo.getAlgorithm().encode());209DerInputStream in = val.toDerInputStream();210algOid = in.getOID();211algParams = parseAlgParameters(in);212213} catch (IOException ioe) {214UnrecoverableKeyException uke =215new UnrecoverableKeyException("Private key not stored as "216+ "PKCS#8 EncryptedPrivateKeyInfo: " + ioe);217uke.initCause(ioe);218throw uke;219}220221// Use JCE to decrypt the data using the supplied password.222SecretKey skey = getPBEKey(password);223Cipher cipher = Cipher.getInstance(algOid.toString());224cipher.init(Cipher.DECRYPT_MODE, skey, algParams);225byte[] decryptedPrivateKey = cipher.doFinal(encryptedKey);226PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(decryptedPrivateKey);227228// Parse the key algorithm and then use a JCA key factory to create the private key.229DerValue val = new DerValue(decryptedPrivateKey);230DerInputStream in = val.toDerInputStream();231232// Ignore this -- version should be 0.233int i = in.getInteger();234235// Get the Algorithm ID next236DerValue[] value = in.getSequence(2);237if (value.length < 1 || value.length > 2) {238throw new IOException("Invalid length for AlgorithmIdentifier");239}240AlgorithmId algId = new AlgorithmId(value[0].getOID());241String algName = algId.getName();242243// Get a key factory for this algorithm. It's likely to be 'RSA'.244KeyFactory kfac = KeyFactory.getInstance(algName);245returnValue = kfac.generatePrivate(kspec);246} catch (Exception e) {247UnrecoverableKeyException uke =248new UnrecoverableKeyException("Get Key failed: " +249e.getMessage());250uke.initCause(e);251throw uke;252}253254return returnValue;255}256257private native byte[] _getEncodedKeyData(long secKeyRef, char[] password);258259/**260* Returns the certificate chain associated with the given alias.261*262* @param alias the alias name263*264* @return the certificate chain (ordered with the user's certificate first265* and the root certificate authority last), or null if the given alias266* does not exist or does not contain a certificate chain (i.e., the given267* alias identifies either a <i>trusted certificate entry</i> or a268* <i>key entry</i> without a certificate chain).269*/270public Certificate[] engineGetCertificateChain(String alias) {271permissionCheck();272273Object entry = entries.get(alias.toLowerCase());274275if (entry != null && entry instanceof KeyEntry) {276if (((KeyEntry)entry).chain == null) {277return null;278} else {279return ((KeyEntry)entry).chain.clone();280}281} else {282return null;283}284}285286/**287* Returns the certificate associated with the given alias.288*289* <p>If the given alias name identifies a290* <i>trusted certificate entry</i>, the certificate associated with that291* entry is returned. If the given alias name identifies a292* <i>key entry</i>, the first element of the certificate chain of that293* entry is returned, or null if that entry does not have a certificate294* chain.295*296* @param alias the alias name297*298* @return the certificate, or null if the given alias does not exist or299* does not contain a certificate.300*/301public Certificate engineGetCertificate(String alias) {302permissionCheck();303304Object entry = entries.get(alias.toLowerCase());305306if (entry != null) {307if (entry instanceof TrustedCertEntry) {308return ((TrustedCertEntry)entry).cert;309} else {310KeyEntry ke = (KeyEntry)entry;311if (ke.chain == null || ke.chain.length == 0) {312return null;313}314return ke.chain[0];315}316} else {317return null;318}319}320321private record LocalAttr(String name, String value)322implements KeyStore.Entry.Attribute {323324@Override325public String getName() {326return name;327}328329@Override330public String getValue() {331return value;332}333}334335@Override336public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter protParam)337throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {338if (engineIsCertificateEntry(alias)) {339Object entry = entries.get(alias.toLowerCase());340if (entry instanceof TrustedCertEntry tEntry) {341return new KeyStore.TrustedCertificateEntry(342tEntry.cert, Set.of(343new LocalAttr(KnownOIDs.ORACLE_TrustedKeyUsage.value(), tEntry.trustedKeyUsageValue),344new LocalAttr("trustSettings", tEntry.trustSettings.toString())));345}346}347return super.engineGetEntry(alias, protParam);348}349350/**351* Returns the creation date of the entry identified by the given alias.352*353* @param alias the alias name354*355* @return the creation date of this entry, or null if the given alias does356* not exist357*/358public Date engineGetCreationDate(String alias) {359permissionCheck();360361Object entry = entries.get(alias.toLowerCase());362363if (entry != null) {364if (entry instanceof TrustedCertEntry) {365return new Date(((TrustedCertEntry)entry).date.getTime());366} else {367return new Date(((KeyEntry)entry).date.getTime());368}369} else {370return null;371}372}373374/**375* Assigns the given key to the given alias, protecting it with the given376* password.377*378* <p>If the given key is of type <code>java.security.PrivateKey</code>,379* it must be accompanied by a certificate chain certifying the380* corresponding public key.381*382* <p>If the given alias already exists, the keystore information383* associated with it is overridden by the given key (and possibly384* certificate chain).385*386* @param alias the alias name387* @param key the key to be associated with the alias388* @param password the password to protect the key389* @param chain the certificate chain for the corresponding public390* key (only required if the given key is of type391* <code>java.security.PrivateKey</code>).392*393* @exception KeyStoreException if the given key cannot be protected, or394* this operation fails for some other reason395*/396public void engineSetKeyEntry(String alias, Key key, char[] password,397Certificate[] chain)398throws KeyStoreException399{400permissionCheck();401402synchronized(entries) {403try {404KeyEntry entry = new KeyEntry();405entry.date = new Date();406407if (key instanceof PrivateKey) {408if ((key.getFormat().equals("PKCS#8")) ||409(key.getFormat().equals("PKCS8"))) {410entry.protectedPrivKey = encryptPrivateKey(key.getEncoded(), password);411entry.password = password.clone();412} else {413throw new KeyStoreException("Private key is not encoded as PKCS#8");414}415} else {416throw new KeyStoreException("Key is not a PrivateKey");417}418419// clone the chain420if (chain != null) {421if ((chain.length > 1) && !validateChain(chain)) {422throw new KeyStoreException("Certificate chain does not validate");423}424425entry.chain = chain.clone();426entry.chainRefs = new long[entry.chain.length];427}428429String lowerAlias = alias.toLowerCase();430if (entries.get(lowerAlias) != null) {431deletedEntries.put(lowerAlias, entries.get(lowerAlias));432}433434entries.put(lowerAlias, entry);435addedEntries.put(lowerAlias, entry);436} catch (Exception nsae) {437KeyStoreException ke = new KeyStoreException("Key protection algorithm not found: " + nsae);438ke.initCause(nsae);439throw ke;440}441}442}443444/**445* Assigns the given key (that has already been protected) to the given446* alias.447*448* <p>If the protected key is of type449* <code>java.security.PrivateKey</code>, it must be accompanied by a450* certificate chain certifying the corresponding public key. If the451* underlying keystore implementation is of type <code>jks</code>,452* <code>key</code> must be encoded as an453* <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.454*455* <p>If the given alias already exists, the keystore information456* associated with it is overridden by the given key (and possibly457* certificate chain).458*459* @param alias the alias name460* @param key the key (in protected format) to be associated with the alias461* @param chain the certificate chain for the corresponding public462* key (only useful if the protected key is of type463* <code>java.security.PrivateKey</code>).464*465* @exception KeyStoreException if this operation fails.466*/467public void engineSetKeyEntry(String alias, byte[] key,468Certificate[] chain)469throws KeyStoreException470{471permissionCheck();472473synchronized(entries) {474// key must be encoded as EncryptedPrivateKeyInfo as defined in475// PKCS#8476KeyEntry entry = new KeyEntry();477try {478EncryptedPrivateKeyInfo privateKey = new EncryptedPrivateKeyInfo(key);479entry.protectedPrivKey = privateKey.getEncoded();480} catch (IOException ioe) {481throw new KeyStoreException("key is not encoded as "482+ "EncryptedPrivateKeyInfo");483}484485entry.date = new Date();486487if ((chain != null) &&488(chain.length != 0)) {489entry.chain = chain.clone();490entry.chainRefs = new long[entry.chain.length];491}492493String lowerAlias = alias.toLowerCase();494if (entries.get(lowerAlias) != null) {495deletedEntries.put(lowerAlias, entries.get(alias));496}497entries.put(lowerAlias, entry);498addedEntries.put(lowerAlias, entry);499}500}501502/**503* Adding trusted certificate entry is not supported.504*/505public void engineSetCertificateEntry(String alias, Certificate cert)506throws KeyStoreException {507throw new KeyStoreException("Cannot set trusted certificate entry." +508" Use the macOS \"security add-trusted-cert\" command instead.");509}510511/**512* Deletes the entry identified by the given alias from this keystore.513*514* @param alias the alias name515*516* @exception KeyStoreException if the entry cannot be removed.517*/518public void engineDeleteEntry(String alias)519throws KeyStoreException520{521permissionCheck();522523synchronized(entries) {524Object entry = entries.remove(alias.toLowerCase());525deletedEntries.put(alias.toLowerCase(), entry);526}527}528529/**530* Lists all the alias names of this keystore.531*532* @return enumeration of the alias names533*/534public Enumeration<String> engineAliases() {535permissionCheck();536return entries.keys();537}538539/**540* Checks if the given alias exists in this keystore.541*542* @param alias the alias name543*544* @return true if the alias exists, false otherwise545*/546public boolean engineContainsAlias(String alias) {547permissionCheck();548return entries.containsKey(alias.toLowerCase());549}550551/**552* Retrieves the number of entries in this keystore.553*554* @return the number of entries in this keystore555*/556public int engineSize() {557permissionCheck();558return entries.size();559}560561/**562* Returns true if the entry identified by the given alias is a563* <i>key entry</i>, and false otherwise.564*565* @return true if the entry identified by the given alias is a566* <i>key entry</i>, false otherwise.567*/568public boolean engineIsKeyEntry(String alias) {569permissionCheck();570Object entry = entries.get(alias.toLowerCase());571if ((entry != null) && (entry instanceof KeyEntry)) {572return true;573} else {574return false;575}576}577578/**579* Returns true if the entry identified by the given alias is a580* <i>trusted certificate entry</i>, and false otherwise.581*582* @return true if the entry identified by the given alias is a583* <i>trusted certificate entry</i>, false otherwise.584*/585public boolean engineIsCertificateEntry(String alias) {586permissionCheck();587Object entry = entries.get(alias.toLowerCase());588if ((entry != null) && (entry instanceof TrustedCertEntry)) {589return true;590} else {591return false;592}593}594595/**596* Returns the (alias) name of the first keystore entry whose certificate597* matches the given certificate.598*599* <p>This method attempts to match the given certificate with each600* keystore entry. If the entry being considered601* is a <i>trusted certificate entry</i>, the given certificate is602* compared to that entry's certificate. If the entry being considered is603* a <i>key entry</i>, the given certificate is compared to the first604* element of that entry's certificate chain (if a chain exists).605*606* @param cert the certificate to match with.607*608* @return the (alias) name of the first entry with matching certificate,609* or null if no such entry exists in this keystore.610*/611public String engineGetCertificateAlias(Certificate cert) {612permissionCheck();613Certificate certElem;614615for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {616String alias = e.nextElement();617Object entry = entries.get(alias);618if (entry instanceof TrustedCertEntry) {619certElem = ((TrustedCertEntry)entry).cert;620} else {621KeyEntry ke = (KeyEntry)entry;622if (ke.chain == null || ke.chain.length == 0) {623continue;624}625certElem = ke.chain[0];626}627if (certElem.equals(cert)) {628return alias;629}630}631return null;632}633634/**635* Stores this keystore to the given output stream, and protects its636* integrity with the given password.637*638* @param stream Ignored. the output stream to which this keystore is written.639* @param password the password to generate the keystore integrity check640*641* @exception IOException if there was an I/O problem with data642* @exception NoSuchAlgorithmException if the appropriate data integrity643* algorithm could not be found644* @exception CertificateException if any of the certificates included in645* the keystore data could not be stored646*/647public void engineStore(OutputStream stream, char[] password)648throws IOException, NoSuchAlgorithmException, CertificateException649{650permissionCheck();651652// Delete items that do have a keychain item ref.653for (Enumeration<String> e = deletedEntries.keys(); e.hasMoreElements(); ) {654String alias = e.nextElement();655Object entry = deletedEntries.get(alias);656if (entry instanceof TrustedCertEntry) {657if (((TrustedCertEntry)entry).certRef != 0) {658_removeItemFromKeychain(((TrustedCertEntry)entry).certRef);659_releaseKeychainItemRef(((TrustedCertEntry)entry).certRef);660}661} else {662Certificate certElem;663KeyEntry keyEntry = (KeyEntry)entry;664665if (keyEntry.chain != null) {666for (int i = 0; i < keyEntry.chain.length; i++) {667if (keyEntry.chainRefs[i] != 0) {668_removeItemFromKeychain(keyEntry.chainRefs[i]);669_releaseKeychainItemRef(keyEntry.chainRefs[i]);670}671}672673if (keyEntry.keyRef != 0) {674_removeItemFromKeychain(keyEntry.keyRef);675_releaseKeychainItemRef(keyEntry.keyRef);676}677}678}679}680681// Add all of the certs or keys in the added entries.682// No need to check for 0 refs, as they are in the added list.683for (Enumeration<String> e = addedEntries.keys(); e.hasMoreElements(); ) {684String alias = e.nextElement();685Object entry = addedEntries.get(alias);686if (entry instanceof TrustedCertEntry) {687// Cannot set trusted certificate entry688} else {689KeyEntry keyEntry = (KeyEntry)entry;690691if (keyEntry.chain != null) {692for (int i = 0; i < keyEntry.chain.length; i++) {693keyEntry.chainRefs[i] = addCertificateToKeychain(alias, keyEntry.chain[i]);694}695696keyEntry.keyRef = _addItemToKeychain(alias, false, keyEntry.protectedPrivKey, keyEntry.password);697}698}699}700701// Clear the added and deletedEntries hashtables here, now that we're done with the updates.702// For the deleted entries, we freed up the native references above.703deletedEntries.clear();704addedEntries.clear();705}706707private long addCertificateToKeychain(String alias, Certificate cert) {708byte[] certblob = null;709long returnValue = 0;710711try {712certblob = cert.getEncoded();713returnValue = _addItemToKeychain(alias, true, certblob, null);714} catch (Exception e) {715e.printStackTrace();716}717718return returnValue;719}720721private native long _addItemToKeychain(String alias, boolean isCertificate, byte[] datablob, char[] password);722private native int _removeItemFromKeychain(long certRef);723private native void _releaseKeychainItemRef(long keychainItemRef);724725/**726* Loads the keystore from the Keychain.727*728* @param stream Ignored - here for API compatibility.729* @param password Ignored - if user needs to unlock keychain Security730* framework will post any dialogs.731*732* @exception IOException if there is an I/O or format problem with the733* keystore data734* @exception NoSuchAlgorithmException if the algorithm used to check735* the integrity of the keystore cannot be found736* @exception CertificateException if any of the certificates in the737* keystore could not be loaded738*/739public void engineLoad(InputStream stream, char[] password)740throws IOException, NoSuchAlgorithmException, CertificateException741{742permissionCheck();743744// Release any stray keychain references before clearing out the entries.745synchronized(entries) {746for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {747String alias = e.nextElement();748Object entry = entries.get(alias);749if (entry instanceof TrustedCertEntry) {750if (((TrustedCertEntry)entry).certRef != 0) {751_releaseKeychainItemRef(((TrustedCertEntry)entry).certRef);752}753} else {754KeyEntry keyEntry = (KeyEntry)entry;755756if (keyEntry.chain != null) {757for (int i = 0; i < keyEntry.chain.length; i++) {758if (keyEntry.chainRefs[i] != 0) {759_releaseKeychainItemRef(keyEntry.chainRefs[i]);760}761}762763if (keyEntry.keyRef != 0) {764_releaseKeychainItemRef(keyEntry.keyRef);765}766}767}768}769770entries.clear();771_scanKeychain();772if (debug != null) {773debug.println("KeychainStore load entry count: " +774entries.size());775}776}777}778779private native void _scanKeychain();780781/**782* Callback method from _scanKeychain. If a trusted certificate is found,783* this method will be called.784*785* inputTrust is a list of strings in groups. Each group contains key/value786* pairs for one trust setting and ends with a null. Thus the size of the787* whole list is (2 * s_1 + 1) + (2 * s_2 + 1) + ... + (2 * s_n + 1),788* where s_i is the size of mapping for the i'th trust setting,789* and n is the number of trust settings. Ex:790*791* key1 for trust1792* value1 for trust1793* ..794* null (end of trust1)795* key1 for trust2796* value1 for trust2797* ...798* null (end of trust2)799* ...800* null (end if trust_n)801*/802private void createTrustedCertEntry(String alias, List<String> inputTrust,803long keychainItemRef, long creationDate, byte[] derStream) {804TrustedCertEntry tce = new TrustedCertEntry();805806try {807CertificateFactory cf = CertificateFactory.getInstance("X.509");808InputStream input = new ByteArrayInputStream(derStream);809X509Certificate cert = (X509Certificate) cf.generateCertificate(input);810input.close();811tce.cert = cert;812tce.certRef = keychainItemRef;813814tce.trustSettings = new ArrayList<>();815Map<String,String> tmpMap = new LinkedHashMap<>();816for (int i = 0; i < inputTrust.size(); i++) {817if (inputTrust.get(i) == null) {818tce.trustSettings.add(tmpMap);819if (i < inputTrust.size() - 1) {820// Prepare an empty map for the next trust setting.821// Do not just clear(), must be a new object.822// Only create if not at end of list.823tmpMap = new LinkedHashMap<>();824}825} else {826tmpMap.put(inputTrust.get(i), inputTrust.get(i+1));827i++;828}829}830831boolean isSelfSigned;832try {833cert.verify(cert.getPublicKey());834isSelfSigned = true;835} catch (Exception e) {836isSelfSigned = false;837}838if (tce.trustSettings.isEmpty()) {839if (isSelfSigned) {840// If a self-signed certificate has an empty trust settings,841// trust it for all purposes842tce.trustedKeyUsageValue = KnownOIDs.anyExtendedKeyUsage.value();843} else {844// Otherwise, return immediately. The certificate is not845// added into entries.846return;847}848} else {849List<String> values = new ArrayList<>();850for (var oneTrust : tce.trustSettings) {851var result = oneTrust.get("kSecTrustSettingsResult");852// https://developer.apple.com/documentation/security/sectrustsettingsresult?language=objc853// 1 = kSecTrustSettingsResultTrustRoot, 2 = kSecTrustSettingsResultTrustAsRoot854// If missing, a default value of kSecTrustSettingsResultTrustRoot is assumed855// for self-signed certificates (see doc for SecTrustSettingsCopyTrustSettings).856// Note that the same SecPolicyOid can appear in multiple trust settings857// for different kSecTrustSettingsAllowedError and/or kSecTrustSettingsPolicyString.858if ((result == null && isSelfSigned)859|| "1".equals(result) || "2".equals(result)) {860// When no kSecTrustSettingsPolicy, it means everything861String oid = oneTrust.getOrDefault("SecPolicyOid",862KnownOIDs.anyExtendedKeyUsage.value());863if (!values.contains(oid)) {864values.add(oid);865}866}867}868if (values.isEmpty()) {869return;870}871if (values.size() == 1) {872tce.trustedKeyUsageValue = values.get(0);873} else {874tce.trustedKeyUsageValue = values.toString();875}876}877// Make a creation date.878if (creationDate != 0)879tce.date = new Date(creationDate);880else881tce.date = new Date();882883int uniqueVal = 1;884String originalAlias = alias;885886while (entries.containsKey(alias.toLowerCase())) {887alias = originalAlias + " " + uniqueVal;888uniqueVal++;889}890891entries.put(alias.toLowerCase(), tce);892} catch (Exception e) {893// The certificate will be skipped.894System.err.println("KeychainStore Ignored Exception: " + e);895}896}897898/**899* Callback method from _scanKeychain. If an identity is found, this method will be called to create Java certificate900* and private key objects from the keychain data.901*/902private void createKeyEntry(String alias, long creationDate, long secKeyRef,903long[] secCertificateRefs, byte[][] rawCertData) {904KeyEntry ke = new KeyEntry();905906// First, store off the private key information. This is the easy part.907ke.protectedPrivKey = null;908ke.keyRef = secKeyRef;909910// Make a creation date.911if (creationDate != 0)912ke.date = new Date(creationDate);913else914ke.date = new Date();915916// Next, create X.509 Certificate objects from the raw data. This is complicated917// because a certificate's public key may be too long for Java's default encryption strength.918List<CertKeychainItemPair> createdCerts = new ArrayList<>();919920try {921CertificateFactory cf = CertificateFactory.getInstance("X.509");922923for (int i = 0; i < rawCertData.length; i++) {924try {925InputStream input = new ByteArrayInputStream(rawCertData[i]);926X509Certificate cert = (X509Certificate) cf.generateCertificate(input);927input.close();928929// We successfully created the certificate, so track it and its corresponding SecCertificateRef.930createdCerts.add(new CertKeychainItemPair(secCertificateRefs[i], cert));931} catch (CertificateException e) {932// The certificate will be skipped.933System.err.println("KeychainStore Ignored Exception: " + e);934}935}936} catch (CertificateException e) {937e.printStackTrace();938} catch (IOException ioe) {939ioe.printStackTrace(); // How would this happen?940}941942// We have our certificates in the List, so now extract them into an array of943// Certificates and SecCertificateRefs.944CertKeychainItemPair[] objArray = createdCerts.toArray(new CertKeychainItemPair[0]);945Certificate[] certArray = new Certificate[objArray.length];946long[] certRefArray = new long[objArray.length];947948for (int i = 0; i < objArray.length; i++) {949CertKeychainItemPair addedItem = objArray[i];950certArray[i] = addedItem.mCert;951certRefArray[i] = addedItem.mCertificateRef;952}953954ke.chain = certArray;955ke.chainRefs = certRefArray;956957// If we don't have already have an item with this item's alias958// create a new one for it.959int uniqueVal = 1;960String originalAlias = alias;961962while (entries.containsKey(alias.toLowerCase())) {963alias = originalAlias + " " + uniqueVal;964uniqueVal++;965}966967entries.put(alias.toLowerCase(), ke);968}969970private static class CertKeychainItemPair {971long mCertificateRef;972Certificate mCert;973974CertKeychainItemPair(long inCertRef, Certificate cert) {975mCertificateRef = inCertRef;976mCert = cert;977}978}979980/*981* Validate Certificate Chain982*/983private boolean validateChain(Certificate[] certChain)984{985for (int i = 0; i < certChain.length-1; i++) {986X500Principal issuerDN =987((X509Certificate)certChain[i]).getIssuerX500Principal();988X500Principal subjectDN =989((X509Certificate)certChain[i+1]).getSubjectX500Principal();990if (!(issuerDN.equals(subjectDN)))991return false;992}993return true;994}995996private byte[] fetchPrivateKeyFromBag(byte[] privateKeyInfo) throws IOException, NoSuchAlgorithmException, CertificateException997{998byte[] returnValue = null;999DerValue val = new DerValue(new ByteArrayInputStream(privateKeyInfo));1000DerInputStream s = val.toDerInputStream();1001int version = s.getInteger();10021003if (version != 3) {1004throw new IOException("PKCS12 keystore not in version 3 format");1005}10061007/*1008* Read the authSafe.1009*/1010byte[] authSafeData;1011ContentInfo authSafe = new ContentInfo(s);1012ObjectIdentifier contentType = authSafe.getContentType();10131014if (contentType.equals(ContentInfo.DATA_OID)) {1015authSafeData = authSafe.getData();1016} else /* signed data */ {1017throw new IOException("public key protected PKCS12 not supported");1018}10191020DerInputStream as = new DerInputStream(authSafeData);1021DerValue[] safeContentsArray = as.getSequence(2);1022int count = safeContentsArray.length;10231024/*1025* Spin over the ContentInfos.1026*/1027for (int i = 0; i < count; i++) {1028byte[] safeContentsData;1029ContentInfo safeContents;1030DerInputStream sci;1031byte[] eAlgId = null;10321033sci = new DerInputStream(safeContentsArray[i].toByteArray());1034safeContents = new ContentInfo(sci);1035contentType = safeContents.getContentType();1036safeContentsData = null;10371038if (contentType.equals(ContentInfo.DATA_OID)) {1039safeContentsData = safeContents.getData();1040} else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) {1041// The password was used to export the private key from the keychain.1042// The Keychain won't export the key with encrypted data, so we don't need1043// to worry about it.1044continue;1045} else {1046throw new IOException("public key protected PKCS12" +1047" not supported");1048}1049DerInputStream sc = new DerInputStream(safeContentsData);1050returnValue = extractKeyData(sc);1051}10521053return returnValue;1054}10551056private byte[] extractKeyData(DerInputStream stream)1057throws IOException, NoSuchAlgorithmException, CertificateException1058{1059byte[] returnValue = null;1060DerValue[] safeBags = stream.getSequence(2);1061int count = safeBags.length;10621063/*1064* Spin over the SafeBags.1065*/1066for (int i = 0; i < count; i++) {1067ObjectIdentifier bagId;1068DerInputStream sbi;1069DerValue bagValue;1070Object bagItem = null;10711072sbi = safeBags[i].toDerInputStream();1073bagId = sbi.getOID();1074bagValue = sbi.getDerValue();1075if (!bagValue.isContextSpecific((byte)0)) {1076throw new IOException("unsupported PKCS12 bag value type "1077+ bagValue.tag);1078}1079bagValue = bagValue.data.getDerValue();1080if (bagId.equals(PKCS8ShroudedKeyBag_OID)) {1081// got what we were looking for. Return it.1082returnValue = bagValue.toByteArray();1083} else {1084// log error message for "unsupported PKCS12 bag type"1085System.out.println("Unsupported bag type '" + bagId + "'");1086}1087}10881089return returnValue;1090}10911092/*1093* Generate PBE Algorithm Parameters1094*/1095private AlgorithmParameters getAlgorithmParameters(String algorithm)1096throws IOException1097{1098AlgorithmParameters algParams = null;10991100// create PBE parameters from salt and iteration count1101PBEParameterSpec paramSpec =1102new PBEParameterSpec(getSalt(), iterationCount);1103try {1104algParams = AlgorithmParameters.getInstance(algorithm);1105algParams.init(paramSpec);1106} catch (Exception e) {1107IOException ioe =1108new IOException("getAlgorithmParameters failed: " +1109e.getMessage());1110ioe.initCause(e);1111throw ioe;1112}1113return algParams;1114}11151116// the source of randomness1117private SecureRandom random;11181119/*1120* Generate random salt1121*/1122private byte[] getSalt()1123{1124// Generate a random salt.1125byte[] salt = new byte[SALT_LEN];1126if (random == null) {1127random = new SecureRandom();1128}1129random.nextBytes(salt);1130return salt;1131}11321133/*1134* parse Algorithm Parameters1135*/1136private AlgorithmParameters parseAlgParameters(DerInputStream in)1137throws IOException1138{1139AlgorithmParameters algParams = null;1140try {1141DerValue params;1142if (in.available() == 0) {1143params = null;1144} else {1145params = in.getDerValue();1146if (params.tag == DerValue.tag_Null) {1147params = null;1148}1149}1150if (params != null) {1151algParams = AlgorithmParameters.getInstance("PBE");1152algParams.init(params.toByteArray());1153}1154} catch (Exception e) {1155IOException ioe =1156new IOException("parseAlgParameters failed: " +1157e.getMessage());1158ioe.initCause(e);1159throw ioe;1160}1161return algParams;1162}11631164/*1165* Generate PBE key1166*/1167private SecretKey getPBEKey(char[] password) throws IOException1168{1169SecretKey skey = null;11701171try {1172PBEKeySpec keySpec = new PBEKeySpec(password);1173SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE");1174skey = skFac.generateSecret(keySpec);1175} catch (Exception e) {1176IOException ioe = new IOException("getSecretKey failed: " +1177e.getMessage());1178ioe.initCause(e);1179throw ioe;1180}1181return skey;1182}11831184/*1185* Encrypt private key using Password-based encryption (PBE)1186* as defined in PKCS#5.1187*1188* NOTE: Currently pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is1189* used to derive the key and IV.1190*1191* @return encrypted private key encoded as EncryptedPrivateKeyInfo1192*/1193private byte[] encryptPrivateKey(byte[] data, char[] password)1194throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException1195{1196byte[] key = null;11971198try {1199// create AlgorithmParameters1200AlgorithmParameters algParams =1201getAlgorithmParameters("PBEWithSHA1AndDESede");12021203// Use JCE1204SecretKey skey = getPBEKey(password);1205Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede");1206cipher.init(Cipher.ENCRYPT_MODE, skey, algParams);1207byte[] encryptedKey = cipher.doFinal(data);12081209// wrap encrypted private key in EncryptedPrivateKeyInfo1210// as defined in PKCS#81211AlgorithmId algid =1212new AlgorithmId(pbeWithSHAAnd3KeyTripleDESCBC_OID, algParams);1213EncryptedPrivateKeyInfo encrInfo =1214new EncryptedPrivateKeyInfo(algid, encryptedKey);1215key = encrInfo.getEncoded();1216} catch (Exception e) {1217UnrecoverableKeyException uke =1218new UnrecoverableKeyException("Encrypt Private Key failed: "1219+ e.getMessage());1220uke.initCause(e);1221throw uke;1222}12231224return key;1225}122612271228}1229123012311232