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