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