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