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