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