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