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