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