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