1 /*
   2  * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package apple.security;
  27 
  28 import java.io.*;
  29 import java.security.*;
  30 import java.security.cert.*;
  31 import java.security.cert.Certificate;
  32 import java.security.spec.*;
  33 import java.util.*;
  34 
  35 import javax.crypto.*;
  36 import javax.crypto.spec.*;
  37 import javax.security.auth.x500.*;
  38 
  39 import sun.security.pkcs.*;
  40 import sun.security.pkcs.EncryptedPrivateKeyInfo;
  41 import sun.security.util.*;
  42 import sun.security.x509.*;
  43 
  44 /**
  45  * This class provides the keystore implementation referred to as "KeychainStore".
  46  * It uses the current user's keychain as its backing storage, and does NOT support
  47  * a file-based implementation.
  48  */
  49 
  50 public final class KeychainStore extends KeyStoreSpi {
  51 
  52     // Private keys and their supporting certificate chains
  53     // If a key came from the keychain it has a SecKeyRef and one or more
  54     // SecCertificateRef.  When we delete the key we have to delete all of the corresponding
  55     // native objects.
  56     class KeyEntry {
  57         Date date; // the creation date of this entry
  58         byte[] protectedPrivKey;
  59         char[] password;
  60         long keyRef;  // SecKeyRef for this key
  61         Certificate chain[];
  62         long chainRefs[];  // SecCertificateRefs for this key's chain.
  63     };
  64 
  65     // Trusted certificates
  66     class TrustedCertEntry {
  67         Date date; // the creation date of this entry
  68 
  69         Certificate cert;
  70         long certRef;  // SecCertificateRef for this key
  71     };
  72 
  73     /**
  74      * Entries that have been deleted.  When something calls engineStore we'll
  75      * remove them from the keychain.
  76      */
  77     private Hashtable<String, Object> deletedEntries = new Hashtable<>();
  78 
  79     /**
  80      * Entries that have been added.  When something calls engineStore we'll
  81      * add them to the keychain.
  82      */
  83     private Hashtable<String, Object> addedEntries = new Hashtable<>();
  84 
  85     /**
  86      * Private keys and certificates are stored in a hashtable.
  87      * Hash entries are keyed by alias names.
  88      */
  89     private Hashtable<String, Object> entries = new Hashtable<>();
  90 
  91     /**
  92      * Algorithm identifiers and corresponding OIDs for the contents of the
  93      * PKCS12 bag we get from the Keychain.
  94      */
  95     private static ObjectIdentifier PKCS8ShroudedKeyBag_OID =
  96             ObjectIdentifier.of("1.2.840.113549.1.12.10.1.2");
  97     private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID =
  98             ObjectIdentifier.of("1.2.840.113549.1.12.1.3");
  99 
 100     /**
 101      * Constnats used in PBE decryption.
 102      */
 103     private static final int iterationCount = 1024;
 104     private static final int SALT_LEN = 20;
 105 
 106     private static final Debug debug = Debug.getInstance("keystore");
 107 
 108     static {
 109         sun.security.action.LoadLibraryAction.privilegedLoadLibrary("osxsecurity");
 110     }
 111 
 112     private static void permissionCheck() {
 113         SecurityManager sec = System.getSecurityManager();
 114 
 115         if (sec != null) {
 116             sec.checkPermission(new RuntimePermission("useKeychainStore"));
 117         }
 118     }
 119 
 120 
 121     /**
 122      * Verify the Apple provider in the constructor.
 123      *
 124      * @exception SecurityException if fails to verify
 125      * its own integrity
 126      */
 127     public KeychainStore() { }
 128 
 129     /**
 130         * Returns the key associated with the given alias, using the given
 131      * password to recover it.
 132      *
 133      * @param alias the alias name
 134      * @param password the password for recovering the key. This password is
 135      *        used internally as the key is exported in a PKCS12 format.
 136      *
 137      * @return the requested key, or null if the given alias does not exist
 138      * or does not identify a <i>key entry</i>.
 139      *
 140      * @exception NoSuchAlgorithmException if the algorithm for recovering the
 141      * key cannot be found
 142      * @exception UnrecoverableKeyException if the key cannot be recovered
 143      * (e.g., the given password is wrong).
 144      */
 145     public Key engineGetKey(String alias, char[] password)
 146         throws NoSuchAlgorithmException, UnrecoverableKeyException
 147     {
 148         permissionCheck();
 149 
 150         // An empty password is rejected by MacOS API, no private key data
 151         // is exported. If no password is passed (as is the case when
 152         // this implementation is used as browser keystore in various
 153         // deployment scenarios like Webstart, JFX and applets), create
 154         // a dummy password so MacOS API is happy.
 155         if (password == null || password.length == 0) {
 156             // Must not be a char array with only a 0, as this is an empty
 157             // string.
 158             if (random == null) {
 159                 random = new SecureRandom();
 160             }
 161             password = Long.toString(random.nextLong()).toCharArray();
 162         }
 163 
 164         Object entry = entries.get(alias.toLowerCase());
 165 
 166         if (entry == null || !(entry instanceof KeyEntry)) {
 167             return null;
 168         }
 169 
 170         // This call gives us a PKCS12 bag, with the key inside it.
 171         byte[] exportedKeyInfo = _getEncodedKeyData(((KeyEntry)entry).keyRef, password);
 172         if (exportedKeyInfo == null) {
 173             return null;
 174         }
 175 
 176         PrivateKey returnValue = null;
 177 
 178         try {
 179             byte[] pkcs8KeyData = fetchPrivateKeyFromBag(exportedKeyInfo);
 180             byte[] encryptedKey;
 181             AlgorithmParameters algParams;
 182             ObjectIdentifier algOid;
 183             try {
 184                 // get the encrypted private key
 185                 EncryptedPrivateKeyInfo encrInfo = new EncryptedPrivateKeyInfo(pkcs8KeyData);
 186                 encryptedKey = encrInfo.getEncryptedData();
 187 
 188                 // parse Algorithm parameters
 189                 DerValue val = new DerValue(encrInfo.getAlgorithm().encode());
 190                 DerInputStream in = val.toDerInputStream();
 191                 algOid = in.getOID();
 192                 algParams = parseAlgParameters(in);
 193 
 194             } catch (IOException ioe) {
 195                 UnrecoverableKeyException uke =
 196                 new UnrecoverableKeyException("Private key not stored as "
 197                                               + "PKCS#8 EncryptedPrivateKeyInfo: " + ioe);
 198                 uke.initCause(ioe);
 199                 throw uke;
 200             }
 201 
 202             // Use JCE to decrypt the data using the supplied password.
 203             SecretKey skey = getPBEKey(password);
 204             Cipher cipher = Cipher.getInstance(algOid.toString());
 205             cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
 206             byte[] decryptedPrivateKey = cipher.doFinal(encryptedKey);
 207             PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(decryptedPrivateKey);
 208 
 209              // Parse the key algorithm and then use a JCA key factory to create the private key.
 210             DerValue val = new DerValue(decryptedPrivateKey);
 211             DerInputStream in = val.toDerInputStream();
 212 
 213             // Ignore this -- version should be 0.
 214             int i = in.getInteger();
 215 
 216             // Get the Algorithm ID next
 217             DerValue[] value = in.getSequence(2);
 218             AlgorithmId algId = new AlgorithmId(value[0].getOID());
 219             String algName = algId.getName();
 220 
 221             // Get a key factory for this algorithm.  It's likely to be 'RSA'.
 222             KeyFactory kfac = KeyFactory.getInstance(algName);
 223             returnValue = kfac.generatePrivate(kspec);
 224         } catch (Exception e) {
 225             UnrecoverableKeyException uke =
 226             new UnrecoverableKeyException("Get Key failed: " +
 227                                           e.getMessage());
 228             uke.initCause(e);
 229             throw uke;
 230         }
 231 
 232         return returnValue;
 233     }
 234 
 235     private native byte[] _getEncodedKeyData(long secKeyRef, char[] password);
 236 
 237     /**
 238      * Returns the certificate chain associated with the given alias.
 239      *
 240      * @param alias the alias name
 241      *
 242      * @return the certificate chain (ordered with the user's certificate first
 243                                       * and the root certificate authority last), or null if the given alias
 244      * does not exist or does not contain a certificate chain (i.e., the given
 245                                                                * alias identifies either a <i>trusted certificate entry</i> or a
 246                                                                * <i>key entry</i> without a certificate chain).
 247      */
 248     public Certificate[] engineGetCertificateChain(String alias) {
 249         permissionCheck();
 250 
 251         Object entry = entries.get(alias.toLowerCase());
 252 
 253         if (entry != null && entry instanceof KeyEntry) {
 254             if (((KeyEntry)entry).chain == null) {
 255                 return null;
 256             } else {
 257                 return ((KeyEntry)entry).chain.clone();
 258             }
 259         } else {
 260             return null;
 261         }
 262     }
 263 
 264     /**
 265      * Returns the certificate associated with the given alias.
 266      *
 267      * <p>If the given alias name identifies a
 268      * <i>trusted certificate entry</i>, the certificate associated with that
 269      * entry is returned. If the given alias name identifies a
 270      * <i>key entry</i>, the first element of the certificate chain of that
 271      * entry is returned, or null if that entry does not have a certificate
 272      * chain.
 273      *
 274      * @param alias the alias name
 275      *
 276      * @return the certificate, or null if the given alias does not exist or
 277      * does not contain a certificate.
 278      */
 279     public Certificate engineGetCertificate(String alias) {
 280         permissionCheck();
 281 
 282         Object entry = entries.get(alias.toLowerCase());
 283 
 284         if (entry != null) {
 285             if (entry instanceof TrustedCertEntry) {
 286                 return ((TrustedCertEntry)entry).cert;
 287             } else {
 288                 KeyEntry ke = (KeyEntry)entry;
 289                 if (ke.chain == null || ke.chain.length == 0) {
 290                     return null;
 291                 }
 292                 return ke.chain[0];
 293             }
 294         } else {
 295             return null;
 296         }
 297     }
 298 
 299     /**
 300         * Returns the creation date of the entry identified by the given alias.
 301      *
 302      * @param alias the alias name
 303      *
 304      * @return the creation date of this entry, or null if the given alias does
 305      * not exist
 306      */
 307     public Date engineGetCreationDate(String alias) {
 308         permissionCheck();
 309 
 310         Object entry = entries.get(alias.toLowerCase());
 311 
 312         if (entry != null) {
 313             if (entry instanceof TrustedCertEntry) {
 314                 return new Date(((TrustedCertEntry)entry).date.getTime());
 315             } else {
 316                 return new Date(((KeyEntry)entry).date.getTime());
 317             }
 318         } else {
 319             return null;
 320         }
 321     }
 322 
 323     /**
 324         * Assigns the given key to the given alias, protecting it with the given
 325      * password.
 326      *
 327      * <p>If the given key is of type <code>java.security.PrivateKey</code>,
 328      * it must be accompanied by a certificate chain certifying the
 329      * corresponding public key.
 330      *
 331      * <p>If the given alias already exists, the keystore information
 332      * associated with it is overridden by the given key (and possibly
 333                                                           * certificate chain).
 334      *
 335      * @param alias the alias name
 336      * @param key the key to be associated with the alias
 337      * @param password the password to protect the key
 338      * @param chain the certificate chain for the corresponding public
 339      * key (only required if the given key is of type
 340             * <code>java.security.PrivateKey</code>).
 341      *
 342      * @exception KeyStoreException if the given key cannot be protected, or
 343      * this operation fails for some other reason
 344      */
 345     public void engineSetKeyEntry(String alias, Key key, char[] password,
 346                                   Certificate[] chain)
 347         throws KeyStoreException
 348     {
 349         permissionCheck();
 350 
 351         synchronized(entries) {
 352             try {
 353                 KeyEntry entry = new KeyEntry();
 354                 entry.date = new Date();
 355 
 356                 if (key instanceof PrivateKey) {
 357                     if ((key.getFormat().equals("PKCS#8")) ||
 358                         (key.getFormat().equals("PKCS8"))) {
 359                         entry.protectedPrivKey = encryptPrivateKey(key.getEncoded(), password);
 360                         entry.password = password.clone();
 361                     } else {
 362                         throw new KeyStoreException("Private key is not encoded as PKCS#8");
 363                     }
 364                 } else {
 365                     throw new KeyStoreException("Key is not a PrivateKey");
 366                 }
 367 
 368                 // clone the chain
 369                 if (chain != null) {
 370                     if ((chain.length > 1) && !validateChain(chain)) {
 371                         throw new KeyStoreException("Certificate chain does not validate");
 372                     }
 373 
 374                     entry.chain = chain.clone();
 375                     entry.chainRefs = new long[entry.chain.length];
 376                 }
 377 
 378                 String lowerAlias = alias.toLowerCase();
 379                 if (entries.get(lowerAlias) != null) {
 380                     deletedEntries.put(lowerAlias, entries.get(lowerAlias));
 381                 }
 382 
 383                 entries.put(lowerAlias, entry);
 384                 addedEntries.put(lowerAlias, entry);
 385             } catch (Exception nsae) {
 386                 KeyStoreException ke = new KeyStoreException("Key protection algorithm not found: " + nsae);
 387                 ke.initCause(nsae);
 388                 throw ke;
 389             }
 390         }
 391     }
 392 
 393     /**
 394         * Assigns the given key (that has already been protected) to the given
 395      * alias.
 396      *
 397      * <p>If the protected key is of type
 398      * <code>java.security.PrivateKey</code>, it must be accompanied by a
 399      * certificate chain certifying the corresponding public key. If the
 400      * underlying keystore implementation is of type <code>jks</code>,
 401      * <code>key</code> must be encoded as an
 402      * <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.
 403      *
 404      * <p>If the given alias already exists, the keystore information
 405      * associated with it is overridden by the given key (and possibly
 406                                                           * certificate chain).
 407      *
 408      * @param alias the alias name
 409      * @param key the key (in protected format) to be associated with the alias
 410      * @param chain the certificate chain for the corresponding public
 411      * key (only useful if the protected key is of type
 412             * <code>java.security.PrivateKey</code>).
 413      *
 414      * @exception KeyStoreException if this operation fails.
 415      */
 416     public void engineSetKeyEntry(String alias, byte[] key,
 417                                   Certificate[] chain)
 418         throws KeyStoreException
 419     {
 420         permissionCheck();
 421 
 422         synchronized(entries) {
 423             // key must be encoded as EncryptedPrivateKeyInfo as defined in
 424             // PKCS#8
 425             KeyEntry entry = new KeyEntry();
 426             try {
 427                 EncryptedPrivateKeyInfo privateKey = new EncryptedPrivateKeyInfo(key);
 428                 entry.protectedPrivKey = privateKey.getEncoded();
 429             } catch (IOException ioe) {
 430                 throw new KeyStoreException("key is not encoded as "
 431                                             + "EncryptedPrivateKeyInfo");
 432             }
 433 
 434             entry.date = new Date();
 435 
 436             if ((chain != null) &&
 437                 (chain.length != 0)) {
 438                 entry.chain = chain.clone();
 439                 entry.chainRefs = new long[entry.chain.length];
 440             }
 441 
 442             String lowerAlias = alias.toLowerCase();
 443             if (entries.get(lowerAlias) != null) {
 444                 deletedEntries.put(lowerAlias, entries.get(alias));
 445             }
 446             entries.put(lowerAlias, entry);
 447             addedEntries.put(lowerAlias, entry);
 448         }
 449     }
 450 
 451     /**
 452         * Assigns the given certificate to the given alias.
 453      *
 454      * <p>If the given alias already exists in this keystore and identifies a
 455      * <i>trusted certificate entry</i>, the certificate associated with it is
 456      * overridden by the given certificate.
 457      *
 458      * @param alias the alias name
 459      * @param cert the certificate
 460      *
 461      * @exception KeyStoreException if the given alias already exists and does
 462      * not identify a <i>trusted certificate entry</i>, or this operation
 463      * fails for some other reason.
 464      */
 465     public void engineSetCertificateEntry(String alias, Certificate cert)
 466         throws KeyStoreException
 467     {
 468         permissionCheck();
 469 
 470         synchronized(entries) {
 471 
 472             Object entry = entries.get(alias.toLowerCase());
 473             if ((entry != null) && (entry instanceof KeyEntry)) {
 474                 throw new KeyStoreException
 475                 ("Cannot overwrite key entry with certificate");
 476             }
 477 
 478             // This will be slow, but necessary.  Enumerate the values and then see if the cert matches the one in the trusted cert entry.
 479             // Security framework doesn't support the same certificate twice in a keychain.
 480             Collection<Object> allValues = entries.values();
 481 
 482             for (Object value : allValues) {
 483                 if (value instanceof TrustedCertEntry) {
 484                     TrustedCertEntry tce = (TrustedCertEntry)value;
 485                     if (tce.cert.equals(cert)) {
 486                         throw new KeyStoreException("Keychain does not support mulitple copies of same certificate.");
 487                     }
 488                 }
 489             }
 490 
 491             TrustedCertEntry trustedCertEntry = new TrustedCertEntry();
 492             trustedCertEntry.cert = cert;
 493             trustedCertEntry.date = new Date();
 494             String lowerAlias = alias.toLowerCase();
 495             if (entries.get(lowerAlias) != null) {
 496                 deletedEntries.put(lowerAlias, entries.get(lowerAlias));
 497             }
 498             entries.put(lowerAlias, trustedCertEntry);
 499             addedEntries.put(lowerAlias, trustedCertEntry);
 500         }
 501     }
 502 
 503     /**
 504         * Deletes the entry identified by the given alias from this keystore.
 505      *
 506      * @param alias the alias name
 507      *
 508      * @exception KeyStoreException if the entry cannot be removed.
 509      */
 510     public void engineDeleteEntry(String alias)
 511         throws KeyStoreException
 512     {
 513         permissionCheck();
 514 
 515         synchronized(entries) {
 516             Object entry = entries.remove(alias.toLowerCase());
 517             deletedEntries.put(alias.toLowerCase(), entry);
 518         }
 519     }
 520 
 521     /**
 522         * Lists all the alias names of this keystore.
 523      *
 524      * @return enumeration of the alias names
 525      */
 526     public Enumeration<String> engineAliases() {
 527         permissionCheck();
 528         return entries.keys();
 529     }
 530 
 531     /**
 532         * Checks if the given alias exists in this keystore.
 533      *
 534      * @param alias the alias name
 535      *
 536      * @return true if the alias exists, false otherwise
 537      */
 538     public boolean engineContainsAlias(String alias) {
 539         permissionCheck();
 540         return entries.containsKey(alias.toLowerCase());
 541     }
 542 
 543     /**
 544         * Retrieves the number of entries in this keystore.
 545      *
 546      * @return the number of entries in this keystore
 547      */
 548     public int engineSize() {
 549         permissionCheck();
 550         return entries.size();
 551     }
 552 
 553     /**
 554         * Returns true if the entry identified by the given alias is a
 555      * <i>key entry</i>, and false otherwise.
 556      *
 557      * @return true if the entry identified by the given alias is a
 558      * <i>key entry</i>, false otherwise.
 559      */
 560     public boolean engineIsKeyEntry(String alias) {
 561         permissionCheck();
 562         Object entry = entries.get(alias.toLowerCase());
 563         if ((entry != null) && (entry instanceof KeyEntry)) {
 564             return true;
 565         } else {
 566             return false;
 567         }
 568     }
 569 
 570     /**
 571         * Returns true if the entry identified by the given alias is a
 572      * <i>trusted certificate entry</i>, and false otherwise.
 573      *
 574      * @return true if the entry identified by the given alias is a
 575      * <i>trusted certificate entry</i>, false otherwise.
 576      */
 577     public boolean engineIsCertificateEntry(String alias) {
 578         permissionCheck();
 579         Object entry = entries.get(alias.toLowerCase());
 580         if ((entry != null) && (entry instanceof TrustedCertEntry)) {
 581             return true;
 582         } else {
 583             return false;
 584         }
 585     }
 586 
 587     /**
 588         * Returns the (alias) name of the first keystore entry whose certificate
 589      * matches the given certificate.
 590      *
 591      * <p>This method attempts to match the given certificate with each
 592      * keystore entry. If the entry being considered
 593      * is a <i>trusted certificate entry</i>, the given certificate is
 594      * compared to that entry's certificate. If the entry being considered is
 595      * a <i>key entry</i>, the given certificate is compared to the first
 596      * element of that entry's certificate chain (if a chain exists).
 597      *
 598      * @param cert the certificate to match with.
 599      *
 600      * @return the (alias) name of the first entry with matching certificate,
 601      * or null if no such entry exists in this keystore.
 602      */
 603     public String engineGetCertificateAlias(Certificate cert) {
 604         permissionCheck();
 605         Certificate certElem;
 606 
 607         for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {
 608             String alias = e.nextElement();
 609             Object entry = entries.get(alias);
 610             if (entry instanceof TrustedCertEntry) {
 611                 certElem = ((TrustedCertEntry)entry).cert;
 612             } else {
 613                 KeyEntry ke = (KeyEntry)entry;
 614                 if (ke.chain == null || ke.chain.length == 0) {
 615                     continue;
 616                 }
 617                 certElem = ke.chain[0];
 618             }
 619             if (certElem.equals(cert)) {
 620                 return alias;
 621             }
 622         }
 623         return null;
 624     }
 625 
 626     /**
 627         * Stores this keystore to the given output stream, and protects its
 628      * integrity with the given password.
 629      *
 630      * @param stream Ignored. the output stream to which this keystore is written.
 631      * @param password the password to generate the keystore integrity check
 632      *
 633      * @exception IOException if there was an I/O problem with data
 634      * @exception NoSuchAlgorithmException if the appropriate data integrity
 635      * algorithm could not be found
 636      * @exception CertificateException if any of the certificates included in
 637      * the keystore data could not be stored
 638      */
 639     public void engineStore(OutputStream stream, char[] password)
 640         throws IOException, NoSuchAlgorithmException, CertificateException
 641     {
 642         permissionCheck();
 643 
 644         // Delete items that do have a keychain item ref.
 645         for (Enumeration<String> e = deletedEntries.keys(); e.hasMoreElements(); ) {
 646             String alias = e.nextElement();
 647             Object entry = deletedEntries.get(alias);
 648             if (entry instanceof TrustedCertEntry) {
 649                 if (((TrustedCertEntry)entry).certRef != 0) {
 650                     _removeItemFromKeychain(((TrustedCertEntry)entry).certRef);
 651                     _releaseKeychainItemRef(((TrustedCertEntry)entry).certRef);
 652                 }
 653             } else {
 654                 Certificate certElem;
 655                 KeyEntry keyEntry = (KeyEntry)entry;
 656 
 657                 if (keyEntry.chain != null) {
 658                     for (int i = 0; i < keyEntry.chain.length; i++) {
 659                         if (keyEntry.chainRefs[i] != 0) {
 660                             _removeItemFromKeychain(keyEntry.chainRefs[i]);
 661                             _releaseKeychainItemRef(keyEntry.chainRefs[i]);
 662                         }
 663                     }
 664 
 665                     if (keyEntry.keyRef != 0) {
 666                         _removeItemFromKeychain(keyEntry.keyRef);
 667                         _releaseKeychainItemRef(keyEntry.keyRef);
 668                     }
 669                 }
 670             }
 671         }
 672 
 673         // Add all of the certs or keys in the added entries.
 674         // No need to check for 0 refs, as they are in the added list.
 675         for (Enumeration<String> e = addedEntries.keys(); e.hasMoreElements(); ) {
 676             String alias = e.nextElement();
 677             Object entry = addedEntries.get(alias);
 678             if (entry instanceof TrustedCertEntry) {
 679                 TrustedCertEntry tce = (TrustedCertEntry)entry;
 680                 Certificate certElem;
 681                 certElem = tce.cert;
 682                 tce.certRef = addCertificateToKeychain(alias, certElem);
 683             } else {
 684                 KeyEntry keyEntry = (KeyEntry)entry;
 685 
 686                 if (keyEntry.chain != null) {
 687                     for (int i = 0; i < keyEntry.chain.length; i++) {
 688                         keyEntry.chainRefs[i] = addCertificateToKeychain(alias, keyEntry.chain[i]);
 689                     }
 690 
 691                     keyEntry.keyRef = _addItemToKeychain(alias, false, keyEntry.protectedPrivKey, keyEntry.password);
 692                 }
 693             }
 694         }
 695 
 696         // Clear the added and deletedEntries hashtables here, now that we're done with the updates.
 697         // For the deleted entries, we freed up the native references above.
 698         deletedEntries.clear();
 699         addedEntries.clear();
 700     }
 701 
 702     private long addCertificateToKeychain(String alias, Certificate cert) {
 703         byte[] certblob = null;
 704         long returnValue = 0;
 705 
 706         try {
 707             certblob = cert.getEncoded();
 708             returnValue = _addItemToKeychain(alias, true, certblob, null);
 709         } catch (Exception e) {
 710             e.printStackTrace();
 711         }
 712 
 713         return returnValue;
 714     }
 715 
 716     private native long _addItemToKeychain(String alias, boolean isCertificate, byte[] datablob, char[] password);
 717     private native int _removeItemFromKeychain(long certRef);
 718     private native void _releaseKeychainItemRef(long keychainItemRef);
 719 
 720     /**
 721       * Loads the keystore from the Keychain.
 722      *
 723      * @param stream Ignored - here for API compatibility.
 724      * @param password Ignored - if user needs to unlock keychain Security
 725      * framework will post any dialogs.
 726      *
 727      * @exception IOException if there is an I/O or format problem with the
 728      * keystore data
 729      * @exception NoSuchAlgorithmException if the algorithm used to check
 730      * the integrity of the keystore cannot be found
 731      * @exception CertificateException if any of the certificates in the
 732      * keystore could not be loaded
 733      */
 734     public void engineLoad(InputStream stream, char[] password)
 735         throws IOException, NoSuchAlgorithmException, CertificateException
 736     {
 737         permissionCheck();
 738 
 739         // Release any stray keychain references before clearing out the entries.
 740         synchronized(entries) {
 741             for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {
 742                 String alias = e.nextElement();
 743                 Object entry = entries.get(alias);
 744                 if (entry instanceof TrustedCertEntry) {
 745                     if (((TrustedCertEntry)entry).certRef != 0) {
 746                         _releaseKeychainItemRef(((TrustedCertEntry)entry).certRef);
 747                     }
 748                 } else {
 749                     KeyEntry keyEntry = (KeyEntry)entry;
 750 
 751                     if (keyEntry.chain != null) {
 752                         for (int i = 0; i < keyEntry.chain.length; i++) {
 753                             if (keyEntry.chainRefs[i] != 0) {
 754                                 _releaseKeychainItemRef(keyEntry.chainRefs[i]);
 755                             }
 756                         }
 757 
 758                         if (keyEntry.keyRef != 0) {
 759                             _releaseKeychainItemRef(keyEntry.keyRef);
 760                         }
 761                     }
 762                 }
 763             }
 764 
 765             entries.clear();
 766             _scanKeychain();
 767             if (debug != null) {
 768                 debug.println("KeychainStore load entry count: " +
 769                         entries.size());
 770             }
 771         }
 772     }
 773 
 774     private native void _scanKeychain();
 775 
 776     /**
 777      * Callback method from _scanKeychain.  If a trusted certificate is found, this method will be called.
 778      */
 779     private void createTrustedCertEntry(String alias, long keychainItemRef, long creationDate, byte[] derStream) {
 780         TrustedCertEntry tce = new TrustedCertEntry();
 781 
 782         try {
 783             CertificateFactory cf = CertificateFactory.getInstance("X.509");
 784             InputStream input = new ByteArrayInputStream(derStream);
 785             X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
 786             input.close();
 787             tce.cert = cert;
 788             tce.certRef = keychainItemRef;
 789 
 790             // Make a creation date.
 791             if (creationDate != 0)
 792                 tce.date = new Date(creationDate);
 793             else
 794                 tce.date = new Date();
 795 
 796             int uniqueVal = 1;
 797             String originalAlias = alias;
 798 
 799             while (entries.containsKey(alias.toLowerCase())) {
 800                 alias = originalAlias + " " + uniqueVal;
 801                 uniqueVal++;
 802             }
 803 
 804             entries.put(alias.toLowerCase(), tce);
 805         } catch (Exception e) {
 806             // The certificate will be skipped.
 807             System.err.println("KeychainStore Ignored Exception: " + e);
 808         }
 809     }
 810 
 811     /**
 812      * Callback method from _scanKeychain.  If an identity is found, this method will be called to create Java certificate
 813      * and private key objects from the keychain data.
 814      */
 815     private void createKeyEntry(String alias, long creationDate, long secKeyRef, long[] secCertificateRefs, byte[][] rawCertData)
 816         throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
 817         KeyEntry ke = new KeyEntry();
 818 
 819         // First, store off the private key information.  This is the easy part.
 820         ke.protectedPrivKey = null;
 821         ke.keyRef = secKeyRef;
 822 
 823         // Make a creation date.
 824         if (creationDate != 0)
 825             ke.date = new Date(creationDate);
 826         else
 827             ke.date = new Date();
 828 
 829         // Next, create X.509 Certificate objects from the raw data.  This is complicated
 830         // because a certificate's public key may be too long for Java's default encryption strength.
 831         List<CertKeychainItemPair> createdCerts = new ArrayList<>();
 832 
 833         try {
 834             CertificateFactory cf = CertificateFactory.getInstance("X.509");
 835 
 836             for (int i = 0; i < rawCertData.length; i++) {
 837                 try {
 838                     InputStream input = new ByteArrayInputStream(rawCertData[i]);
 839                     X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
 840                     input.close();
 841 
 842                     // We successfully created the certificate, so track it and its corresponding SecCertificateRef.
 843                     createdCerts.add(new CertKeychainItemPair(secCertificateRefs[i], cert));
 844                 } catch (CertificateException e) {
 845                     // The certificate will be skipped.
 846                     System.err.println("KeychainStore Ignored Exception: " + e);
 847                 }
 848             }
 849         } catch (CertificateException e) {
 850             e.printStackTrace();
 851         } catch (IOException ioe) {
 852             ioe.printStackTrace();  // How would this happen?
 853         }
 854 
 855         // We have our certificates in the List, so now extract them into an array of
 856         // Certificates and SecCertificateRefs.
 857         CertKeychainItemPair[] objArray = createdCerts.toArray(new CertKeychainItemPair[0]);
 858         Certificate[] certArray = new Certificate[objArray.length];
 859         long[] certRefArray = new long[objArray.length];
 860 
 861         for (int i = 0; i < objArray.length; i++) {
 862             CertKeychainItemPair addedItem = objArray[i];
 863             certArray[i] = addedItem.mCert;
 864             certRefArray[i] = addedItem.mCertificateRef;
 865         }
 866 
 867         ke.chain = certArray;
 868         ke.chainRefs = certRefArray;
 869 
 870         // If we don't have already have an item with this item's alias
 871         // create a new one for it.
 872         int uniqueVal = 1;
 873         String originalAlias = alias;
 874 
 875         while (entries.containsKey(alias.toLowerCase())) {
 876             alias = originalAlias + " " + uniqueVal;
 877             uniqueVal++;
 878         }
 879 
 880         entries.put(alias.toLowerCase(), ke);
 881     }
 882 
 883     private class CertKeychainItemPair {
 884         long mCertificateRef;
 885         Certificate mCert;
 886 
 887         CertKeychainItemPair(long inCertRef, Certificate cert) {
 888             mCertificateRef = inCertRef;
 889             mCert = cert;
 890         }
 891     }
 892 
 893     /*
 894      * Validate Certificate Chain
 895      */
 896     private boolean validateChain(Certificate[] certChain)
 897     {
 898         for (int i = 0; i < certChain.length-1; i++) {
 899             X500Principal issuerDN =
 900             ((X509Certificate)certChain[i]).getIssuerX500Principal();
 901             X500Principal subjectDN =
 902                 ((X509Certificate)certChain[i+1]).getSubjectX500Principal();
 903             if (!(issuerDN.equals(subjectDN)))
 904                 return false;
 905         }
 906         return true;
 907     }
 908 
 909     private byte[] fetchPrivateKeyFromBag(byte[] privateKeyInfo) throws IOException, NoSuchAlgorithmException, CertificateException
 910     {
 911         byte[] returnValue = null;
 912         DerValue val = new DerValue(new ByteArrayInputStream(privateKeyInfo));
 913         DerInputStream s = val.toDerInputStream();
 914         int version = s.getInteger();
 915 
 916         if (version != 3) {
 917             throw new IOException("PKCS12 keystore not in version 3 format");
 918         }
 919 
 920         /*
 921             * Read the authSafe.
 922          */
 923         byte[] authSafeData;
 924         ContentInfo authSafe = new ContentInfo(s);
 925         ObjectIdentifier contentType = authSafe.getContentType();
 926 
 927         if (contentType.equals(ContentInfo.DATA_OID)) {
 928             authSafeData = authSafe.getData();
 929         } else /* signed data */ {
 930             throw new IOException("public key protected PKCS12 not supported");
 931         }
 932 
 933         DerInputStream as = new DerInputStream(authSafeData);
 934         DerValue[] safeContentsArray = as.getSequence(2);
 935         int count = safeContentsArray.length;
 936 
 937         /*
 938          * Spin over the ContentInfos.
 939          */
 940         for (int i = 0; i < count; i++) {
 941             byte[] safeContentsData;
 942             ContentInfo safeContents;
 943             DerInputStream sci;
 944             byte[] eAlgId = null;
 945 
 946             sci = new DerInputStream(safeContentsArray[i].toByteArray());
 947             safeContents = new ContentInfo(sci);
 948             contentType = safeContents.getContentType();
 949             safeContentsData = null;
 950 
 951             if (contentType.equals(ContentInfo.DATA_OID)) {
 952                 safeContentsData = safeContents.getData();
 953             } else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) {
 954                 // The password was used to export the private key from the keychain.
 955                 // The Keychain won't export the key with encrypted data, so we don't need
 956                 // to worry about it.
 957                 continue;
 958             } else {
 959                 throw new IOException("public key protected PKCS12" +
 960                                       " not supported");
 961             }
 962             DerInputStream sc = new DerInputStream(safeContentsData);
 963             returnValue = extractKeyData(sc);
 964         }
 965 
 966         return returnValue;
 967     }
 968 
 969     private byte[] extractKeyData(DerInputStream stream)
 970         throws IOException, NoSuchAlgorithmException, CertificateException
 971     {
 972         byte[] returnValue = null;
 973         DerValue[] safeBags = stream.getSequence(2);
 974         int count = safeBags.length;
 975 
 976         /*
 977          * Spin over the SafeBags.
 978          */
 979         for (int i = 0; i < count; i++) {
 980             ObjectIdentifier bagId;
 981             DerInputStream sbi;
 982             DerValue bagValue;
 983             Object bagItem = null;
 984 
 985             sbi = safeBags[i].toDerInputStream();
 986             bagId = sbi.getOID();
 987             bagValue = sbi.getDerValue();
 988             if (!bagValue.isContextSpecific((byte)0)) {
 989                 throw new IOException("unsupported PKCS12 bag value type "
 990                                       + bagValue.tag);
 991             }
 992             bagValue = bagValue.data.getDerValue();
 993             if (bagId.equals(PKCS8ShroudedKeyBag_OID)) {
 994                 // got what we were looking for.  Return it.
 995                 returnValue = bagValue.toByteArray();
 996             } else {
 997                 // log error message for "unsupported PKCS12 bag type"
 998                 System.out.println("Unsupported bag type '" + bagId + "'");
 999             }
1000         }
1001 
1002         return returnValue;
1003     }
1004 
1005     /*
1006         * Generate PBE Algorithm Parameters
1007      */
1008     private AlgorithmParameters getAlgorithmParameters(String algorithm)
1009         throws IOException
1010     {
1011         AlgorithmParameters algParams = null;
1012 
1013         // create PBE parameters from salt and iteration count
1014         PBEParameterSpec paramSpec =
1015             new PBEParameterSpec(getSalt(), iterationCount);
1016         try {
1017             algParams = AlgorithmParameters.getInstance(algorithm);
1018             algParams.init(paramSpec);
1019         } catch (Exception e) {
1020             IOException ioe =
1021             new IOException("getAlgorithmParameters failed: " +
1022                             e.getMessage());
1023             ioe.initCause(e);
1024             throw ioe;
1025         }
1026         return algParams;
1027     }
1028 
1029     // the source of randomness
1030     private SecureRandom random;
1031 
1032     /*
1033      * Generate random salt
1034      */
1035     private byte[] getSalt()
1036     {
1037         // Generate a random salt.
1038         byte[] salt = new byte[SALT_LEN];
1039         if (random == null) {
1040             random = new SecureRandom();
1041         }
1042         random.nextBytes(salt);
1043         return salt;
1044     }
1045 
1046     /*
1047      * parse Algorithm Parameters
1048      */
1049     private AlgorithmParameters parseAlgParameters(DerInputStream in)
1050         throws IOException
1051     {
1052         AlgorithmParameters algParams = null;
1053         try {
1054             DerValue params;
1055             if (in.available() == 0) {
1056                 params = null;
1057             } else {
1058                 params = in.getDerValue();
1059                 if (params.tag == DerValue.tag_Null) {
1060                     params = null;
1061                 }
1062             }
1063             if (params != null) {
1064                 algParams = AlgorithmParameters.getInstance("PBE");
1065                 algParams.init(params.toByteArray());
1066             }
1067         } catch (Exception e) {
1068             IOException ioe =
1069             new IOException("parseAlgParameters failed: " +
1070                             e.getMessage());
1071             ioe.initCause(e);
1072             throw ioe;
1073         }
1074         return algParams;
1075     }
1076 
1077     /*
1078      * Generate PBE key
1079      */
1080     private SecretKey getPBEKey(char[] password) throws IOException
1081     {
1082         SecretKey skey = null;
1083 
1084         try {
1085             PBEKeySpec keySpec = new PBEKeySpec(password);
1086             SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE");
1087             skey = skFac.generateSecret(keySpec);
1088         } catch (Exception e) {
1089             IOException ioe = new IOException("getSecretKey failed: " +
1090                                               e.getMessage());
1091             ioe.initCause(e);
1092             throw ioe;
1093         }
1094         return skey;
1095     }
1096 
1097     /*
1098      * Encrypt private key using Password-based encryption (PBE)
1099      * as defined in PKCS#5.
1100      *
1101      * NOTE: Currently pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is
1102      *       used to derive the key and IV.
1103      *
1104      * @return encrypted private key encoded as EncryptedPrivateKeyInfo
1105      */
1106     private byte[] encryptPrivateKey(byte[] data, char[] password)
1107         throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException
1108     {
1109         byte[] key = null;
1110 
1111         try {
1112             // create AlgorithmParameters
1113             AlgorithmParameters algParams =
1114             getAlgorithmParameters("PBEWithSHA1AndDESede");
1115 
1116             // Use JCE
1117             SecretKey skey = getPBEKey(password);
1118             Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede");
1119             cipher.init(Cipher.ENCRYPT_MODE, skey, algParams);
1120             byte[] encryptedKey = cipher.doFinal(data);
1121 
1122             // wrap encrypted private key in EncryptedPrivateKeyInfo
1123             // as defined in PKCS#8
1124             AlgorithmId algid =
1125                 new AlgorithmId(pbeWithSHAAnd3KeyTripleDESCBC_OID, algParams);
1126             EncryptedPrivateKeyInfo encrInfo =
1127                 new EncryptedPrivateKeyInfo(algid, encryptedKey);
1128             key = encrInfo.getEncoded();
1129         } catch (Exception e) {
1130             UnrecoverableKeyException uke =
1131             new UnrecoverableKeyException("Encrypt Private Key failed: "
1132                                           + e.getMessage());
1133             uke.initCause(e);
1134             throw uke;
1135         }
1136 
1137         return key;
1138     }
1139 
1140 
1141 }
1142