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