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