1 /* 2 * Copyright (c) 2003, 2017, 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 sun.security.pkcs11; 27 28 import java.math.BigInteger; 29 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.io.IOException; 33 import java.io.ByteArrayInputStream; 34 import java.io.UnsupportedEncodingException; 35 36 import java.util.Arrays; 37 import java.util.Collections; 38 import java.util.Date; 39 import java.util.Enumeration; 40 import java.util.ArrayList; 41 import java.util.HashSet; 42 import java.util.HashMap; 43 import java.util.Set; 44 45 import java.security.*; 46 import java.security.KeyStore.*; 47 48 import java.security.cert.Certificate; 49 import java.security.cert.X509Certificate; 50 import java.security.cert.CertificateFactory; 51 import java.security.cert.CertificateException; 52 53 import java.security.interfaces.*; 54 import java.security.spec.*; 55 56 import javax.crypto.SecretKey; 57 import javax.crypto.interfaces.*; 58 59 import javax.security.auth.x500.X500Principal; 60 import javax.security.auth.login.LoginException; 61 import javax.security.auth.callback.Callback; 62 import javax.security.auth.callback.PasswordCallback; 63 import javax.security.auth.callback.CallbackHandler; 64 import javax.security.auth.callback.UnsupportedCallbackException; 65 66 import sun.security.util.Debug; 67 import sun.security.util.DerValue; 68 import sun.security.util.ECUtil; 69 70 import sun.security.pkcs11.Secmod.*; 71 import static sun.security.pkcs11.P11Util.*; 72 73 import sun.security.pkcs11.wrapper.*; 74 import static sun.security.pkcs11.wrapper.PKCS11Constants.*; 75 76 import sun.security.rsa.RSAKeyFactory; 77 78 final class P11KeyStore extends KeyStoreSpi { 79 80 private static final CK_ATTRIBUTE ATTR_CLASS_CERT = 81 new CK_ATTRIBUTE(CKA_CLASS, CKO_CERTIFICATE); 82 private static final CK_ATTRIBUTE ATTR_CLASS_PKEY = 83 new CK_ATTRIBUTE(CKA_CLASS, CKO_PRIVATE_KEY); 84 private static final CK_ATTRIBUTE ATTR_CLASS_SKEY = 85 new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY); 86 87 private static final CK_ATTRIBUTE ATTR_X509_CERT_TYPE = 88 new CK_ATTRIBUTE(CKA_CERTIFICATE_TYPE, CKC_X_509); 89 90 private static final CK_ATTRIBUTE ATTR_TOKEN_TRUE = 91 new CK_ATTRIBUTE(CKA_TOKEN, true); 92 93 // XXX for testing purposes only 94 // - NSS doesn't support persistent secret keys 95 // (key type gets mangled if secret key is a token key) 96 // - if debug is turned on, then this is set to false 97 private static CK_ATTRIBUTE ATTR_SKEY_TOKEN_TRUE = ATTR_TOKEN_TRUE; 98 99 private static final CK_ATTRIBUTE ATTR_TRUSTED_TRUE = 100 new CK_ATTRIBUTE(CKA_TRUSTED, true); 101 private static final CK_ATTRIBUTE ATTR_PRIVATE_TRUE = 102 new CK_ATTRIBUTE(CKA_PRIVATE, true); 103 104 private static final long NO_HANDLE = -1; 105 private static final long FINDOBJECTS_MAX = 100; 106 private static final String ALIAS_SEP = "/"; 107 108 private static final boolean NSS_TEST = false; 109 private static final Debug debug = 110 Debug.getInstance("pkcs11keystore"); 111 private static boolean CKA_TRUSTED_SUPPORTED = true; 112 113 private final Token token; 114 115 // If multiple certs are found to share the same CKA_LABEL 116 // at load time (NSS-style keystore), then the keystore is read 117 // and the unique keystore aliases are mapped to the entries. 118 // However, write capabilities are disabled. 119 private boolean writeDisabled = false; 120 121 // Map of unique keystore aliases to entries in the token 122 private HashMap<String, AliasInfo> aliasMap; 123 124 // whether to use NSS Secmod info for trust attributes 125 private final boolean useSecmodTrust; 126 127 // if useSecmodTrust == true, which type of trust we are interested in 128 private Secmod.TrustType nssTrustType; 129 130 /** 131 * The underlying token may contain multiple certs belonging to the 132 * same "personality" (for example, a signing cert and encryption cert), 133 * all sharing the same CKA_LABEL. These must be resolved 134 * into unique keystore aliases. 135 * 136 * In addition, private keys and certs may not have a CKA_LABEL. 137 * It is assumed that a private key and corresponding certificate 138 * share the same CKA_ID, and that the CKA_ID is unique across the token. 139 * The CKA_ID may not be human-readable. 140 * These pairs must be resolved into unique keystore aliases. 141 * 142 * Furthermore, secret keys are assumed to have a CKA_LABEL 143 * unique across the entire token. 144 * 145 * When the KeyStore is loaded, instances of this class are 146 * created to represent the private keys/secret keys/certs 147 * that reside on the token. 148 */ 149 private static class AliasInfo { 150 151 // CKA_CLASS - entry type 152 private CK_ATTRIBUTE type = null; 153 154 // CKA_LABEL of cert and secret key 155 private String label = null; 156 157 // CKA_ID of the private key/cert pair 158 private byte[] id = null; 159 160 // CKA_TRUSTED - true if cert is trusted 161 private boolean trusted = false; 162 163 // either end-entity cert or trusted cert depending on 'type' 164 private X509Certificate cert = null; 165 166 // chain 167 private X509Certificate[] chain = null; 168 169 // true if CKA_ID for private key and cert match up 170 private boolean matched = false; 171 172 // SecretKeyEntry 173 public AliasInfo(String label) { 174 this.type = ATTR_CLASS_SKEY; 175 this.label = label; 176 } 177 178 // PrivateKeyEntry 179 public AliasInfo(String label, 180 byte[] id, 181 boolean trusted, 182 X509Certificate cert) { 183 this.type = ATTR_CLASS_PKEY; 184 this.label = label; 185 this.id = id; 186 this.trusted = trusted; 187 this.cert = cert; 188 } 189 190 public String toString() { 191 StringBuilder sb = new StringBuilder(); 192 if (type == ATTR_CLASS_PKEY) { 193 sb.append("\ttype=[private key]\n"); 194 } else if (type == ATTR_CLASS_SKEY) { 195 sb.append("\ttype=[secret key]\n"); 196 } else if (type == ATTR_CLASS_CERT) { 197 sb.append("\ttype=[trusted cert]\n"); 198 } 199 sb.append("\tlabel=[" + label + "]\n"); 200 if (id == null) { 201 sb.append("\tid=[null]\n"); 202 } else { 203 sb.append("\tid=" + P11KeyStore.getID(id) + "\n"); 204 } 205 sb.append("\ttrusted=[" + trusted + "]\n"); 206 sb.append("\tmatched=[" + matched + "]\n"); 207 if (cert == null) { 208 sb.append("\tcert=[null]\n"); 209 } else { 210 sb.append("\tcert=[\tsubject: " + 211 cert.getSubjectX500Principal() + 212 "\n\t\tissuer: " + 213 cert.getIssuerX500Principal() + 214 "\n\t\tserialNum: " + 215 cert.getSerialNumber().toString() + 216 "]"); 217 } 218 return sb.toString(); 219 } 220 } 221 222 /** 223 * callback handler for passing password to Provider.login method 224 */ 225 private static class PasswordCallbackHandler implements CallbackHandler { 226 227 private char[] password; 228 229 private PasswordCallbackHandler(char[] password) { 230 if (password != null) { 231 this.password = password.clone(); 232 } 233 } 234 235 public void handle(Callback[] callbacks) 236 throws IOException, UnsupportedCallbackException { 237 if (!(callbacks[0] instanceof PasswordCallback)) { 238 throw new UnsupportedCallbackException(callbacks[0]); 239 } 240 PasswordCallback pc = (PasswordCallback)callbacks[0]; 241 pc.setPassword(password); // this clones the password if not null 242 } 243 244 @SuppressWarnings("deprecation") 245 protected void finalize() throws Throwable { 246 if (password != null) { 247 Arrays.fill(password, ' '); 248 } 249 super.finalize(); 250 } 251 } 252 253 /** 254 * getTokenObject return value. 255 * 256 * if object is not found, type is set to null. 257 * otherwise, type is set to the requested type. 258 */ 259 private static class THandle { 260 private final long handle; // token object handle 261 private final CK_ATTRIBUTE type; // CKA_CLASS 262 263 private THandle(long handle, CK_ATTRIBUTE type) { 264 this.handle = handle; 265 this.type = type; 266 } 267 } 268 269 P11KeyStore(Token token) { 270 this.token = token; 271 this.useSecmodTrust = token.provider.nssUseSecmodTrust; 272 } 273 274 /** 275 * Returns the key associated with the given alias. 276 * The key must have been associated with 277 * the alias by a call to <code>setKeyEntry</code>, 278 * or by a call to <code>setEntry</code> with a 279 * <code>PrivateKeyEntry</code> or <code>SecretKeyEntry</code>. 280 * 281 * @param alias the alias name 282 * @param password the password, which must be <code>null</code> 283 * 284 * @return the requested key, or null if the given alias does not exist 285 * or does not identify a key-related entry. 286 * 287 * @exception NoSuchAlgorithmException if the algorithm for recovering the 288 * key cannot be found 289 * @exception UnrecoverableKeyException if the key cannot be recovered 290 */ 291 public synchronized Key engineGetKey(String alias, char[] password) 292 throws NoSuchAlgorithmException, UnrecoverableKeyException { 293 294 token.ensureValid(); 295 if (password != null && !token.config.getKeyStoreCompatibilityMode()) { 296 throw new NoSuchAlgorithmException("password must be null"); 297 } 298 299 AliasInfo aliasInfo = aliasMap.get(alias); 300 if (aliasInfo == null || aliasInfo.type == ATTR_CLASS_CERT) { 301 return null; 302 } 303 304 Session session = null; 305 try { 306 session = token.getOpSession(); 307 308 if (aliasInfo.type == ATTR_CLASS_PKEY) { 309 THandle h = getTokenObject(session, 310 aliasInfo.type, 311 aliasInfo.id, 312 null); 313 if (h.type == ATTR_CLASS_PKEY) { 314 return loadPkey(session, h.handle); 315 } 316 } else { 317 THandle h = getTokenObject(session, 318 ATTR_CLASS_SKEY, 319 null, 320 alias); 321 if (h.type == ATTR_CLASS_SKEY) { 322 return loadSkey(session, h.handle); 323 } 324 } 325 326 // did not find anything 327 return null; 328 } catch (PKCS11Exception | KeyStoreException e) { 329 throw new ProviderException(e); 330 } finally { 331 token.releaseSession(session); 332 } 333 } 334 335 /** 336 * Returns the certificate chain associated with the given alias. 337 * The certificate chain must have been associated with the alias 338 * by a call to <code>setKeyEntry</code>, 339 * or by a call to <code>setEntry</code> with a 340 * <code>PrivateKeyEntry</code>. 341 * 342 * @param alias the alias name 343 * 344 * @return the certificate chain (ordered with the user's certificate first 345 * and the root certificate authority last), or null if the given alias 346 * does not exist or does not contain a certificate chain 347 */ 348 public synchronized Certificate[] engineGetCertificateChain(String alias) { 349 350 token.ensureValid(); 351 352 AliasInfo aliasInfo = aliasMap.get(alias); 353 if (aliasInfo == null || aliasInfo.type != ATTR_CLASS_PKEY) { 354 return null; 355 } 356 return aliasInfo.chain; 357 } 358 359 /** 360 * Returns the certificate associated with the given alias. 361 * 362 * <p> If the given alias name identifies an entry 363 * created by a call to <code>setCertificateEntry</code>, 364 * or created by a call to <code>setEntry</code> with a 365 * <code>TrustedCertificateEntry</code>, 366 * then the trusted certificate contained in that entry is returned. 367 * 368 * <p> If the given alias name identifies an entry 369 * created by a call to <code>setKeyEntry</code>, 370 * or created by a call to <code>setEntry</code> with a 371 * <code>PrivateKeyEntry</code>, 372 * then the first element of the certificate chain in that entry 373 * (if a chain exists) is returned. 374 * 375 * @param alias the alias name 376 * 377 * @return the certificate, or null if the given alias does not exist or 378 * does not contain a certificate. 379 */ 380 public synchronized Certificate engineGetCertificate(String alias) { 381 token.ensureValid(); 382 383 AliasInfo aliasInfo = aliasMap.get(alias); 384 if (aliasInfo == null) { 385 return null; 386 } 387 return aliasInfo.cert; 388 } 389 390 /** 391 * Returns the creation date of the entry identified by the given alias. 392 * 393 * @param alias the alias name 394 * 395 * @return the creation date of this entry, or null if the given alias does 396 * not exist 397 */ 398 public Date engineGetCreationDate(String alias) { 399 token.ensureValid(); 400 throw new ProviderException(new UnsupportedOperationException()); 401 } 402 403 /** 404 * Assigns the given key to the given alias, protecting it with the given 405 * password. 406 * 407 * <p>If the given key is of type <code>java.security.PrivateKey</code>, 408 * it must be accompanied by a certificate chain certifying the 409 * corresponding public key. 410 * 411 * <p>If the given alias already exists, the keystore information 412 * associated with it is overridden by the given key (and possibly 413 * certificate chain). 414 * 415 * @param alias the alias name 416 * @param key the key to be associated with the alias 417 * @param password the password to protect the key 418 * @param chain the certificate chain for the corresponding public 419 * key (only required if the given key is of type 420 * <code>java.security.PrivateKey</code>). 421 * 422 * @exception KeyStoreException if the given key cannot be protected, or 423 * this operation fails for some other reason 424 */ 425 public synchronized void engineSetKeyEntry(String alias, Key key, 426 char[] password, 427 Certificate[] chain) 428 throws KeyStoreException { 429 430 token.ensureValid(); 431 checkWrite(); 432 433 if (!(key instanceof PrivateKey) && !(key instanceof SecretKey)) { 434 throw new KeyStoreException("key must be PrivateKey or SecretKey"); 435 } else if (key instanceof PrivateKey && chain == null) { 436 throw new KeyStoreException 437 ("PrivateKey must be accompanied by non-null chain"); 438 } else if (key instanceof SecretKey && chain != null) { 439 throw new KeyStoreException 440 ("SecretKey must be accompanied by null chain"); 441 } else if (password != null && 442 !token.config.getKeyStoreCompatibilityMode()) { 443 throw new KeyStoreException("Password must be null"); 444 } 445 446 KeyStore.Entry entry = null; 447 try { 448 if (key instanceof PrivateKey) { 449 entry = new KeyStore.PrivateKeyEntry((PrivateKey)key, chain); 450 } else if (key instanceof SecretKey) { 451 entry = new KeyStore.SecretKeyEntry((SecretKey)key); 452 } 453 } catch (NullPointerException | IllegalArgumentException e) { 454 throw new KeyStoreException(e); 455 } 456 engineSetEntry(alias, entry, new KeyStore.PasswordProtection(password)); 457 } 458 459 /** 460 * Assigns the given key (that has already been protected) to the given 461 * alias. 462 * 463 * <p>If the protected key is of type 464 * <code>java.security.PrivateKey</code>, 465 * it must be accompanied by a certificate chain certifying the 466 * corresponding public key. 467 * 468 * <p>If the given alias already exists, the keystore information 469 * associated with it is overridden by the given key (and possibly 470 * certificate chain). 471 * 472 * @param alias the alias name 473 * @param key the key (in protected format) to be associated with the alias 474 * @param chain the certificate chain for the corresponding public 475 * key (only useful if the protected key is of type 476 * <code>java.security.PrivateKey</code>). 477 * 478 * @exception KeyStoreException if this operation fails. 479 */ 480 public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) 481 throws KeyStoreException { 482 token.ensureValid(); 483 throw new ProviderException(new UnsupportedOperationException()); 484 } 485 486 /** 487 * Assigns the given certificate to the given alias. 488 * 489 * <p> If the given alias identifies an existing entry 490 * created by a call to <code>setCertificateEntry</code>, 491 * or created by a call to <code>setEntry</code> with a 492 * <code>TrustedCertificateEntry</code>, 493 * the trusted certificate in the existing entry 494 * is overridden by the given certificate. 495 * 496 * @param alias the alias name 497 * @param cert the certificate 498 * 499 * @exception KeyStoreException if the given alias already exists and does 500 * not identify an entry containing a trusted certificate, 501 * or this operation fails for some other reason. 502 */ 503 public synchronized void engineSetCertificateEntry 504 (String alias, Certificate cert) throws KeyStoreException { 505 506 token.ensureValid(); 507 checkWrite(); 508 509 if (cert == null) { 510 throw new KeyStoreException("invalid null certificate"); 511 } 512 513 KeyStore.Entry entry = null; 514 entry = new KeyStore.TrustedCertificateEntry(cert); 515 engineSetEntry(alias, entry, null); 516 } 517 518 /** 519 * Deletes the entry identified by the given alias from this keystore. 520 * 521 * @param alias the alias name 522 * 523 * @exception KeyStoreException if the entry cannot be removed. 524 */ 525 public synchronized void engineDeleteEntry(String alias) 526 throws KeyStoreException { 527 token.ensureValid(); 528 529 if (token.isWriteProtected()) { 530 throw new KeyStoreException("token write-protected"); 531 } 532 checkWrite(); 533 deleteEntry(alias); 534 } 535 536 /** 537 * XXX - not sure whether to keep this 538 */ 539 private boolean deleteEntry(String alias) throws KeyStoreException { 540 AliasInfo aliasInfo = aliasMap.get(alias); 541 if (aliasInfo != null) { 542 543 aliasMap.remove(alias); 544 545 try { 546 if (aliasInfo.type == ATTR_CLASS_CERT) { 547 // trusted certificate entry 548 return destroyCert(aliasInfo.id); 549 } else if (aliasInfo.type == ATTR_CLASS_PKEY) { 550 // private key entry 551 return destroyPkey(aliasInfo.id) && 552 destroyChain(aliasInfo.id); 553 } else if (aliasInfo.type == ATTR_CLASS_SKEY) { 554 // secret key entry 555 return destroySkey(alias); 556 } else { 557 throw new KeyStoreException("unexpected entry type"); 558 } 559 } catch (PKCS11Exception | CertificateException e) { 560 throw new KeyStoreException(e); 561 } 562 } 563 return false; 564 } 565 566 /** 567 * Lists all the alias names of this keystore. 568 * 569 * @return enumeration of the alias names 570 */ 571 public synchronized Enumeration<String> engineAliases() { 572 token.ensureValid(); 573 574 // don't want returned enumeration to iterate off actual keySet - 575 // otherwise applications that iterate and modify the keystore 576 // may run into concurrent modification problems 577 return Collections.enumeration(new HashSet<String>(aliasMap.keySet())); 578 } 579 580 /** 581 * Checks if the given alias exists in this keystore. 582 * 583 * @param alias the alias name 584 * 585 * @return true if the alias exists, false otherwise 586 */ 587 public synchronized boolean engineContainsAlias(String alias) { 588 token.ensureValid(); 589 return aliasMap.containsKey(alias); 590 } 591 592 /** 593 * Retrieves the number of entries in this keystore. 594 * 595 * @return the number of entries in this keystore 596 */ 597 public synchronized int engineSize() { 598 token.ensureValid(); 599 return aliasMap.size(); 600 } 601 602 /** 603 * Returns true if the entry identified by the given alias 604 * was created by a call to <code>setKeyEntry</code>, 605 * or created by a call to <code>setEntry</code> with a 606 * <code>PrivateKeyEntry</code> or a <code>SecretKeyEntry</code>. 607 * 608 * @param alias the alias for the keystore entry to be checked 609 * 610 * @return true if the entry identified by the given alias is a 611 * key-related, false otherwise. 612 */ 613 public synchronized boolean engineIsKeyEntry(String alias) { 614 token.ensureValid(); 615 616 AliasInfo aliasInfo = aliasMap.get(alias); 617 if (aliasInfo == null || aliasInfo.type == ATTR_CLASS_CERT) { 618 return false; 619 } 620 return true; 621 } 622 623 /** 624 * Returns true if the entry identified by the given alias 625 * was created by a call to <code>setCertificateEntry</code>, 626 * or created by a call to <code>setEntry</code> with a 627 * <code>TrustedCertificateEntry</code>. 628 * 629 * @param alias the alias for the keystore entry to be checked 630 * 631 * @return true if the entry identified by the given alias contains a 632 * trusted certificate, false otherwise. 633 */ 634 public synchronized boolean engineIsCertificateEntry(String alias) { 635 token.ensureValid(); 636 637 AliasInfo aliasInfo = aliasMap.get(alias); 638 if (aliasInfo == null || aliasInfo.type != ATTR_CLASS_CERT) { 639 return false; 640 } 641 return true; 642 } 643 644 /** 645 * Returns the (alias) name of the first keystore entry whose certificate 646 * matches the given certificate. 647 * 648 * <p>This method attempts to match the given certificate with each 649 * keystore entry. If the entry being considered was 650 * created by a call to <code>setCertificateEntry</code>, 651 * or created by a call to <code>setEntry</code> with a 652 * <code>TrustedCertificateEntry</code>, 653 * then the given certificate is compared to that entry's certificate. 654 * 655 * <p> If the entry being considered was 656 * created by a call to <code>setKeyEntry</code>, 657 * or created by a call to <code>setEntry</code> with a 658 * <code>PrivateKeyEntry</code>, 659 * then the given certificate is compared to the first 660 * element of that entry's certificate chain. 661 * 662 * @param cert the certificate to match with. 663 * 664 * @return the alias name of the first entry with matching certificate, 665 * or null if no such entry exists in this keystore. 666 */ 667 public synchronized String engineGetCertificateAlias(Certificate cert) { 668 token.ensureValid(); 669 Enumeration<String> e = engineAliases(); 670 while (e.hasMoreElements()) { 671 String alias = e.nextElement(); 672 Certificate tokenCert = engineGetCertificate(alias); 673 if (tokenCert != null && tokenCert.equals(cert)) { 674 return alias; 675 } 676 } 677 return null; 678 } 679 680 /** 681 * engineStore currently is a No-op. 682 * Entries are stored to the token during engineSetEntry 683 * 684 * @param stream this must be <code>null</code> 685 * @param password this must be <code>null</code> 686 */ 687 public synchronized void engineStore(OutputStream stream, char[] password) 688 throws IOException, NoSuchAlgorithmException, CertificateException { 689 token.ensureValid(); 690 if (stream != null && !token.config.getKeyStoreCompatibilityMode()) { 691 throw new IOException("output stream must be null"); 692 } 693 694 if (password != null && !token.config.getKeyStoreCompatibilityMode()) { 695 throw new IOException("password must be null"); 696 } 697 } 698 699 /** 700 * engineStore currently is a No-op. 701 * Entries are stored to the token during engineSetEntry 702 * 703 * @param param this must be <code>null</code> 704 * 705 * @exception IllegalArgumentException if the given 706 * <code>KeyStore.LoadStoreParameter</code> 707 * input is not <code>null</code> 708 */ 709 public synchronized void engineStore(KeyStore.LoadStoreParameter param) 710 throws IOException, NoSuchAlgorithmException, CertificateException { 711 token.ensureValid(); 712 if (param != null) { 713 throw new IllegalArgumentException 714 ("LoadStoreParameter must be null"); 715 } 716 } 717 718 /** 719 * Loads the keystore. 720 * 721 * @param stream the input stream, which must be <code>null</code> 722 * @param password the password used to unlock the keystore, 723 * or <code>null</code> if the token supports a 724 * CKF_PROTECTED_AUTHENTICATION_PATH 725 * 726 * @exception IOException if the given <code>stream</code> is not 727 * <code>null</code>, if the token supports a 728 * CKF_PROTECTED_AUTHENTICATION_PATH and a non-null 729 * password is given, of if the token login operation failed 730 */ 731 public synchronized void engineLoad(InputStream stream, char[] password) 732 throws IOException, NoSuchAlgorithmException, CertificateException { 733 734 token.ensureValid(); 735 736 if (NSS_TEST) { 737 ATTR_SKEY_TOKEN_TRUE = new CK_ATTRIBUTE(CKA_TOKEN, false); 738 } 739 740 if (stream != null && !token.config.getKeyStoreCompatibilityMode()) { 741 throw new IOException("input stream must be null"); 742 } 743 744 if (useSecmodTrust) { 745 nssTrustType = Secmod.TrustType.ALL; 746 } 747 748 try { 749 if (password == null) { 750 login(null); 751 } else { 752 login(new PasswordCallbackHandler(password)); 753 } 754 } catch(LoginException e) { 755 Throwable cause = e.getCause(); 756 if (cause instanceof PKCS11Exception) { 757 PKCS11Exception pe = (PKCS11Exception) cause; 758 if (pe.getErrorCode() == CKR_PIN_INCORRECT) { 759 // if password is wrong, the cause of the IOException 760 // should be an UnrecoverableKeyException 761 throw new IOException("load failed", 762 new UnrecoverableKeyException().initCause(e)); 763 } 764 } 765 throw new IOException("load failed", e); 766 } 767 768 try { 769 if (mapLabels() == true) { 770 // CKA_LABELs are shared by multiple certs 771 writeDisabled = true; 772 } 773 if (debug != null) { 774 dumpTokenMap(); 775 } 776 } catch (KeyStoreException | PKCS11Exception e) { 777 throw new IOException("load failed", e); 778 } 779 } 780 781 /** 782 * Loads the keystore using the given 783 * <code>KeyStore.LoadStoreParameter</code>. 784 * 785 * <p> The <code>LoadStoreParameter.getProtectionParameter()</code> 786 * method is expected to return a <code>KeyStore.PasswordProtection</code> 787 * object. The password is retrieved from that object and used 788 * to unlock the PKCS#11 token. 789 * 790 * <p> If the token supports a CKF_PROTECTED_AUTHENTICATION_PATH 791 * then the provided password must be <code>null</code>. 792 * 793 * @param param the <code>KeyStore.LoadStoreParameter</code> 794 * 795 * @exception IllegalArgumentException if the given 796 * <code>KeyStore.LoadStoreParameter</code> is <code>null</code>, 797 * or if that parameter returns a <code>null</code> 798 * <code>ProtectionParameter</code> object. 799 * input is not recognized 800 * @exception IOException if the token supports a 801 * CKF_PROTECTED_AUTHENTICATION_PATH and the provided password 802 * is non-null, or if the token login operation fails 803 */ 804 public synchronized void engineLoad(KeyStore.LoadStoreParameter param) 805 throws IOException, NoSuchAlgorithmException, 806 CertificateException { 807 808 token.ensureValid(); 809 810 if (NSS_TEST) { 811 ATTR_SKEY_TOKEN_TRUE = new CK_ATTRIBUTE(CKA_TOKEN, false); 812 } 813 814 // if caller wants to pass a NULL password, 815 // force it to pass a non-NULL PasswordProtection that returns 816 // a NULL password 817 818 if (param == null) { 819 throw new IllegalArgumentException 820 ("invalid null LoadStoreParameter"); 821 } 822 if (useSecmodTrust) { 823 if (param instanceof Secmod.KeyStoreLoadParameter) { 824 nssTrustType = ((Secmod.KeyStoreLoadParameter)param).getTrustType(); 825 } else { 826 nssTrustType = Secmod.TrustType.ALL; 827 } 828 } 829 830 CallbackHandler handler; 831 KeyStore.ProtectionParameter pp = param.getProtectionParameter(); 832 if (pp instanceof PasswordProtection) { 833 char[] password = ((PasswordProtection)pp).getPassword(); 834 if (password == null) { 835 handler = null; 836 } else { 837 handler = new PasswordCallbackHandler(password); 838 } 839 } else if (pp instanceof CallbackHandlerProtection) { 840 handler = ((CallbackHandlerProtection)pp).getCallbackHandler(); 841 } else { 842 throw new IllegalArgumentException 843 ("ProtectionParameter must be either " + 844 "PasswordProtection or CallbackHandlerProtection"); 845 } 846 847 try { 848 login(handler); 849 if (mapLabels() == true) { 850 // CKA_LABELs are shared by multiple certs 851 writeDisabled = true; 852 } 853 if (debug != null) { 854 dumpTokenMap(); 855 } 856 } catch (LoginException | KeyStoreException | PKCS11Exception e) { 857 throw new IOException("load failed", e); 858 } 859 } 860 861 private void login(CallbackHandler handler) throws LoginException { 862 if ((token.tokenInfo.flags & CKF_PROTECTED_AUTHENTICATION_PATH) == 0) { 863 token.provider.login(null, handler); 864 } else { 865 // token supports protected authentication path 866 // (external pin-pad, for example) 867 if (handler != null && 868 !token.config.getKeyStoreCompatibilityMode()) { 869 throw new LoginException("can not specify password if token " + 870 "supports protected authentication path"); 871 } 872 873 // must rely on application-set or default handler 874 // if one is necessary 875 token.provider.login(null, null); 876 } 877 } 878 879 /** 880 * Get a <code>KeyStore.Entry</code> for the specified alias 881 * 882 * @param alias get the <code>KeyStore.Entry</code> for this alias 883 * @param protParam this must be <code>null</code> 884 * 885 * @return the <code>KeyStore.Entry</code> for the specified alias, 886 * or <code>null</code> if there is no such entry 887 * 888 * @exception KeyStoreException if the operation failed 889 * @exception NoSuchAlgorithmException if the algorithm for recovering the 890 * entry cannot be found 891 * @exception UnrecoverableEntryException if the specified 892 * <code>protParam</code> were insufficient or invalid 893 * 894 * @since 1.5 895 */ 896 public synchronized KeyStore.Entry engineGetEntry(String alias, 897 KeyStore.ProtectionParameter protParam) 898 throws KeyStoreException, NoSuchAlgorithmException, 899 UnrecoverableEntryException { 900 901 token.ensureValid(); 902 903 if (protParam != null && 904 protParam instanceof KeyStore.PasswordProtection && 905 ((KeyStore.PasswordProtection)protParam).getPassword() != null && 906 !token.config.getKeyStoreCompatibilityMode()) { 907 throw new KeyStoreException("ProtectionParameter must be null"); 908 } 909 910 AliasInfo aliasInfo = aliasMap.get(alias); 911 if (aliasInfo == null) { 912 if (debug != null) { 913 debug.println("engineGetEntry did not find alias [" + 914 alias + 915 "] in map"); 916 } 917 return null; 918 } 919 920 Session session = null; 921 try { 922 session = token.getOpSession(); 923 924 if (aliasInfo.type == ATTR_CLASS_CERT) { 925 // trusted certificate entry 926 if (debug != null) { 927 debug.println("engineGetEntry found trusted cert entry"); 928 } 929 return new KeyStore.TrustedCertificateEntry(aliasInfo.cert); 930 } else if (aliasInfo.type == ATTR_CLASS_SKEY) { 931 // secret key entry 932 if (debug != null) { 933 debug.println("engineGetEntry found secret key entry"); 934 } 935 936 THandle h = getTokenObject 937 (session, ATTR_CLASS_SKEY, null, aliasInfo.label); 938 if (h.type != ATTR_CLASS_SKEY) { 939 throw new KeyStoreException 940 ("expected but could not find secret key"); 941 } else { 942 SecretKey skey = loadSkey(session, h.handle); 943 return new KeyStore.SecretKeyEntry(skey); 944 } 945 } else { 946 // private key entry 947 if (debug != null) { 948 debug.println("engineGetEntry found private key entry"); 949 } 950 951 THandle h = getTokenObject 952 (session, ATTR_CLASS_PKEY, aliasInfo.id, null); 953 if (h.type != ATTR_CLASS_PKEY) { 954 throw new KeyStoreException 955 ("expected but could not find private key"); 956 } else { 957 PrivateKey pkey = loadPkey(session, h.handle); 958 Certificate[] chain = aliasInfo.chain; 959 if ((pkey != null) && (chain != null)) { 960 return new KeyStore.PrivateKeyEntry(pkey, chain); 961 } else { 962 if (debug != null) { 963 debug.println 964 ("engineGetEntry got null cert chain or private key"); 965 } 966 } 967 } 968 } 969 return null; 970 } catch (PKCS11Exception pe) { 971 throw new KeyStoreException(pe); 972 } finally { 973 token.releaseSession(session); 974 } 975 } 976 977 /** 978 * Save a <code>KeyStore.Entry</code> under the specified alias. 979 * 980 * <p> If an entry already exists for the specified alias, 981 * it is overridden. 982 * 983 * <p> This KeyStore implementation only supports the standard 984 * entry types, and only supports X509Certificates in 985 * TrustedCertificateEntries. Also, this implementation does not support 986 * protecting entries using a different password 987 * from the one used for token login. 988 * 989 * <p> Entries are immediately stored on the token. 990 * 991 * @param alias save the <code>KeyStore.Entry</code> under this alias 992 * @param entry the <code>Entry</code> to save 993 * @param protParam this must be <code>null</code> 994 * 995 * @exception KeyStoreException if this operation fails 996 * 997 * @since 1.5 998 */ 999 public synchronized void engineSetEntry(String alias, KeyStore.Entry entry, 1000 KeyStore.ProtectionParameter protParam) 1001 throws KeyStoreException { 1002 1003 token.ensureValid(); 1004 checkWrite(); 1005 1006 if (protParam != null && 1007 protParam instanceof KeyStore.PasswordProtection && 1008 ((KeyStore.PasswordProtection)protParam).getPassword() != null && 1009 !token.config.getKeyStoreCompatibilityMode()) { 1010 throw new KeyStoreException(new UnsupportedOperationException 1011 ("ProtectionParameter must be null")); 1012 } 1013 1014 if (token.isWriteProtected()) { 1015 throw new KeyStoreException("token write-protected"); 1016 } 1017 1018 if (entry instanceof KeyStore.TrustedCertificateEntry) { 1019 1020 if (useSecmodTrust == false) { 1021 // PKCS #11 does not allow app to modify trusted certs - 1022 throw new KeyStoreException(new UnsupportedOperationException 1023 ("trusted certificates may only be set by " + 1024 "token initialization application")); 1025 } 1026 Secmod.Module module = token.provider.nssModule; 1027 if ((module.type != ModuleType.KEYSTORE) && (module.type != ModuleType.FIPS)) { 1028 // XXX allow TRUSTANCHOR module 1029 throw new KeyStoreException("Trusted certificates can only be " 1030 + "added to the NSS KeyStore module"); 1031 } 1032 Certificate cert = ((TrustedCertificateEntry)entry).getTrustedCertificate(); 1033 if (cert instanceof X509Certificate == false) { 1034 throw new KeyStoreException("Certificate must be an X509Certificate"); 1035 } 1036 X509Certificate xcert = (X509Certificate)cert; 1037 AliasInfo info = aliasMap.get(alias); 1038 if (info != null) { 1039 // XXX try to update 1040 deleteEntry(alias); 1041 } 1042 try { 1043 storeCert(alias, xcert); 1044 module.setTrust(token, xcert); 1045 mapLabels(); 1046 } catch (PKCS11Exception | CertificateException e) { 1047 throw new KeyStoreException(e); 1048 } 1049 1050 } else { 1051 1052 if (entry instanceof KeyStore.PrivateKeyEntry) { 1053 1054 PrivateKey key = 1055 ((KeyStore.PrivateKeyEntry)entry).getPrivateKey(); 1056 if (!(key instanceof P11Key) && 1057 !(key instanceof RSAPrivateKey) && 1058 !(key instanceof DSAPrivateKey) && 1059 !(key instanceof DHPrivateKey) && 1060 !(key instanceof ECPrivateKey)) { 1061 throw new KeyStoreException("unsupported key type: " + 1062 key.getClass().getName()); 1063 } 1064 1065 // only support X509Certificate chains 1066 Certificate[] chain = 1067 ((KeyStore.PrivateKeyEntry)entry).getCertificateChain(); 1068 if (!(chain instanceof X509Certificate[])) { 1069 throw new KeyStoreException 1070 (new UnsupportedOperationException 1071 ("unsupported certificate array type: " + 1072 chain.getClass().getName())); 1073 } 1074 1075 try { 1076 boolean updatedAlias = false; 1077 Set<String> aliases = aliasMap.keySet(); 1078 for (String oldAlias : aliases) { 1079 1080 // see if there's an existing entry with the same info 1081 1082 AliasInfo aliasInfo = aliasMap.get(oldAlias); 1083 if (aliasInfo.type == ATTR_CLASS_PKEY && 1084 aliasInfo.cert.getPublicKey().equals 1085 (chain[0].getPublicKey())) { 1086 1087 // found existing entry - 1088 // caller is renaming entry or updating cert chain 1089 // 1090 // set new CKA_LABEL/CKA_ID 1091 // and update certs if necessary 1092 1093 updatePkey(alias, 1094 aliasInfo.id, 1095 (X509Certificate[])chain, 1096 !aliasInfo.cert.equals(chain[0])); 1097 updatedAlias = true; 1098 break; 1099 } 1100 } 1101 1102 if (!updatedAlias) { 1103 // caller adding new entry 1104 engineDeleteEntry(alias); 1105 storePkey(alias, (KeyStore.PrivateKeyEntry)entry); 1106 } 1107 1108 } catch (PKCS11Exception | CertificateException pe) { 1109 throw new KeyStoreException(pe); 1110 } 1111 1112 } else if (entry instanceof KeyStore.SecretKeyEntry) { 1113 1114 KeyStore.SecretKeyEntry ske = (KeyStore.SecretKeyEntry)entry; 1115 SecretKey skey = ske.getSecretKey(); 1116 1117 try { 1118 // first check if the key already exists 1119 AliasInfo aliasInfo = aliasMap.get(alias); 1120 1121 if (aliasInfo != null) { 1122 engineDeleteEntry(alias); 1123 } 1124 storeSkey(alias, ske); 1125 1126 } catch (PKCS11Exception pe) { 1127 throw new KeyStoreException(pe); 1128 } 1129 1130 } else { 1131 throw new KeyStoreException(new UnsupportedOperationException 1132 ("unsupported entry type: " + entry.getClass().getName())); 1133 } 1134 1135 try { 1136 1137 // XXX NSS does not write out the CKA_ID we pass to them 1138 // 1139 // therefore we must re-map labels 1140 // (can not simply update aliasMap) 1141 1142 mapLabels(); 1143 if (debug != null) { 1144 dumpTokenMap(); 1145 } 1146 } catch (PKCS11Exception | CertificateException pe) { 1147 throw new KeyStoreException(pe); 1148 } 1149 } 1150 1151 if (debug != null) { 1152 debug.println 1153 ("engineSetEntry added new entry for [" + 1154 alias + 1155 "] to token"); 1156 } 1157 } 1158 1159 /** 1160 * Determines if the keystore <code>Entry</code> for the specified 1161 * <code>alias</code> is an instance or subclass of the specified 1162 * <code>entryClass</code>. 1163 * 1164 * @param alias the alias name 1165 * @param entryClass the entry class 1166 * 1167 * @return true if the keystore <code>Entry</code> for the specified 1168 * <code>alias</code> is an instance or subclass of the 1169 * specified <code>entryClass</code>, false otherwise 1170 */ 1171 public synchronized boolean engineEntryInstanceOf 1172 (String alias, Class<? extends KeyStore.Entry> entryClass) { 1173 token.ensureValid(); 1174 return super.engineEntryInstanceOf(alias, entryClass); 1175 } 1176 1177 private X509Certificate loadCert(Session session, long oHandle) 1178 throws PKCS11Exception, CertificateException { 1179 1180 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] 1181 { new CK_ATTRIBUTE(CKA_VALUE) }; 1182 token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); 1183 1184 byte[] bytes = attrs[0].getByteArray(); 1185 if (bytes == null) { 1186 throw new CertificateException 1187 ("unexpectedly retrieved null byte array"); 1188 } 1189 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 1190 return (X509Certificate)cf.generateCertificate 1191 (new ByteArrayInputStream(bytes)); 1192 } 1193 1194 private X509Certificate[] loadChain(Session session, 1195 X509Certificate endCert) 1196 throws PKCS11Exception, CertificateException { 1197 1198 ArrayList<X509Certificate> lChain = null; 1199 1200 if (endCert.getSubjectX500Principal().equals 1201 (endCert.getIssuerX500Principal())) { 1202 // self signed 1203 return new X509Certificate[] { endCert }; 1204 } else { 1205 lChain = new ArrayList<X509Certificate>(); 1206 lChain.add(endCert); 1207 } 1208 1209 // try loading remaining certs in chain by following 1210 // issuer->subject links 1211 1212 X509Certificate next = endCert; 1213 while (true) { 1214 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1215 ATTR_TOKEN_TRUE, 1216 ATTR_CLASS_CERT, 1217 new CK_ATTRIBUTE(CKA_SUBJECT, 1218 next.getIssuerX500Principal().getEncoded()) }; 1219 long[] ch = findObjects(session, attrs); 1220 1221 if (ch == null || ch.length == 0) { 1222 // done 1223 break; 1224 } else { 1225 // if more than one found, use first 1226 if (debug != null && ch.length > 1) { 1227 debug.println("engineGetEntry found " + 1228 ch.length + 1229 " certificate entries for subject [" + 1230 next.getIssuerX500Principal().toString() + 1231 "] in token - using first entry"); 1232 } 1233 1234 next = loadCert(session, ch[0]); 1235 lChain.add(next); 1236 if (next.getSubjectX500Principal().equals 1237 (next.getIssuerX500Principal())) { 1238 // self signed 1239 break; 1240 } 1241 } 1242 } 1243 1244 return lChain.toArray(new X509Certificate[lChain.size()]); 1245 } 1246 1247 private SecretKey loadSkey(Session session, long oHandle) 1248 throws PKCS11Exception { 1249 1250 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1251 new CK_ATTRIBUTE(CKA_KEY_TYPE) }; 1252 token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); 1253 long kType = attrs[0].getLong(); 1254 1255 String keyType = null; 1256 int keyLength = -1; 1257 1258 // XXX NSS mangles the stored key type for secret key token objects 1259 1260 if (kType == CKK_DES || kType == CKK_DES3) { 1261 if (kType == CKK_DES) { 1262 keyType = "DES"; 1263 keyLength = 64; 1264 } else if (kType == CKK_DES3) { 1265 keyType = "DESede"; 1266 keyLength = 192; 1267 } 1268 } else { 1269 if (kType == CKK_AES) { 1270 keyType = "AES"; 1271 } else if (kType == CKK_BLOWFISH) { 1272 keyType = "Blowfish"; 1273 } else if (kType == CKK_RC4) { 1274 keyType = "ARCFOUR"; 1275 } else { 1276 if (debug != null) { 1277 debug.println("unknown key type [" + 1278 kType + 1279 "] - using 'Generic Secret'"); 1280 } 1281 keyType = "Generic Secret"; 1282 } 1283 1284 // XXX NSS problem CKR_ATTRIBUTE_TYPE_INVALID? 1285 if (NSS_TEST) { 1286 keyLength = 128; 1287 } else { 1288 attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_VALUE_LEN) }; 1289 token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); 1290 keyLength = (int)attrs[0].getLong(); 1291 } 1292 } 1293 1294 return P11Key.secretKey(session, oHandle, keyType, keyLength, null); 1295 } 1296 1297 private PrivateKey loadPkey(Session session, long oHandle) 1298 throws PKCS11Exception, KeyStoreException { 1299 1300 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1301 new CK_ATTRIBUTE(CKA_KEY_TYPE) }; 1302 token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); 1303 long kType = attrs[0].getLong(); 1304 String keyType = null; 1305 int keyLength = 0; 1306 1307 if (kType == CKK_RSA) { 1308 1309 keyType = "RSA"; 1310 1311 attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_MODULUS) }; 1312 token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); 1313 BigInteger modulus = attrs[0].getBigInteger(); 1314 keyLength = modulus.bitLength(); 1315 1316 // This check will combine our "don't care" values here 1317 // with the system-wide min/max values. 1318 try { 1319 RSAKeyFactory.checkKeyLengths(keyLength, null, 1320 -1, Integer.MAX_VALUE); 1321 } catch (InvalidKeyException e) { 1322 throw new KeyStoreException(e.getMessage()); 1323 } 1324 1325 return P11Key.privateKey(session, 1326 oHandle, 1327 keyType, 1328 keyLength, 1329 null); 1330 1331 } else if (kType == CKK_DSA) { 1332 1333 keyType = "DSA"; 1334 1335 attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_PRIME) }; 1336 token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); 1337 BigInteger prime = attrs[0].getBigInteger(); 1338 keyLength = prime.bitLength(); 1339 1340 return P11Key.privateKey(session, 1341 oHandle, 1342 keyType, 1343 keyLength, 1344 null); 1345 1346 } else if (kType == CKK_DH) { 1347 1348 keyType = "DH"; 1349 1350 attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_PRIME) }; 1351 token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); 1352 BigInteger prime = attrs[0].getBigInteger(); 1353 keyLength = prime.bitLength(); 1354 1355 return P11Key.privateKey(session, 1356 oHandle, 1357 keyType, 1358 keyLength, 1359 null); 1360 1361 } else if (kType == CKK_EC) { 1362 1363 attrs = new CK_ATTRIBUTE[] { 1364 new CK_ATTRIBUTE(CKA_EC_PARAMS), 1365 }; 1366 token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); 1367 byte[] encodedParams = attrs[0].getByteArray(); 1368 try { 1369 ECParameterSpec params = 1370 ECUtil.getECParameterSpec(null, encodedParams); 1371 keyLength = params.getCurve().getField().getFieldSize(); 1372 } catch (IOException e) { 1373 // we do not want to accept key with unsupported parameters 1374 throw new KeyStoreException("Unsupported parameters", e); 1375 } 1376 1377 return P11Key.privateKey(session, oHandle, "EC", keyLength, null); 1378 1379 } else { 1380 if (debug != null) { 1381 debug.println("unknown key type [" + kType + "]"); 1382 } 1383 throw new KeyStoreException("unknown key type"); 1384 } 1385 } 1386 1387 1388 /** 1389 * XXX On ibutton, when you C_SetAttribute(CKA_ID) for a private key 1390 * it not only changes the CKA_ID of the private key, 1391 * it changes the CKA_ID of the corresponding cert too. 1392 * And vice versa. 1393 * 1394 * XXX On ibutton, CKR_DEVICE_ERROR if you C_SetAttribute(CKA_ID) 1395 * for a private key, and then try to delete the corresponding cert. 1396 * So this code reverses the order. 1397 * After the cert is first destroyed (if necessary), 1398 * then the CKA_ID of the private key can be changed successfully. 1399 * 1400 * @param replaceCert if true, then caller is updating alias info for 1401 * existing cert (only update CKA_ID/CKA_LABEL). 1402 * if false, then caller is updating cert chain 1403 * (delete old end cert and add new chain). 1404 */ 1405 private void updatePkey(String alias, 1406 byte[] cka_id, 1407 X509Certificate[] chain, 1408 boolean replaceCert) throws 1409 KeyStoreException, CertificateException, PKCS11Exception { 1410 1411 // XXX 1412 // 1413 // always set replaceCert to true 1414 // 1415 // NSS does not allow resetting of CKA_LABEL on an existing cert 1416 // (C_SetAttribute call succeeds, but is ignored) 1417 1418 replaceCert = true; 1419 1420 Session session = null; 1421 try { 1422 session = token.getOpSession(); 1423 1424 // first get private key object handle and hang onto it 1425 1426 THandle h = getTokenObject(session, ATTR_CLASS_PKEY, cka_id, null); 1427 long pKeyHandle; 1428 if (h.type == ATTR_CLASS_PKEY) { 1429 pKeyHandle = h.handle; 1430 } else { 1431 throw new KeyStoreException 1432 ("expected but could not find private key " + 1433 "with CKA_ID " + 1434 getID(cka_id)); 1435 } 1436 1437 // next find existing end entity cert 1438 1439 h = getTokenObject(session, ATTR_CLASS_CERT, cka_id, null); 1440 if (h.type != ATTR_CLASS_CERT) { 1441 throw new KeyStoreException 1442 ("expected but could not find certificate " + 1443 "with CKA_ID " + 1444 getID(cka_id)); 1445 } else { 1446 if (replaceCert) { 1447 // replacing existing cert and chain 1448 destroyChain(cka_id); 1449 } else { 1450 // renaming alias for existing cert 1451 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1452 new CK_ATTRIBUTE(CKA_LABEL, alias), 1453 new CK_ATTRIBUTE(CKA_ID, alias) }; 1454 token.p11.C_SetAttributeValue 1455 (session.id(), h.handle, attrs); 1456 } 1457 } 1458 1459 // add new chain 1460 1461 if (replaceCert) { 1462 // add all certs in chain 1463 storeChain(alias, chain); 1464 } else { 1465 // already updated alias info for existing end cert - 1466 // just update CA certs 1467 storeCaCerts(chain, 1); 1468 } 1469 1470 // finally update CKA_ID for private key 1471 // 1472 // ibutton may have already done this (that is ok) 1473 1474 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1475 new CK_ATTRIBUTE(CKA_ID, alias) }; 1476 token.p11.C_SetAttributeValue(session.id(), pKeyHandle, attrs); 1477 1478 if (debug != null) { 1479 debug.println("updatePkey set new alias [" + 1480 alias + 1481 "] for private key entry"); 1482 } 1483 } finally { 1484 token.releaseSession(session); 1485 } 1486 } 1487 1488 private void updateP11Pkey(String alias, CK_ATTRIBUTE attribute, P11Key key) 1489 throws PKCS11Exception { 1490 1491 // if token key, update alias. 1492 // if session key, convert to token key. 1493 1494 Session session = null; 1495 try { 1496 session = token.getOpSession(); 1497 if (key.tokenObject == true) { 1498 1499 // token key - set new CKA_ID 1500 1501 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1502 new CK_ATTRIBUTE(CKA_ID, alias) }; 1503 token.p11.C_SetAttributeValue 1504 (session.id(), key.keyID, attrs); 1505 if (debug != null) { 1506 debug.println("updateP11Pkey set new alias [" + 1507 alias + 1508 "] for key entry"); 1509 } 1510 } else { 1511 1512 // session key - convert to token key and set CKA_ID 1513 1514 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1515 ATTR_TOKEN_TRUE, 1516 new CK_ATTRIBUTE(CKA_ID, alias), 1517 }; 1518 if (attribute != null) { 1519 attrs = addAttribute(attrs, attribute); 1520 } 1521 token.p11.C_CopyObject(session.id(), key.keyID, attrs); 1522 if (debug != null) { 1523 debug.println("updateP11Pkey copied private session key " + 1524 "for [" + 1525 alias + 1526 "] to token entry"); 1527 } 1528 } 1529 } finally { 1530 token.releaseSession(session); 1531 } 1532 } 1533 1534 private void storeCert(String alias, X509Certificate cert) 1535 throws PKCS11Exception, CertificateException { 1536 1537 ArrayList<CK_ATTRIBUTE> attrList = new ArrayList<CK_ATTRIBUTE>(); 1538 attrList.add(ATTR_TOKEN_TRUE); 1539 attrList.add(ATTR_CLASS_CERT); 1540 attrList.add(ATTR_X509_CERT_TYPE); 1541 attrList.add(new CK_ATTRIBUTE(CKA_SUBJECT, 1542 cert.getSubjectX500Principal().getEncoded())); 1543 attrList.add(new CK_ATTRIBUTE(CKA_ISSUER, 1544 cert.getIssuerX500Principal().getEncoded())); 1545 attrList.add(new CK_ATTRIBUTE(CKA_SERIAL_NUMBER, 1546 cert.getSerialNumber().toByteArray())); 1547 attrList.add(new CK_ATTRIBUTE(CKA_VALUE, cert.getEncoded())); 1548 1549 if (alias != null) { 1550 attrList.add(new CK_ATTRIBUTE(CKA_LABEL, alias)); 1551 attrList.add(new CK_ATTRIBUTE(CKA_ID, alias)); 1552 } else { 1553 // ibutton requires something to be set 1554 // - alias must be unique 1555 attrList.add(new CK_ATTRIBUTE(CKA_ID, 1556 getID(cert.getSubjectX500Principal().getName 1557 (X500Principal.CANONICAL), cert))); 1558 } 1559 1560 Session session = null; 1561 try { 1562 session = token.getOpSession(); 1563 token.p11.C_CreateObject(session.id(), 1564 attrList.toArray(new CK_ATTRIBUTE[attrList.size()])); 1565 } finally { 1566 token.releaseSession(session); 1567 } 1568 } 1569 1570 private void storeChain(String alias, X509Certificate[] chain) 1571 throws PKCS11Exception, CertificateException { 1572 1573 // add new chain 1574 // 1575 // end cert has CKA_LABEL and CKA_ID set to alias. 1576 // other certs in chain have neither set. 1577 1578 storeCert(alias, chain[0]); 1579 storeCaCerts(chain, 1); 1580 } 1581 1582 private void storeCaCerts(X509Certificate[] chain, int start) 1583 throws PKCS11Exception, CertificateException { 1584 1585 // do not add duplicate CA cert if already in token 1586 // 1587 // XXX ibutton stores duplicate CA certs, NSS does not 1588 1589 Session session = null; 1590 HashSet<X509Certificate> cacerts = new HashSet<X509Certificate>(); 1591 try { 1592 session = token.getOpSession(); 1593 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1594 ATTR_TOKEN_TRUE, 1595 ATTR_CLASS_CERT }; 1596 long[] handles = findObjects(session, attrs); 1597 1598 // load certs currently on the token 1599 for (long handle : handles) { 1600 cacerts.add(loadCert(session, handle)); 1601 } 1602 } finally { 1603 token.releaseSession(session); 1604 } 1605 1606 for (int i = start; i < chain.length; i++) { 1607 if (!cacerts.contains(chain[i])) { 1608 storeCert(null, chain[i]); 1609 } else if (debug != null) { 1610 debug.println("ignoring duplicate CA cert for [" + 1611 chain[i].getSubjectX500Principal() + 1612 "]"); 1613 } 1614 } 1615 } 1616 1617 private void storeSkey(String alias, KeyStore.SecretKeyEntry ske) 1618 throws PKCS11Exception, KeyStoreException { 1619 1620 SecretKey skey = ske.getSecretKey(); 1621 // No need to specify CKA_CLASS, CKA_KEY_TYPE, CKA_VALUE since 1622 // they are handled in P11SecretKeyFactory.createKey() method. 1623 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 1624 ATTR_SKEY_TOKEN_TRUE, 1625 ATTR_PRIVATE_TRUE, 1626 new CK_ATTRIBUTE(CKA_LABEL, alias), 1627 }; 1628 try { 1629 P11SecretKeyFactory.convertKey(token, skey, null, attrs); 1630 } catch (InvalidKeyException ike) { 1631 // re-throw KeyStoreException to match javadoc 1632 throw new KeyStoreException("Cannot convert to PKCS11 keys", ike); 1633 } 1634 1635 // update global alias map 1636 aliasMap.put(alias, new AliasInfo(alias)); 1637 1638 if (debug != null) { 1639 debug.println("storeSkey created token secret key for [" + 1640 alias + "]"); 1641 } 1642 } 1643 1644 private static CK_ATTRIBUTE[] addAttribute(CK_ATTRIBUTE[] attrs, CK_ATTRIBUTE attr) { 1645 int n = attrs.length; 1646 CK_ATTRIBUTE[] newAttrs = new CK_ATTRIBUTE[n + 1]; 1647 System.arraycopy(attrs, 0, newAttrs, 0, n); 1648 newAttrs[n] = attr; 1649 return newAttrs; 1650 } 1651 1652 private void storePkey(String alias, KeyStore.PrivateKeyEntry pke) 1653 throws PKCS11Exception, CertificateException, KeyStoreException { 1654 1655 PrivateKey key = pke.getPrivateKey(); 1656 CK_ATTRIBUTE[] attrs = null; 1657 1658 // If the key is a token object on this token, update it instead 1659 // of creating a duplicate key object. 1660 // Otherwise, treat a P11Key like any other key, if it is extractable. 1661 if (key instanceof P11Key) { 1662 P11Key p11Key = (P11Key)key; 1663 if (p11Key.tokenObject && (p11Key.token == this.token)) { 1664 updateP11Pkey(alias, null, p11Key); 1665 storeChain(alias, (X509Certificate[])pke.getCertificateChain()); 1666 return; 1667 } 1668 } 1669 1670 boolean useNDB = token.config.getNssNetscapeDbWorkaround(); 1671 PublicKey publicKey = pke.getCertificate().getPublicKey(); 1672 1673 if (key instanceof RSAPrivateKey) { 1674 1675 X509Certificate cert = (X509Certificate)pke.getCertificate(); 1676 attrs = getRsaPrivKeyAttrs 1677 (alias, (RSAPrivateKey)key, cert.getSubjectX500Principal()); 1678 1679 } else if (key instanceof DSAPrivateKey) { 1680 1681 DSAPrivateKey dsaKey = (DSAPrivateKey)key; 1682 1683 CK_ATTRIBUTE[] idAttrs = getIdAttributes(key, publicKey, false, useNDB); 1684 if (idAttrs[0] == null) { 1685 idAttrs[0] = new CK_ATTRIBUTE(CKA_ID, alias); 1686 } 1687 1688 attrs = new CK_ATTRIBUTE[] { 1689 ATTR_TOKEN_TRUE, 1690 ATTR_CLASS_PKEY, 1691 ATTR_PRIVATE_TRUE, 1692 new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_DSA), 1693 idAttrs[0], 1694 new CK_ATTRIBUTE(CKA_PRIME, dsaKey.getParams().getP()), 1695 new CK_ATTRIBUTE(CKA_SUBPRIME, dsaKey.getParams().getQ()), 1696 new CK_ATTRIBUTE(CKA_BASE, dsaKey.getParams().getG()), 1697 new CK_ATTRIBUTE(CKA_VALUE, dsaKey.getX()), 1698 }; 1699 if (idAttrs[1] != null) { 1700 attrs = addAttribute(attrs, idAttrs[1]); 1701 } 1702 1703 attrs = token.getAttributes 1704 (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_DSA, attrs); 1705 1706 if (debug != null) { 1707 debug.println("storePkey created DSA template"); 1708 } 1709 1710 } else if (key instanceof DHPrivateKey) { 1711 1712 DHPrivateKey dhKey = (DHPrivateKey)key; 1713 1714 CK_ATTRIBUTE[] idAttrs = getIdAttributes(key, publicKey, false, useNDB); 1715 if (idAttrs[0] == null) { 1716 idAttrs[0] = new CK_ATTRIBUTE(CKA_ID, alias); 1717 } 1718 1719 attrs = new CK_ATTRIBUTE[] { 1720 ATTR_TOKEN_TRUE, 1721 ATTR_CLASS_PKEY, 1722 ATTR_PRIVATE_TRUE, 1723 new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_DH), 1724 idAttrs[0], 1725 new CK_ATTRIBUTE(CKA_PRIME, dhKey.getParams().getP()), 1726 new CK_ATTRIBUTE(CKA_BASE, dhKey.getParams().getG()), 1727 new CK_ATTRIBUTE(CKA_VALUE, dhKey.getX()), 1728 }; 1729 if (idAttrs[1] != null) { 1730 attrs = addAttribute(attrs, idAttrs[1]); 1731 } 1732 1733 attrs = token.getAttributes 1734 (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_DH, attrs); 1735 1736 } else if (key instanceof ECPrivateKey) { 1737 1738 ECPrivateKey ecKey = (ECPrivateKey)key; 1739 1740 CK_ATTRIBUTE[] idAttrs = getIdAttributes(key, publicKey, false, useNDB); 1741 if (idAttrs[0] == null) { 1742 idAttrs[0] = new CK_ATTRIBUTE(CKA_ID, alias); 1743 } 1744 1745 byte[] encodedParams = 1746 ECUtil.encodeECParameterSpec(null, ecKey.getParams()); 1747 attrs = new CK_ATTRIBUTE[] { 1748 ATTR_TOKEN_TRUE, 1749 ATTR_CLASS_PKEY, 1750 ATTR_PRIVATE_TRUE, 1751 new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_EC), 1752 idAttrs[0], 1753 new CK_ATTRIBUTE(CKA_VALUE, ecKey.getS()), 1754 new CK_ATTRIBUTE(CKA_EC_PARAMS, encodedParams), 1755 }; 1756 if (idAttrs[1] != null) { 1757 attrs = addAttribute(attrs, idAttrs[1]); 1758 } 1759 1760 attrs = token.getAttributes 1761 (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_EC, attrs); 1762 1763 if (debug != null) { 1764 debug.println("storePkey created EC template"); 1765 } 1766 1767 } else if (key instanceof P11Key) { 1768 // sensitive/non-extractable P11Key 1769 P11Key p11Key = (P11Key)key; 1770 if (p11Key.token != this.token) { 1771 throw new KeyStoreException 1772 ("Cannot move sensitive keys across tokens"); 1773 } 1774 CK_ATTRIBUTE netscapeDB = null; 1775 if (useNDB) { 1776 // Note that this currently fails due to an NSS bug. 1777 // They do not allow the CKA_NETSCAPE_DB attribute to be 1778 // specified during C_CopyObject() and fail with 1779 // CKR_ATTRIBUTE_READ_ONLY. 1780 // But if we did not specify it, they would fail with 1781 // CKA_TEMPLATE_INCOMPLETE, so leave this code in here. 1782 CK_ATTRIBUTE[] idAttrs = getIdAttributes(key, publicKey, false, true); 1783 netscapeDB = idAttrs[1]; 1784 } 1785 // Update the key object. 1786 updateP11Pkey(alias, netscapeDB, p11Key); 1787 storeChain(alias, (X509Certificate[])pke.getCertificateChain()); 1788 return; 1789 1790 } else { 1791 throw new KeyStoreException("unsupported key type: " + key); 1792 } 1793 1794 Session session = null; 1795 try { 1796 session = token.getOpSession(); 1797 1798 // create private key entry 1799 token.p11.C_CreateObject(session.id(), attrs); 1800 if (debug != null) { 1801 debug.println("storePkey created token key for [" + 1802 alias + 1803 "]"); 1804 } 1805 } finally { 1806 token.releaseSession(session); 1807 } 1808 1809 storeChain(alias, (X509Certificate[])pke.getCertificateChain()); 1810 } 1811 1812 private CK_ATTRIBUTE[] getRsaPrivKeyAttrs(String alias, 1813 RSAPrivateKey key, 1814 X500Principal subject) throws PKCS11Exception { 1815 1816 // subject is currently ignored - could be used to set CKA_SUBJECT 1817 1818 CK_ATTRIBUTE[] attrs = null; 1819 if (key instanceof RSAPrivateCrtKey) { 1820 1821 if (debug != null) { 1822 debug.println("creating RSAPrivateCrtKey attrs"); 1823 } 1824 1825 RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey)key; 1826 1827 attrs = new CK_ATTRIBUTE[] { 1828 ATTR_TOKEN_TRUE, 1829 ATTR_CLASS_PKEY, 1830 ATTR_PRIVATE_TRUE, 1831 new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_RSA), 1832 new CK_ATTRIBUTE(CKA_ID, alias), 1833 new CK_ATTRIBUTE(CKA_MODULUS, 1834 rsaKey.getModulus()), 1835 new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT, 1836 rsaKey.getPrivateExponent()), 1837 new CK_ATTRIBUTE(CKA_PUBLIC_EXPONENT, 1838 rsaKey.getPublicExponent()), 1839 new CK_ATTRIBUTE(CKA_PRIME_1, 1840 rsaKey.getPrimeP()), 1841 new CK_ATTRIBUTE(CKA_PRIME_2, 1842 rsaKey.getPrimeQ()), 1843 new CK_ATTRIBUTE(CKA_EXPONENT_1, 1844 rsaKey.getPrimeExponentP()), 1845 new CK_ATTRIBUTE(CKA_EXPONENT_2, 1846 rsaKey.getPrimeExponentQ()), 1847 new CK_ATTRIBUTE(CKA_COEFFICIENT, 1848 rsaKey.getCrtCoefficient()) }; 1849 attrs = token.getAttributes 1850 (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_RSA, attrs); 1851 1852 } else { 1853 1854 if (debug != null) { 1855 debug.println("creating RSAPrivateKey attrs"); 1856 } 1857 1858 RSAPrivateKey rsaKey = key; 1859 1860 attrs = new CK_ATTRIBUTE[] { 1861 ATTR_TOKEN_TRUE, 1862 ATTR_CLASS_PKEY, 1863 ATTR_PRIVATE_TRUE, 1864 new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_RSA), 1865 new CK_ATTRIBUTE(CKA_ID, alias), 1866 new CK_ATTRIBUTE(CKA_MODULUS, 1867 rsaKey.getModulus()), 1868 new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT, 1869 rsaKey.getPrivateExponent()) }; 1870 attrs = token.getAttributes 1871 (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_RSA, attrs); 1872 } 1873 1874 return attrs; 1875 } 1876 1877 /** 1878 * Compute the CKA_ID and/or CKA_NETSCAPE_DB attributes that should be 1879 * used for this private key. It uses the same algorithm to calculate the 1880 * values as NSS. The public and private keys MUST match for the result to 1881 * be correct. 1882 * 1883 * It returns a 2 element array with CKA_ID at index 0 and CKA_NETSCAPE_DB 1884 * at index 1. The boolean flags determine what is to be calculated. 1885 * If false or if we could not calculate the value, that element is null. 1886 * 1887 * NOTE that we currently do not use the CKA_ID value calculated by this 1888 * method. 1889 */ 1890 private CK_ATTRIBUTE[] getIdAttributes(PrivateKey privateKey, 1891 PublicKey publicKey, boolean id, boolean netscapeDb) { 1892 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[2]; 1893 if ((id || netscapeDb) == false) { 1894 return attrs; 1895 } 1896 String alg = privateKey.getAlgorithm(); 1897 if (id && alg.equals("RSA") && (publicKey instanceof RSAPublicKey)) { 1898 // CKA_NETSCAPE_DB not needed for RSA public keys 1899 BigInteger n = ((RSAPublicKey)publicKey).getModulus(); 1900 attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(getMagnitude(n))); 1901 } else if (alg.equals("DSA") && (publicKey instanceof DSAPublicKey)) { 1902 BigInteger y = ((DSAPublicKey)publicKey).getY(); 1903 if (id) { 1904 attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(getMagnitude(y))); 1905 } 1906 if (netscapeDb) { 1907 attrs[1] = new CK_ATTRIBUTE(CKA_NETSCAPE_DB, y); 1908 } 1909 } else if (alg.equals("DH") && (publicKey instanceof DHPublicKey)) { 1910 BigInteger y = ((DHPublicKey)publicKey).getY(); 1911 if (id) { 1912 attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(getMagnitude(y))); 1913 } 1914 if (netscapeDb) { 1915 attrs[1] = new CK_ATTRIBUTE(CKA_NETSCAPE_DB, y); 1916 } 1917 } else if (alg.equals("EC") && (publicKey instanceof ECPublicKey)) { 1918 ECPublicKey ecPub = (ECPublicKey)publicKey; 1919 ECPoint point = ecPub.getW(); 1920 ECParameterSpec params = ecPub.getParams(); 1921 byte[] encodedPoint = ECUtil.encodePoint(point, params.getCurve()); 1922 if (id) { 1923 attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(encodedPoint)); 1924 } 1925 if (netscapeDb) { 1926 attrs[1] = new CK_ATTRIBUTE(CKA_NETSCAPE_DB, encodedPoint); 1927 } 1928 } else { 1929 throw new RuntimeException("Unknown key algorithm " + alg); 1930 } 1931 return attrs; 1932 } 1933 1934 /** 1935 * return true if cert destroyed 1936 */ 1937 private boolean destroyCert(byte[] cka_id) 1938 throws PKCS11Exception, KeyStoreException { 1939 Session session = null; 1940 try { 1941 session = token.getOpSession(); 1942 THandle h = getTokenObject(session, ATTR_CLASS_CERT, cka_id, null); 1943 if (h.type != ATTR_CLASS_CERT) { 1944 return false; 1945 } 1946 1947 token.p11.C_DestroyObject(session.id(), h.handle); 1948 if (debug != null) { 1949 debug.println("destroyCert destroyed cert with CKA_ID [" + 1950 getID(cka_id) + 1951 "]"); 1952 } 1953 return true; 1954 } finally { 1955 token.releaseSession(session); 1956 } 1957 } 1958 1959 /** 1960 * return true if chain destroyed 1961 */ 1962 private boolean destroyChain(byte[] cka_id) 1963 throws PKCS11Exception, CertificateException, KeyStoreException { 1964 1965 Session session = null; 1966 try { 1967 session = token.getOpSession(); 1968 1969 THandle h = getTokenObject(session, ATTR_CLASS_CERT, cka_id, null); 1970 if (h.type != ATTR_CLASS_CERT) { 1971 if (debug != null) { 1972 debug.println("destroyChain could not find " + 1973 "end entity cert with CKA_ID [0x" + 1974 Functions.toHexString(cka_id) + 1975 "]"); 1976 } 1977 return false; 1978 } 1979 1980 X509Certificate endCert = loadCert(session, h.handle); 1981 token.p11.C_DestroyObject(session.id(), h.handle); 1982 if (debug != null) { 1983 debug.println("destroyChain destroyed end entity cert " + 1984 "with CKA_ID [" + 1985 getID(cka_id) + 1986 "]"); 1987 } 1988 1989 // build chain following issuer->subject links 1990 1991 X509Certificate next = endCert; 1992 while (true) { 1993 1994 if (next.getSubjectX500Principal().equals 1995 (next.getIssuerX500Principal())) { 1996 // self signed - done 1997 break; 1998 } 1999 2000 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 2001 ATTR_TOKEN_TRUE, 2002 ATTR_CLASS_CERT, 2003 new CK_ATTRIBUTE(CKA_SUBJECT, 2004 next.getIssuerX500Principal().getEncoded()) }; 2005 long[] ch = findObjects(session, attrs); 2006 2007 if (ch == null || ch.length == 0) { 2008 // done 2009 break; 2010 } else { 2011 // if more than one found, use first 2012 if (debug != null && ch.length > 1) { 2013 debug.println("destroyChain found " + 2014 ch.length + 2015 " certificate entries for subject [" + 2016 next.getIssuerX500Principal() + 2017 "] in token - using first entry"); 2018 } 2019 2020 next = loadCert(session, ch[0]); 2021 2022 // only delete if not part of any other chain 2023 2024 attrs = new CK_ATTRIBUTE[] { 2025 ATTR_TOKEN_TRUE, 2026 ATTR_CLASS_CERT, 2027 new CK_ATTRIBUTE(CKA_ISSUER, 2028 next.getSubjectX500Principal().getEncoded()) }; 2029 long[] issuers = findObjects(session, attrs); 2030 2031 boolean destroyIt = false; 2032 if (issuers == null || issuers.length == 0) { 2033 // no other certs with this issuer - 2034 // destroy it 2035 destroyIt = true; 2036 } else if (issuers.length == 1) { 2037 X509Certificate iCert = loadCert(session, issuers[0]); 2038 if (next.equals(iCert)) { 2039 // only cert with issuer is itself (self-signed) - 2040 // destroy it 2041 destroyIt = true; 2042 } 2043 } 2044 2045 if (destroyIt) { 2046 token.p11.C_DestroyObject(session.id(), ch[0]); 2047 if (debug != null) { 2048 debug.println 2049 ("destroyChain destroyed cert in chain " + 2050 "with subject [" + 2051 next.getSubjectX500Principal() + "]"); 2052 } 2053 } else { 2054 if (debug != null) { 2055 debug.println("destroyChain did not destroy " + 2056 "shared cert in chain with subject [" + 2057 next.getSubjectX500Principal() + "]"); 2058 } 2059 } 2060 } 2061 } 2062 2063 return true; 2064 2065 } finally { 2066 token.releaseSession(session); 2067 } 2068 } 2069 2070 /** 2071 * return true if secret key destroyed 2072 */ 2073 private boolean destroySkey(String alias) 2074 throws PKCS11Exception, KeyStoreException { 2075 Session session = null; 2076 try { 2077 session = token.getOpSession(); 2078 2079 THandle h = getTokenObject(session, ATTR_CLASS_SKEY, null, alias); 2080 if (h.type != ATTR_CLASS_SKEY) { 2081 if (debug != null) { 2082 debug.println("destroySkey did not find secret key " + 2083 "with CKA_LABEL [" + 2084 alias + 2085 "]"); 2086 } 2087 return false; 2088 } 2089 token.p11.C_DestroyObject(session.id(), h.handle); 2090 return true; 2091 } finally { 2092 token.releaseSession(session); 2093 } 2094 } 2095 2096 /** 2097 * return true if private key destroyed 2098 */ 2099 private boolean destroyPkey(byte[] cka_id) 2100 throws PKCS11Exception, KeyStoreException { 2101 Session session = null; 2102 try { 2103 session = token.getOpSession(); 2104 2105 THandle h = getTokenObject(session, ATTR_CLASS_PKEY, cka_id, null); 2106 if (h.type != ATTR_CLASS_PKEY) { 2107 if (debug != null) { 2108 debug.println 2109 ("destroyPkey did not find private key with CKA_ID [" + 2110 getID(cka_id) + 2111 "]"); 2112 } 2113 return false; 2114 } 2115 token.p11.C_DestroyObject(session.id(), h.handle); 2116 return true; 2117 } finally { 2118 token.releaseSession(session); 2119 } 2120 } 2121 2122 /** 2123 * build [alias + issuer + serialNumber] string from a cert 2124 */ 2125 private String getID(String alias, X509Certificate cert) { 2126 X500Principal issuer = cert.getIssuerX500Principal(); 2127 BigInteger serialNum = cert.getSerialNumber(); 2128 2129 return alias + 2130 ALIAS_SEP + 2131 issuer.getName(X500Principal.CANONICAL) + 2132 ALIAS_SEP + 2133 serialNum.toString(); 2134 } 2135 2136 /** 2137 * build CKA_ID string from bytes 2138 */ 2139 private static String getID(byte[] bytes) { 2140 boolean printable = true; 2141 for (int i = 0; i < bytes.length; i++) { 2142 if (!DerValue.isPrintableStringChar((char)bytes[i])) { 2143 printable = false; 2144 break; 2145 } 2146 } 2147 2148 if (!printable) { 2149 return "0x" + Functions.toHexString(bytes); 2150 } else { 2151 try { 2152 return new String(bytes, "UTF-8"); 2153 } catch (UnsupportedEncodingException uee) { 2154 return "0x" + Functions.toHexString(bytes); 2155 } 2156 } 2157 } 2158 2159 /** 2160 * find an object on the token 2161 * 2162 * @param type either ATTR_CLASS_CERT, ATTR_CLASS_PKEY, or ATTR_CLASS_SKEY 2163 * @param cka_id the CKA_ID if type is ATTR_CLASS_CERT or ATTR_CLASS_PKEY 2164 * @param cka_label the CKA_LABEL if type is ATTR_CLASS_SKEY 2165 */ 2166 private THandle getTokenObject(Session session, 2167 CK_ATTRIBUTE type, 2168 byte[] cka_id, 2169 String cka_label) 2170 throws PKCS11Exception, KeyStoreException { 2171 2172 CK_ATTRIBUTE[] attrs; 2173 if (type == ATTR_CLASS_SKEY) { 2174 attrs = new CK_ATTRIBUTE[] { 2175 ATTR_SKEY_TOKEN_TRUE, 2176 new CK_ATTRIBUTE(CKA_LABEL, cka_label), 2177 type }; 2178 } else { 2179 attrs = new CK_ATTRIBUTE[] { 2180 ATTR_TOKEN_TRUE, 2181 new CK_ATTRIBUTE(CKA_ID, cka_id), 2182 type }; 2183 } 2184 long[] h = findObjects(session, attrs); 2185 if (h.length == 0) { 2186 if (debug != null) { 2187 if (type == ATTR_CLASS_SKEY) { 2188 debug.println("getTokenObject did not find secret key " + 2189 "with CKA_LABEL [" + 2190 cka_label + 2191 "]"); 2192 } else if (type == ATTR_CLASS_CERT) { 2193 debug.println 2194 ("getTokenObject did not find cert with CKA_ID [" + 2195 getID(cka_id) + 2196 "]"); 2197 } else { 2198 debug.println("getTokenObject did not find private key " + 2199 "with CKA_ID [" + 2200 getID(cka_id) + 2201 "]"); 2202 } 2203 } 2204 } else if (h.length == 1) { 2205 2206 // found object handle - return it 2207 return new THandle(h[0], type); 2208 2209 } else { 2210 2211 // found multiple object handles - 2212 // see if token ignored CKA_LABEL during search (e.g. NSS) 2213 2214 if (type == ATTR_CLASS_SKEY) { 2215 2216 ArrayList<THandle> list = new ArrayList<THandle>(h.length); 2217 for (int i = 0; i < h.length; i++) { 2218 2219 CK_ATTRIBUTE[] label = new CK_ATTRIBUTE[] 2220 { new CK_ATTRIBUTE(CKA_LABEL) }; 2221 token.p11.C_GetAttributeValue(session.id(), h[i], label); 2222 if (label[0].pValue != null && 2223 cka_label.equals(new String(label[0].getCharArray()))) { 2224 list.add(new THandle(h[i], ATTR_CLASS_SKEY)); 2225 } 2226 } 2227 if (list.size() == 1) { 2228 // yes, there was only one CKA_LABEL that matched 2229 return list.get(0); 2230 } else { 2231 throw new KeyStoreException("invalid KeyStore state: " + 2232 "found " + 2233 list.size() + 2234 " secret keys sharing CKA_LABEL [" + 2235 cka_label + 2236 "]"); 2237 } 2238 } else if (type == ATTR_CLASS_CERT) { 2239 throw new KeyStoreException("invalid KeyStore state: " + 2240 "found " + 2241 h.length + 2242 " certificates sharing CKA_ID " + 2243 getID(cka_id)); 2244 } else { 2245 throw new KeyStoreException("invalid KeyStore state: " + 2246 "found " + 2247 h.length + 2248 " private keys sharing CKA_ID " + 2249 getID(cka_id)); 2250 } 2251 } 2252 return new THandle(NO_HANDLE, null); 2253 } 2254 2255 /** 2256 * Create a mapping of all key pairs, trusted certs, and secret keys 2257 * on the token into logical KeyStore entries unambiguously 2258 * accessible via an alias. 2259 * 2260 * If the token is removed, the map may contain stale values. 2261 * KeyStore.load should be called to re-create the map. 2262 * 2263 * Assume all private keys and matching certs share a unique CKA_ID. 2264 * 2265 * Assume all secret keys have a unique CKA_LABEL. 2266 * 2267 * @return true if multiple certs found sharing the same CKA_LABEL 2268 * (if so, write capabilities are disabled) 2269 */ 2270 private boolean mapLabels() throws 2271 PKCS11Exception, CertificateException, KeyStoreException { 2272 2273 CK_ATTRIBUTE[] trustedAttr = new CK_ATTRIBUTE[] { 2274 new CK_ATTRIBUTE(CKA_TRUSTED) }; 2275 2276 Session session = null; 2277 try { 2278 session = token.getOpSession(); 2279 2280 // get all private key CKA_IDs 2281 2282 ArrayList<byte[]> pkeyIDs = new ArrayList<byte[]>(); 2283 CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { 2284 ATTR_TOKEN_TRUE, 2285 ATTR_CLASS_PKEY, 2286 }; 2287 long[] handles = findObjects(session, attrs); 2288 2289 for (long handle : handles) { 2290 attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_ID) }; 2291 token.p11.C_GetAttributeValue(session.id(), handle, attrs); 2292 2293 if (attrs[0].pValue != null) { 2294 pkeyIDs.add(attrs[0].getByteArray()); 2295 } 2296 } 2297 2298 // Get all certificates 2299 // 2300 // If cert does not have a CKA_LABEL nor CKA_ID, it is ignored. 2301 // 2302 // Get the CKA_LABEL for each cert 2303 // (if the cert does not have a CKA_LABEL, use the CKA_ID). 2304 // 2305 // Map each cert to the its CKA_LABEL 2306 // (multiple certs may be mapped to a single CKA_LABEL) 2307 2308 HashMap<String, HashSet<AliasInfo>> certMap = 2309 new HashMap<String, HashSet<AliasInfo>>(); 2310 2311 attrs = new CK_ATTRIBUTE[] { 2312 ATTR_TOKEN_TRUE, 2313 ATTR_CLASS_CERT, 2314 }; 2315 handles = findObjects(session, attrs); 2316 2317 for (long handle : handles) { 2318 attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_LABEL) }; 2319 2320 String cka_label = null; 2321 byte[] cka_id = null; 2322 try { 2323 token.p11.C_GetAttributeValue(session.id(), handle, attrs); 2324 if (attrs[0].pValue != null) { 2325 // there is a CKA_LABEL 2326 cka_label = new String(attrs[0].getCharArray()); 2327 } 2328 } catch (PKCS11Exception pe) { 2329 if (pe.getErrorCode() != CKR_ATTRIBUTE_TYPE_INVALID) { 2330 throw pe; 2331 } 2332 2333 // GetAttributeValue for CKA_LABEL not supported 2334 // 2335 // XXX SCA1000 2336 } 2337 2338 // get CKA_ID 2339 2340 attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_ID) }; 2341 token.p11.C_GetAttributeValue(session.id(), handle, attrs); 2342 if (attrs[0].pValue == null) { 2343 if (cka_label == null) { 2344 // no cka_label nor cka_id - ignore 2345 continue; 2346 } 2347 } else { 2348 if (cka_label == null) { 2349 // use CKA_ID as CKA_LABEL 2350 cka_label = getID(attrs[0].getByteArray()); 2351 } 2352 cka_id = attrs[0].getByteArray(); 2353 } 2354 2355 X509Certificate cert = loadCert(session, handle); 2356 2357 // get CKA_TRUSTED 2358 2359 boolean cka_trusted = false; 2360 2361 if (useSecmodTrust) { 2362 cka_trusted = Secmod.getInstance().isTrusted(cert, nssTrustType); 2363 } else { 2364 if (CKA_TRUSTED_SUPPORTED) { 2365 try { 2366 token.p11.C_GetAttributeValue 2367 (session.id(), handle, trustedAttr); 2368 cka_trusted = trustedAttr[0].getBoolean(); 2369 } catch (PKCS11Exception pe) { 2370 if (pe.getErrorCode() == CKR_ATTRIBUTE_TYPE_INVALID) { 2371 // XXX NSS, ibutton, sca1000 2372 CKA_TRUSTED_SUPPORTED = false; 2373 if (debug != null) { 2374 debug.println 2375 ("CKA_TRUSTED attribute not supported"); 2376 } 2377 } 2378 } 2379 } 2380 } 2381 2382 HashSet<AliasInfo> infoSet = certMap.get(cka_label); 2383 if (infoSet == null) { 2384 infoSet = new HashSet<AliasInfo>(2); 2385 certMap.put(cka_label, infoSet); 2386 } 2387 2388 // initially create private key entry AliasInfo entries - 2389 // these entries will get resolved into their true 2390 // entry types later 2391 2392 infoSet.add(new AliasInfo 2393 (cka_label, 2394 cka_id, 2395 cka_trusted, 2396 cert)); 2397 } 2398 2399 // create list secret key CKA_LABELS - 2400 // if there are duplicates (either between secret keys, 2401 // or between a secret key and another object), 2402 // throw an exception 2403 HashMap<String, AliasInfo> sKeyMap = 2404 new HashMap<String, AliasInfo>(); 2405 2406 attrs = new CK_ATTRIBUTE[] { 2407 ATTR_SKEY_TOKEN_TRUE, 2408 ATTR_CLASS_SKEY, 2409 }; 2410 handles = findObjects(session, attrs); 2411 2412 for (long handle : handles) { 2413 attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_LABEL) }; 2414 token.p11.C_GetAttributeValue(session.id(), handle, attrs); 2415 if (attrs[0].pValue != null) { 2416 2417 // there is a CKA_LABEL 2418 String cka_label = new String(attrs[0].getCharArray()); 2419 if (sKeyMap.get(cka_label) == null) { 2420 sKeyMap.put(cka_label, new AliasInfo(cka_label)); 2421 } else { 2422 throw new KeyStoreException("invalid KeyStore state: " + 2423 "found multiple secret keys sharing same " + 2424 "CKA_LABEL [" + 2425 cka_label + 2426 "]"); 2427 } 2428 } 2429 } 2430 2431 // update global aliasMap with alias mappings 2432 ArrayList<AliasInfo> matchedCerts = 2433 mapPrivateKeys(pkeyIDs, certMap); 2434 boolean sharedLabel = mapCerts(matchedCerts, certMap); 2435 mapSecretKeys(sKeyMap); 2436 2437 return sharedLabel; 2438 2439 } finally { 2440 token.releaseSession(session); 2441 } 2442 } 2443 2444 /** 2445 * for each private key CKA_ID, find corresponding cert with same CKA_ID. 2446 * if found cert, see if cert CKA_LABEL is unique. 2447 * if CKA_LABEL unique, map private key/cert alias to that CKA_LABEL. 2448 * if CKA_LABEL not unique, map private key/cert alias to: 2449 * CKA_LABEL + ALIAS_SEP + ISSUER + ALIAS_SEP + SERIAL 2450 * if cert not found, ignore private key 2451 * (don't support private key entries without a cert chain yet) 2452 * 2453 * @return a list of AliasInfo entries that represents all matches 2454 */ 2455 private ArrayList<AliasInfo> mapPrivateKeys(ArrayList<byte[]> pkeyIDs, 2456 HashMap<String, HashSet<AliasInfo>> certMap) 2457 throws PKCS11Exception, CertificateException { 2458 2459 // reset global alias map 2460 aliasMap = new HashMap<String, AliasInfo>(); 2461 2462 // list of matched certs that we will return 2463 ArrayList<AliasInfo> matchedCerts = new ArrayList<AliasInfo>(); 2464 2465 for (byte[] pkeyID : pkeyIDs) { 2466 2467 // try to find a matching CKA_ID in a certificate 2468 2469 boolean foundMatch = false; 2470 Set<String> certLabels = certMap.keySet(); 2471 for (String certLabel : certLabels) { 2472 2473 // get cert CKA_IDs (if present) for each cert 2474 2475 HashSet<AliasInfo> infoSet = certMap.get(certLabel); 2476 for (AliasInfo aliasInfo : infoSet) { 2477 if (Arrays.equals(pkeyID, aliasInfo.id)) { 2478 2479 // found private key with matching cert 2480 2481 if (infoSet.size() == 1) { 2482 // unique CKA_LABEL - use certLabel as alias 2483 aliasInfo.matched = true; 2484 aliasMap.put(certLabel, aliasInfo); 2485 } else { 2486 // create new alias 2487 aliasInfo.matched = true; 2488 aliasMap.put(getID(certLabel, aliasInfo.cert), 2489 aliasInfo); 2490 } 2491 matchedCerts.add(aliasInfo); 2492 foundMatch = true; 2493 break; 2494 } 2495 } 2496 if (foundMatch) { 2497 break; 2498 } 2499 } 2500 2501 if (!foundMatch) { 2502 if (debug != null) { 2503 debug.println 2504 ("did not find match for private key with CKA_ID [" + 2505 getID(pkeyID) + 2506 "] (ignoring entry)"); 2507 } 2508 } 2509 } 2510 2511 return matchedCerts; 2512 } 2513 2514 /** 2515 * for each cert not matched with a private key but is CKA_TRUSTED: 2516 * if CKA_LABEL unique, map cert to CKA_LABEL. 2517 * if CKA_LABEL not unique, map cert to [label+issuer+serialNum] 2518 * 2519 * if CKA_TRUSTED not supported, treat all certs not part of a chain 2520 * as trusted 2521 * 2522 * @return true if multiple certs found sharing the same CKA_LABEL 2523 */ 2524 private boolean mapCerts(ArrayList<AliasInfo> matchedCerts, 2525 HashMap<String, HashSet<AliasInfo>> certMap) 2526 throws PKCS11Exception, CertificateException { 2527 2528 // load all cert chains 2529 for (AliasInfo aliasInfo : matchedCerts) { 2530 Session session = null; 2531 try { 2532 session = token.getOpSession(); 2533 aliasInfo.chain = loadChain(session, aliasInfo.cert); 2534 } finally { 2535 token.releaseSession(session); 2536 } 2537 } 2538 2539 // find all certs in certMap not part of a cert chain 2540 // - these are trusted 2541 2542 boolean sharedLabel = false; 2543 2544 Set<String> certLabels = certMap.keySet(); 2545 for (String certLabel : certLabels) { 2546 HashSet<AliasInfo> infoSet = certMap.get(certLabel); 2547 for (AliasInfo aliasInfo : infoSet) { 2548 2549 if (aliasInfo.matched == true) { 2550 // already found a private key match for this cert - 2551 // just continue 2552 aliasInfo.trusted = false; 2553 continue; 2554 } 2555 2556 // cert in this aliasInfo is not matched yet 2557 // 2558 // if CKA_TRUSTED_SUPPORTED == true, 2559 // then check if cert is trusted 2560 2561 if (CKA_TRUSTED_SUPPORTED) { 2562 if (aliasInfo.trusted) { 2563 // trusted certificate 2564 if (mapTrustedCert 2565 (certLabel, aliasInfo, infoSet) == true) { 2566 sharedLabel = true; 2567 } 2568 } 2569 continue; 2570 } 2571 2572 // CKA_TRUSTED_SUPPORTED == false 2573 // 2574 // XXX treat all certs not part of a chain as trusted 2575 // XXX 2576 // XXX Unsupported 2577 // 2578 // boolean partOfChain = false; 2579 // for (AliasInfo matchedInfo : matchedCerts) { 2580 // for (int i = 0; i < matchedInfo.chain.length; i++) { 2581 // if (matchedInfo.chain[i].equals(aliasInfo.cert)) { 2582 // partOfChain = true; 2583 // break; 2584 // } 2585 // } 2586 // if (partOfChain) { 2587 // break; 2588 // } 2589 // } 2590 // 2591 // if (!partOfChain) { 2592 // if (mapTrustedCert(certLabel,aliasInfo,infoSet) == true){ 2593 // sharedLabel = true; 2594 // } 2595 // } else { 2596 // if (debug != null) { 2597 // debug.println("ignoring unmatched/untrusted cert " + 2598 // "that is part of cert chain - cert subject is [" + 2599 // aliasInfo.cert.getSubjectX500Principal().getName 2600 // (X500Principal.CANONICAL) + 2601 // "]"); 2602 // } 2603 // } 2604 } 2605 } 2606 2607 return sharedLabel; 2608 } 2609 2610 private boolean mapTrustedCert(String certLabel, 2611 AliasInfo aliasInfo, 2612 HashSet<AliasInfo> infoSet) { 2613 2614 boolean sharedLabel = false; 2615 2616 aliasInfo.type = ATTR_CLASS_CERT; 2617 aliasInfo.trusted = true; 2618 if (infoSet.size() == 1) { 2619 // unique CKA_LABEL - use certLabel as alias 2620 aliasMap.put(certLabel, aliasInfo); 2621 } else { 2622 // create new alias 2623 sharedLabel = true; 2624 aliasMap.put(getID(certLabel, aliasInfo.cert), aliasInfo); 2625 } 2626 2627 return sharedLabel; 2628 } 2629 2630 /** 2631 * If the secret key shares a CKA_LABEL with another entry, 2632 * throw an exception 2633 */ 2634 private void mapSecretKeys(HashMap<String, AliasInfo> sKeyMap) 2635 throws KeyStoreException { 2636 for (String label : sKeyMap.keySet()) { 2637 if (aliasMap.containsKey(label)) { 2638 throw new KeyStoreException("invalid KeyStore state: " + 2639 "found secret key sharing CKA_LABEL [" + 2640 label + 2641 "] with another token object"); 2642 } 2643 } 2644 aliasMap.putAll(sKeyMap); 2645 } 2646 2647 private void dumpTokenMap() { 2648 Set<String> aliases = aliasMap.keySet(); 2649 System.out.println("Token Alias Map:"); 2650 if (aliases.isEmpty()) { 2651 System.out.println(" [empty]"); 2652 } else { 2653 for (String s : aliases) { 2654 System.out.println(" " + s + aliasMap.get(s)); 2655 } 2656 } 2657 } 2658 2659 private void checkWrite() throws KeyStoreException { 2660 if (writeDisabled) { 2661 throw new KeyStoreException 2662 ("This PKCS11KeyStore does not support write capabilities"); 2663 } 2664 } 2665 2666 private final static long[] LONG0 = new long[0]; 2667 2668 private static long[] findObjects(Session session, CK_ATTRIBUTE[] attrs) 2669 throws PKCS11Exception { 2670 Token token = session.token; 2671 long[] handles = LONG0; 2672 token.p11.C_FindObjectsInit(session.id(), attrs); 2673 while (true) { 2674 long[] h = token.p11.C_FindObjects(session.id(), FINDOBJECTS_MAX); 2675 if (h.length == 0) { 2676 break; 2677 } 2678 handles = P11Util.concat(handles, h); 2679 } 2680 token.p11.C_FindObjectsFinal(session.id()); 2681 return handles; 2682 } 2683 2684 }