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