--- /dev/null 2017-01-18 09:30:05.425422781 -0800 +++ new/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyStore.java 2017-01-18 23:07:14.079885661 -0800 @@ -0,0 +1,2683 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.pkcs11; + +import java.math.BigInteger; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.HashMap; +import java.util.Set; + +import java.security.*; +import java.security.KeyStore.*; + +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateException; + +import java.security.interfaces.*; +import java.security.spec.*; + +import javax.crypto.SecretKey; +import javax.crypto.interfaces.*; + +import javax.security.auth.x500.X500Principal; +import javax.security.auth.login.LoginException; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import sun.security.util.Debug; +import sun.security.util.DerValue; +import sun.security.util.ECUtil; + +import sun.security.pkcs11.Secmod.*; +import static sun.security.pkcs11.P11Util.*; + +import sun.security.pkcs11.wrapper.*; +import static sun.security.pkcs11.wrapper.PKCS11Constants.*; + +import sun.security.rsa.RSAKeyFactory; + +final class P11KeyStore extends KeyStoreSpi { + + private static final CK_ATTRIBUTE ATTR_CLASS_CERT = + new CK_ATTRIBUTE(CKA_CLASS, CKO_CERTIFICATE); + private static final CK_ATTRIBUTE ATTR_CLASS_PKEY = + new CK_ATTRIBUTE(CKA_CLASS, CKO_PRIVATE_KEY); + private static final CK_ATTRIBUTE ATTR_CLASS_SKEY = + new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY); + + private static final CK_ATTRIBUTE ATTR_X509_CERT_TYPE = + new CK_ATTRIBUTE(CKA_CERTIFICATE_TYPE, CKC_X_509); + + private static final CK_ATTRIBUTE ATTR_TOKEN_TRUE = + new CK_ATTRIBUTE(CKA_TOKEN, true); + + // XXX for testing purposes only + // - NSS doesn't support persistent secret keys + // (key type gets mangled if secret key is a token key) + // - if debug is turned on, then this is set to false + private static CK_ATTRIBUTE ATTR_SKEY_TOKEN_TRUE = ATTR_TOKEN_TRUE; + + private static final CK_ATTRIBUTE ATTR_TRUSTED_TRUE = + new CK_ATTRIBUTE(CKA_TRUSTED, true); + private static final CK_ATTRIBUTE ATTR_PRIVATE_TRUE = + new CK_ATTRIBUTE(CKA_PRIVATE, true); + + private static final long NO_HANDLE = -1; + private static final long FINDOBJECTS_MAX = 100; + private static final String ALIAS_SEP = "/"; + + private static final boolean NSS_TEST = false; + private static final Debug debug = + Debug.getInstance("pkcs11keystore"); + private static boolean CKA_TRUSTED_SUPPORTED = true; + + private final Token token; + + // If multiple certs are found to share the same CKA_LABEL + // at load time (NSS-style keystore), then the keystore is read + // and the unique keystore aliases are mapped to the entries. + // However, write capabilities are disabled. + private boolean writeDisabled = false; + + // Map of unique keystore aliases to entries in the token + private HashMap aliasMap; + + // whether to use NSS Secmod info for trust attributes + private final boolean useSecmodTrust; + + // if useSecmodTrust == true, which type of trust we are interested in + private Secmod.TrustType nssTrustType; + + /** + * The underlying token may contain multiple certs belonging to the + * same "personality" (for example, a signing cert and encryption cert), + * all sharing the same CKA_LABEL. These must be resolved + * into unique keystore aliases. + * + * In addition, private keys and certs may not have a CKA_LABEL. + * It is assumed that a private key and corresponding certificate + * share the same CKA_ID, and that the CKA_ID is unique across the token. + * The CKA_ID may not be human-readable. + * These pairs must be resolved into unique keystore aliases. + * + * Furthermore, secret keys are assumed to have a CKA_LABEL + * unique across the entire token. + * + * When the KeyStore is loaded, instances of this class are + * created to represent the private keys/secret keys/certs + * that reside on the token. + */ + private static class AliasInfo { + + // CKA_CLASS - entry type + private CK_ATTRIBUTE type = null; + + // CKA_LABEL of cert and secret key + private String label = null; + + // CKA_ID of the private key/cert pair + private byte[] id = null; + + // CKA_TRUSTED - true if cert is trusted + private boolean trusted = false; + + // either end-entity cert or trusted cert depending on 'type' + private X509Certificate cert = null; + + // chain + private X509Certificate[] chain = null; + + // true if CKA_ID for private key and cert match up + private boolean matched = false; + + // SecretKeyEntry + public AliasInfo(String label) { + this.type = ATTR_CLASS_SKEY; + this.label = label; + } + + // PrivateKeyEntry + public AliasInfo(String label, + byte[] id, + boolean trusted, + X509Certificate cert) { + this.type = ATTR_CLASS_PKEY; + this.label = label; + this.id = id; + this.trusted = trusted; + this.cert = cert; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + if (type == ATTR_CLASS_PKEY) { + sb.append("\ttype=[private key]\n"); + } else if (type == ATTR_CLASS_SKEY) { + sb.append("\ttype=[secret key]\n"); + } else if (type == ATTR_CLASS_CERT) { + sb.append("\ttype=[trusted cert]\n"); + } + sb.append("\tlabel=[" + label + "]\n"); + if (id == null) { + sb.append("\tid=[null]\n"); + } else { + sb.append("\tid=" + P11KeyStore.getID(id) + "\n"); + } + sb.append("\ttrusted=[" + trusted + "]\n"); + sb.append("\tmatched=[" + matched + "]\n"); + if (cert == null) { + sb.append("\tcert=[null]\n"); + } else { + sb.append("\tcert=[\tsubject: " + + cert.getSubjectX500Principal() + + "\n\t\tissuer: " + + cert.getIssuerX500Principal() + + "\n\t\tserialNum: " + + cert.getSerialNumber().toString() + + "]"); + } + return sb.toString(); + } + } + + /** + * callback handler for passing password to Provider.login method + */ + private static class PasswordCallbackHandler implements CallbackHandler { + + private char[] password; + + private PasswordCallbackHandler(char[] password) { + if (password != null) { + this.password = password.clone(); + } + } + + public void handle(Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + if (!(callbacks[0] instanceof PasswordCallback)) { + throw new UnsupportedCallbackException(callbacks[0]); + } + PasswordCallback pc = (PasswordCallback)callbacks[0]; + pc.setPassword(password); // this clones the password if not null + } + + protected void finalize() throws Throwable { + if (password != null) { + Arrays.fill(password, ' '); + } + super.finalize(); + } + } + + /** + * getTokenObject return value. + * + * if object is not found, type is set to null. + * otherwise, type is set to the requested type. + */ + private static class THandle { + private final long handle; // token object handle + private final CK_ATTRIBUTE type; // CKA_CLASS + + private THandle(long handle, CK_ATTRIBUTE type) { + this.handle = handle; + this.type = type; + } + } + + P11KeyStore(Token token) { + this.token = token; + this.useSecmodTrust = token.provider.nssUseSecmodTrust; + } + + /** + * Returns the key associated with the given alias. + * The key must have been associated with + * the alias by a call to setKeyEntry, + * or by a call to setEntry with a + * PrivateKeyEntry or SecretKeyEntry. + * + * @param alias the alias name + * @param password the password, which must be null + * + * @return the requested key, or null if the given alias does not exist + * or does not identify a key-related entry. + * + * @exception NoSuchAlgorithmException if the algorithm for recovering the + * key cannot be found + * @exception UnrecoverableKeyException if the key cannot be recovered + */ + public synchronized Key engineGetKey(String alias, char[] password) + throws NoSuchAlgorithmException, UnrecoverableKeyException { + + token.ensureValid(); + if (password != null && !token.config.getKeyStoreCompatibilityMode()) { + throw new NoSuchAlgorithmException("password must be null"); + } + + AliasInfo aliasInfo = aliasMap.get(alias); + if (aliasInfo == null || aliasInfo.type == ATTR_CLASS_CERT) { + return null; + } + + Session session = null; + try { + session = token.getOpSession(); + + if (aliasInfo.type == ATTR_CLASS_PKEY) { + THandle h = getTokenObject(session, + aliasInfo.type, + aliasInfo.id, + null); + if (h.type == ATTR_CLASS_PKEY) { + return loadPkey(session, h.handle); + } + } else { + THandle h = getTokenObject(session, + ATTR_CLASS_SKEY, + null, + alias); + if (h.type == ATTR_CLASS_SKEY) { + return loadSkey(session, h.handle); + } + } + + // did not find anything + return null; + } catch (PKCS11Exception | KeyStoreException e) { + throw new ProviderException(e); + } finally { + token.releaseSession(session); + } + } + + /** + * Returns the certificate chain associated with the given alias. + * The certificate chain must have been associated with the alias + * by a call to setKeyEntry, + * or by a call to setEntry with a + * PrivateKeyEntry. + * + * @param alias the alias name + * + * @return the certificate chain (ordered with the user's certificate first + * and the root certificate authority last), or null if the given alias + * does not exist or does not contain a certificate chain + */ + public synchronized Certificate[] engineGetCertificateChain(String alias) { + + token.ensureValid(); + + AliasInfo aliasInfo = aliasMap.get(alias); + if (aliasInfo == null || aliasInfo.type != ATTR_CLASS_PKEY) { + return null; + } + return aliasInfo.chain; + } + + /** + * Returns the certificate associated with the given alias. + * + *

If the given alias name identifies an entry + * created by a call to setCertificateEntry, + * or created by a call to setEntry with a + * TrustedCertificateEntry, + * then the trusted certificate contained in that entry is returned. + * + *

If the given alias name identifies an entry + * created by a call to setKeyEntry, + * or created by a call to setEntry with a + * PrivateKeyEntry, + * then the first element of the certificate chain in that entry + * (if a chain exists) is returned. + * + * @param alias the alias name + * + * @return the certificate, or null if the given alias does not exist or + * does not contain a certificate. + */ + public synchronized Certificate engineGetCertificate(String alias) { + token.ensureValid(); + + AliasInfo aliasInfo = aliasMap.get(alias); + if (aliasInfo == null) { + return null; + } + return aliasInfo.cert; + } + + /** + * Returns the creation date of the entry identified by the given alias. + * + * @param alias the alias name + * + * @return the creation date of this entry, or null if the given alias does + * not exist + */ + public Date engineGetCreationDate(String alias) { + token.ensureValid(); + throw new ProviderException(new UnsupportedOperationException()); + } + + /** + * Assigns the given key to the given alias, protecting it with the given + * password. + * + *

If the given key is of type java.security.PrivateKey, + * it must be accompanied by a certificate chain certifying the + * corresponding public key. + * + *

If the given alias already exists, the keystore information + * associated with it is overridden by the given key (and possibly + * certificate chain). + * + * @param alias the alias name + * @param key the key to be associated with the alias + * @param password the password to protect the key + * @param chain the certificate chain for the corresponding public + * key (only required if the given key is of type + * java.security.PrivateKey). + * + * @exception KeyStoreException if the given key cannot be protected, or + * this operation fails for some other reason + */ + public synchronized void engineSetKeyEntry(String alias, Key key, + char[] password, + Certificate[] chain) + throws KeyStoreException { + + token.ensureValid(); + checkWrite(); + + if (!(key instanceof PrivateKey) && !(key instanceof SecretKey)) { + throw new KeyStoreException("key must be PrivateKey or SecretKey"); + } else if (key instanceof PrivateKey && chain == null) { + throw new KeyStoreException + ("PrivateKey must be accompanied by non-null chain"); + } else if (key instanceof SecretKey && chain != null) { + throw new KeyStoreException + ("SecretKey must be accompanied by null chain"); + } else if (password != null && + !token.config.getKeyStoreCompatibilityMode()) { + throw new KeyStoreException("Password must be null"); + } + + KeyStore.Entry entry = null; + try { + if (key instanceof PrivateKey) { + entry = new KeyStore.PrivateKeyEntry((PrivateKey)key, chain); + } else if (key instanceof SecretKey) { + entry = new KeyStore.SecretKeyEntry((SecretKey)key); + } + } catch (NullPointerException | IllegalArgumentException e) { + throw new KeyStoreException(e); + } + engineSetEntry(alias, entry, new KeyStore.PasswordProtection(password)); + } + + /** + * Assigns the given key (that has already been protected) to the given + * alias. + * + *

If the protected key is of type + * java.security.PrivateKey, + * it must be accompanied by a certificate chain certifying the + * corresponding public key. + * + *

If the given alias already exists, the keystore information + * associated with it is overridden by the given key (and possibly + * certificate chain). + * + * @param alias the alias name + * @param key the key (in protected format) to be associated with the alias + * @param chain the certificate chain for the corresponding public + * key (only useful if the protected key is of type + * java.security.PrivateKey). + * + * @exception KeyStoreException if this operation fails. + */ + public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) + throws KeyStoreException { + token.ensureValid(); + throw new ProviderException(new UnsupportedOperationException()); + } + + /** + * Assigns the given certificate to the given alias. + * + *

If the given alias identifies an existing entry + * created by a call to setCertificateEntry, + * or created by a call to setEntry with a + * TrustedCertificateEntry, + * the trusted certificate in the existing entry + * is overridden by the given certificate. + * + * @param alias the alias name + * @param cert the certificate + * + * @exception KeyStoreException if the given alias already exists and does + * not identify an entry containing a trusted certificate, + * or this operation fails for some other reason. + */ + public synchronized void engineSetCertificateEntry + (String alias, Certificate cert) throws KeyStoreException { + + token.ensureValid(); + checkWrite(); + + if (cert == null) { + throw new KeyStoreException("invalid null certificate"); + } + + KeyStore.Entry entry = null; + entry = new KeyStore.TrustedCertificateEntry(cert); + engineSetEntry(alias, entry, null); + } + + /** + * Deletes the entry identified by the given alias from this keystore. + * + * @param alias the alias name + * + * @exception KeyStoreException if the entry cannot be removed. + */ + public synchronized void engineDeleteEntry(String alias) + throws KeyStoreException { + token.ensureValid(); + + if (token.isWriteProtected()) { + throw new KeyStoreException("token write-protected"); + } + checkWrite(); + deleteEntry(alias); + } + + /** + * XXX - not sure whether to keep this + */ + private boolean deleteEntry(String alias) throws KeyStoreException { + AliasInfo aliasInfo = aliasMap.get(alias); + if (aliasInfo != null) { + + aliasMap.remove(alias); + + try { + if (aliasInfo.type == ATTR_CLASS_CERT) { + // trusted certificate entry + return destroyCert(aliasInfo.id); + } else if (aliasInfo.type == ATTR_CLASS_PKEY) { + // private key entry + return destroyPkey(aliasInfo.id) && + destroyChain(aliasInfo.id); + } else if (aliasInfo.type == ATTR_CLASS_SKEY) { + // secret key entry + return destroySkey(alias); + } else { + throw new KeyStoreException("unexpected entry type"); + } + } catch (PKCS11Exception | CertificateException e) { + throw new KeyStoreException(e); + } + } + return false; + } + + /** + * Lists all the alias names of this keystore. + * + * @return enumeration of the alias names + */ + public synchronized Enumeration engineAliases() { + token.ensureValid(); + + // don't want returned enumeration to iterate off actual keySet - + // otherwise applications that iterate and modify the keystore + // may run into concurrent modification problems + return Collections.enumeration(new HashSet(aliasMap.keySet())); + } + + /** + * Checks if the given alias exists in this keystore. + * + * @param alias the alias name + * + * @return true if the alias exists, false otherwise + */ + public synchronized boolean engineContainsAlias(String alias) { + token.ensureValid(); + return aliasMap.containsKey(alias); + } + + /** + * Retrieves the number of entries in this keystore. + * + * @return the number of entries in this keystore + */ + public synchronized int engineSize() { + token.ensureValid(); + return aliasMap.size(); + } + + /** + * Returns true if the entry identified by the given alias + * was created by a call to setKeyEntry, + * or created by a call to setEntry with a + * PrivateKeyEntry or a SecretKeyEntry. + * + * @param alias the alias for the keystore entry to be checked + * + * @return true if the entry identified by the given alias is a + * key-related, false otherwise. + */ + public synchronized boolean engineIsKeyEntry(String alias) { + token.ensureValid(); + + AliasInfo aliasInfo = aliasMap.get(alias); + if (aliasInfo == null || aliasInfo.type == ATTR_CLASS_CERT) { + return false; + } + return true; + } + + /** + * Returns true if the entry identified by the given alias + * was created by a call to setCertificateEntry, + * or created by a call to setEntry with a + * TrustedCertificateEntry. + * + * @param alias the alias for the keystore entry to be checked + * + * @return true if the entry identified by the given alias contains a + * trusted certificate, false otherwise. + */ + public synchronized boolean engineIsCertificateEntry(String alias) { + token.ensureValid(); + + AliasInfo aliasInfo = aliasMap.get(alias); + if (aliasInfo == null || aliasInfo.type != ATTR_CLASS_CERT) { + return false; + } + return true; + } + + /** + * Returns the (alias) name of the first keystore entry whose certificate + * matches the given certificate. + * + *

This method attempts to match the given certificate with each + * keystore entry. If the entry being considered was + * created by a call to setCertificateEntry, + * or created by a call to setEntry with a + * TrustedCertificateEntry, + * then the given certificate is compared to that entry's certificate. + * + *

If the entry being considered was + * created by a call to setKeyEntry, + * or created by a call to setEntry with a + * PrivateKeyEntry, + * then the given certificate is compared to the first + * element of that entry's certificate chain. + * + * @param cert the certificate to match with. + * + * @return the alias name of the first entry with matching certificate, + * or null if no such entry exists in this keystore. + */ + public synchronized String engineGetCertificateAlias(Certificate cert) { + token.ensureValid(); + Enumeration e = engineAliases(); + while (e.hasMoreElements()) { + String alias = e.nextElement(); + Certificate tokenCert = engineGetCertificate(alias); + if (tokenCert != null && tokenCert.equals(cert)) { + return alias; + } + } + return null; + } + + /** + * engineStore currently is a No-op. + * Entries are stored to the token during engineSetEntry + * + * @param stream this must be null + * @param password this must be null + */ + public synchronized void engineStore(OutputStream stream, char[] password) + throws IOException, NoSuchAlgorithmException, CertificateException { + token.ensureValid(); + if (stream != null && !token.config.getKeyStoreCompatibilityMode()) { + throw new IOException("output stream must be null"); + } + + if (password != null && !token.config.getKeyStoreCompatibilityMode()) { + throw new IOException("password must be null"); + } + } + + /** + * engineStore currently is a No-op. + * Entries are stored to the token during engineSetEntry + * + * @param param this must be null + * + * @exception IllegalArgumentException if the given + * KeyStore.LoadStoreParameter + * input is not null + */ + public synchronized void engineStore(KeyStore.LoadStoreParameter param) + throws IOException, NoSuchAlgorithmException, CertificateException { + token.ensureValid(); + if (param != null) { + throw new IllegalArgumentException + ("LoadStoreParameter must be null"); + } + } + + /** + * Loads the keystore. + * + * @param stream the input stream, which must be null + * @param password the password used to unlock the keystore, + * or null if the token supports a + * CKF_PROTECTED_AUTHENTICATION_PATH + * + * @exception IOException if the given stream is not + * null, if the token supports a + * CKF_PROTECTED_AUTHENTICATION_PATH and a non-null + * password is given, of if the token login operation failed + */ + public synchronized void engineLoad(InputStream stream, char[] password) + throws IOException, NoSuchAlgorithmException, CertificateException { + + token.ensureValid(); + + if (NSS_TEST) { + ATTR_SKEY_TOKEN_TRUE = new CK_ATTRIBUTE(CKA_TOKEN, false); + } + + if (stream != null && !token.config.getKeyStoreCompatibilityMode()) { + throw new IOException("input stream must be null"); + } + + if (useSecmodTrust) { + nssTrustType = Secmod.TrustType.ALL; + } + + try { + if (password == null) { + login(null); + } else { + login(new PasswordCallbackHandler(password)); + } + } catch(LoginException e) { + Throwable cause = e.getCause(); + if (cause instanceof PKCS11Exception) { + PKCS11Exception pe = (PKCS11Exception) cause; + if (pe.getErrorCode() == CKR_PIN_INCORRECT) { + // if password is wrong, the cause of the IOException + // should be an UnrecoverableKeyException + throw new IOException("load failed", + new UnrecoverableKeyException().initCause(e)); + } + } + throw new IOException("load failed", e); + } + + try { + if (mapLabels() == true) { + // CKA_LABELs are shared by multiple certs + writeDisabled = true; + } + if (debug != null) { + dumpTokenMap(); + } + } catch (KeyStoreException | PKCS11Exception e) { + throw new IOException("load failed", e); + } + } + + /** + * Loads the keystore using the given + * KeyStore.LoadStoreParameter. + * + *

The LoadStoreParameter.getProtectionParameter() + * method is expected to return a KeyStore.PasswordProtection + * object. The password is retrieved from that object and used + * to unlock the PKCS#11 token. + * + *

If the token supports a CKF_PROTECTED_AUTHENTICATION_PATH + * then the provided password must be null. + * + * @param param the KeyStore.LoadStoreParameter + * + * @exception IllegalArgumentException if the given + * KeyStore.LoadStoreParameter is null, + * or if that parameter returns a null + * ProtectionParameter object. + * input is not recognized + * @exception IOException if the token supports a + * CKF_PROTECTED_AUTHENTICATION_PATH and the provided password + * is non-null, or if the token login operation fails + */ + public synchronized void engineLoad(KeyStore.LoadStoreParameter param) + throws IOException, NoSuchAlgorithmException, + CertificateException { + + token.ensureValid(); + + if (NSS_TEST) { + ATTR_SKEY_TOKEN_TRUE = new CK_ATTRIBUTE(CKA_TOKEN, false); + } + + // if caller wants to pass a NULL password, + // force it to pass a non-NULL PasswordProtection that returns + // a NULL password + + if (param == null) { + throw new IllegalArgumentException + ("invalid null LoadStoreParameter"); + } + if (useSecmodTrust) { + if (param instanceof Secmod.KeyStoreLoadParameter) { + nssTrustType = ((Secmod.KeyStoreLoadParameter)param).getTrustType(); + } else { + nssTrustType = Secmod.TrustType.ALL; + } + } + + CallbackHandler handler; + KeyStore.ProtectionParameter pp = param.getProtectionParameter(); + if (pp instanceof PasswordProtection) { + char[] password = ((PasswordProtection)pp).getPassword(); + if (password == null) { + handler = null; + } else { + handler = new PasswordCallbackHandler(password); + } + } else if (pp instanceof CallbackHandlerProtection) { + handler = ((CallbackHandlerProtection)pp).getCallbackHandler(); + } else { + throw new IllegalArgumentException + ("ProtectionParameter must be either " + + "PasswordProtection or CallbackHandlerProtection"); + } + + try { + login(handler); + if (mapLabels() == true) { + // CKA_LABELs are shared by multiple certs + writeDisabled = true; + } + if (debug != null) { + dumpTokenMap(); + } + } catch (LoginException | KeyStoreException | PKCS11Exception e) { + throw new IOException("load failed", e); + } + } + + private void login(CallbackHandler handler) throws LoginException { + if ((token.tokenInfo.flags & CKF_PROTECTED_AUTHENTICATION_PATH) == 0) { + token.provider.login(null, handler); + } else { + // token supports protected authentication path + // (external pin-pad, for example) + if (handler != null && + !token.config.getKeyStoreCompatibilityMode()) { + throw new LoginException("can not specify password if token " + + "supports protected authentication path"); + } + + // must rely on application-set or default handler + // if one is necessary + token.provider.login(null, null); + } + } + + /** + * Get a KeyStore.Entry for the specified alias + * + * @param alias get the KeyStore.Entry for this alias + * @param protParam this must be null + * + * @return the KeyStore.Entry for the specified alias, + * or null if there is no such entry + * + * @exception KeyStoreException if the operation failed + * @exception NoSuchAlgorithmException if the algorithm for recovering the + * entry cannot be found + * @exception UnrecoverableEntryException if the specified + * protParam were insufficient or invalid + * + * @since 1.5 + */ + public synchronized KeyStore.Entry engineGetEntry(String alias, + KeyStore.ProtectionParameter protParam) + throws KeyStoreException, NoSuchAlgorithmException, + UnrecoverableEntryException { + + token.ensureValid(); + + if (protParam != null && + protParam instanceof KeyStore.PasswordProtection && + ((KeyStore.PasswordProtection)protParam).getPassword() != null && + !token.config.getKeyStoreCompatibilityMode()) { + throw new KeyStoreException("ProtectionParameter must be null"); + } + + AliasInfo aliasInfo = aliasMap.get(alias); + if (aliasInfo == null) { + if (debug != null) { + debug.println("engineGetEntry did not find alias [" + + alias + + "] in map"); + } + return null; + } + + Session session = null; + try { + session = token.getOpSession(); + + if (aliasInfo.type == ATTR_CLASS_CERT) { + // trusted certificate entry + if (debug != null) { + debug.println("engineGetEntry found trusted cert entry"); + } + return new KeyStore.TrustedCertificateEntry(aliasInfo.cert); + } else if (aliasInfo.type == ATTR_CLASS_SKEY) { + // secret key entry + if (debug != null) { + debug.println("engineGetEntry found secret key entry"); + } + + THandle h = getTokenObject + (session, ATTR_CLASS_SKEY, null, aliasInfo.label); + if (h.type != ATTR_CLASS_SKEY) { + throw new KeyStoreException + ("expected but could not find secret key"); + } else { + SecretKey skey = loadSkey(session, h.handle); + return new KeyStore.SecretKeyEntry(skey); + } + } else { + // private key entry + if (debug != null) { + debug.println("engineGetEntry found private key entry"); + } + + THandle h = getTokenObject + (session, ATTR_CLASS_PKEY, aliasInfo.id, null); + if (h.type != ATTR_CLASS_PKEY) { + throw new KeyStoreException + ("expected but could not find private key"); + } else { + PrivateKey pkey = loadPkey(session, h.handle); + Certificate[] chain = aliasInfo.chain; + if ((pkey != null) && (chain != null)) { + return new KeyStore.PrivateKeyEntry(pkey, chain); + } else { + if (debug != null) { + debug.println + ("engineGetEntry got null cert chain or private key"); + } + } + } + } + return null; + } catch (PKCS11Exception pe) { + throw new KeyStoreException(pe); + } finally { + token.releaseSession(session); + } + } + + /** + * Save a KeyStore.Entry under the specified alias. + * + *

If an entry already exists for the specified alias, + * it is overridden. + * + *

This KeyStore implementation only supports the standard + * entry types, and only supports X509Certificates in + * TrustedCertificateEntries. Also, this implementation does not support + * protecting entries using a different password + * from the one used for token login. + * + *

Entries are immediately stored on the token. + * + * @param alias save the KeyStore.Entry under this alias + * @param entry the Entry to save + * @param protParam this must be null + * + * @exception KeyStoreException if this operation fails + * + * @since 1.5 + */ + public synchronized void engineSetEntry(String alias, KeyStore.Entry entry, + KeyStore.ProtectionParameter protParam) + throws KeyStoreException { + + token.ensureValid(); + checkWrite(); + + if (protParam != null && + protParam instanceof KeyStore.PasswordProtection && + ((KeyStore.PasswordProtection)protParam).getPassword() != null && + !token.config.getKeyStoreCompatibilityMode()) { + throw new KeyStoreException(new UnsupportedOperationException + ("ProtectionParameter must be null")); + } + + if (token.isWriteProtected()) { + throw new KeyStoreException("token write-protected"); + } + + if (entry instanceof KeyStore.TrustedCertificateEntry) { + + if (useSecmodTrust == false) { + // PKCS #11 does not allow app to modify trusted certs - + throw new KeyStoreException(new UnsupportedOperationException + ("trusted certificates may only be set by " + + "token initialization application")); + } + Module module = token.provider.nssModule; + if ((module.type != ModuleType.KEYSTORE) && (module.type != ModuleType.FIPS)) { + // XXX allow TRUSTANCHOR module + throw new KeyStoreException("Trusted certificates can only be " + + "added to the NSS KeyStore module"); + } + Certificate cert = ((TrustedCertificateEntry)entry).getTrustedCertificate(); + if (cert instanceof X509Certificate == false) { + throw new KeyStoreException("Certificate must be an X509Certificate"); + } + X509Certificate xcert = (X509Certificate)cert; + AliasInfo info = aliasMap.get(alias); + if (info != null) { + // XXX try to update + deleteEntry(alias); + } + try { + storeCert(alias, xcert); + module.setTrust(token, xcert); + mapLabels(); + } catch (PKCS11Exception | CertificateException e) { + throw new KeyStoreException(e); + } + + } else { + + if (entry instanceof KeyStore.PrivateKeyEntry) { + + PrivateKey key = + ((KeyStore.PrivateKeyEntry)entry).getPrivateKey(); + if (!(key instanceof P11Key) && + !(key instanceof RSAPrivateKey) && + !(key instanceof DSAPrivateKey) && + !(key instanceof DHPrivateKey) && + !(key instanceof ECPrivateKey)) { + throw new KeyStoreException("unsupported key type: " + + key.getClass().getName()); + } + + // only support X509Certificate chains + Certificate[] chain = + ((KeyStore.PrivateKeyEntry)entry).getCertificateChain(); + if (!(chain instanceof X509Certificate[])) { + throw new KeyStoreException + (new UnsupportedOperationException + ("unsupported certificate array type: " + + chain.getClass().getName())); + } + + try { + boolean updatedAlias = false; + Set aliases = aliasMap.keySet(); + for (String oldAlias : aliases) { + + // see if there's an existing entry with the same info + + AliasInfo aliasInfo = aliasMap.get(oldAlias); + if (aliasInfo.type == ATTR_CLASS_PKEY && + aliasInfo.cert.getPublicKey().equals + (chain[0].getPublicKey())) { + + // found existing entry - + // caller is renaming entry or updating cert chain + // + // set new CKA_LABEL/CKA_ID + // and update certs if necessary + + updatePkey(alias, + aliasInfo.id, + (X509Certificate[])chain, + !aliasInfo.cert.equals(chain[0])); + updatedAlias = true; + break; + } + } + + if (!updatedAlias) { + // caller adding new entry + engineDeleteEntry(alias); + storePkey(alias, (KeyStore.PrivateKeyEntry)entry); + } + + } catch (PKCS11Exception | CertificateException pe) { + throw new KeyStoreException(pe); + } + + } else if (entry instanceof KeyStore.SecretKeyEntry) { + + KeyStore.SecretKeyEntry ske = (KeyStore.SecretKeyEntry)entry; + SecretKey skey = ske.getSecretKey(); + + try { + // first check if the key already exists + AliasInfo aliasInfo = aliasMap.get(alias); + + if (aliasInfo != null) { + engineDeleteEntry(alias); + } + storeSkey(alias, ske); + + } catch (PKCS11Exception pe) { + throw new KeyStoreException(pe); + } + + } else { + throw new KeyStoreException(new UnsupportedOperationException + ("unsupported entry type: " + entry.getClass().getName())); + } + + try { + + // XXX NSS does not write out the CKA_ID we pass to them + // + // therefore we must re-map labels + // (can not simply update aliasMap) + + mapLabels(); + if (debug != null) { + dumpTokenMap(); + } + } catch (PKCS11Exception | CertificateException pe) { + throw new KeyStoreException(pe); + } + } + + if (debug != null) { + debug.println + ("engineSetEntry added new entry for [" + + alias + + "] to token"); + } + } + + /** + * Determines if the keystore Entry for the specified + * alias is an instance or subclass of the specified + * entryClass. + * + * @param alias the alias name + * @param entryClass the entry class + * + * @return true if the keystore Entry for the specified + * alias is an instance or subclass of the + * specified entryClass, false otherwise + */ + public synchronized boolean engineEntryInstanceOf + (String alias, Class entryClass) { + token.ensureValid(); + return super.engineEntryInstanceOf(alias, entryClass); + } + + private X509Certificate loadCert(Session session, long oHandle) + throws PKCS11Exception, CertificateException { + + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] + { new CK_ATTRIBUTE(CKA_VALUE) }; + token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); + + byte[] bytes = attrs[0].getByteArray(); + if (bytes == null) { + throw new CertificateException + ("unexpectedly retrieved null byte array"); + } + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate)cf.generateCertificate + (new ByteArrayInputStream(bytes)); + } + + private X509Certificate[] loadChain(Session session, + X509Certificate endCert) + throws PKCS11Exception, CertificateException { + + ArrayList lChain = null; + + if (endCert.getSubjectX500Principal().equals + (endCert.getIssuerX500Principal())) { + // self signed + return new X509Certificate[] { endCert }; + } else { + lChain = new ArrayList(); + lChain.add(endCert); + } + + // try loading remaining certs in chain by following + // issuer->subject links + + X509Certificate next = endCert; + while (true) { + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_CERT, + new CK_ATTRIBUTE(CKA_SUBJECT, + next.getIssuerX500Principal().getEncoded()) }; + long[] ch = findObjects(session, attrs); + + if (ch == null || ch.length == 0) { + // done + break; + } else { + // if more than one found, use first + if (debug != null && ch.length > 1) { + debug.println("engineGetEntry found " + + ch.length + + " certificate entries for subject [" + + next.getIssuerX500Principal().toString() + + "] in token - using first entry"); + } + + next = loadCert(session, ch[0]); + lChain.add(next); + if (next.getSubjectX500Principal().equals + (next.getIssuerX500Principal())) { + // self signed + break; + } + } + } + + return lChain.toArray(new X509Certificate[lChain.size()]); + } + + private SecretKey loadSkey(Session session, long oHandle) + throws PKCS11Exception { + + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_KEY_TYPE) }; + token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); + long kType = attrs[0].getLong(); + + String keyType = null; + int keyLength = -1; + + // XXX NSS mangles the stored key type for secret key token objects + + if (kType == CKK_DES || kType == CKK_DES3) { + if (kType == CKK_DES) { + keyType = "DES"; + keyLength = 64; + } else if (kType == CKK_DES3) { + keyType = "DESede"; + keyLength = 192; + } + } else { + if (kType == CKK_AES) { + keyType = "AES"; + } else if (kType == CKK_BLOWFISH) { + keyType = "Blowfish"; + } else if (kType == CKK_RC4) { + keyType = "ARCFOUR"; + } else { + if (debug != null) { + debug.println("unknown key type [" + + kType + + "] - using 'Generic Secret'"); + } + keyType = "Generic Secret"; + } + + // XXX NSS problem CKR_ATTRIBUTE_TYPE_INVALID? + if (NSS_TEST) { + keyLength = 128; + } else { + attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_VALUE_LEN) }; + token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); + keyLength = (int)attrs[0].getLong(); + } + } + + return P11Key.secretKey(session, oHandle, keyType, keyLength, null); + } + + private PrivateKey loadPkey(Session session, long oHandle) + throws PKCS11Exception, KeyStoreException { + + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_KEY_TYPE) }; + token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); + long kType = attrs[0].getLong(); + String keyType = null; + int keyLength = 0; + + if (kType == CKK_RSA) { + + keyType = "RSA"; + + attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_MODULUS) }; + token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); + BigInteger modulus = attrs[0].getBigInteger(); + keyLength = modulus.bitLength(); + + // This check will combine our "don't care" values here + // with the system-wide min/max values. + try { + RSAKeyFactory.checkKeyLengths(keyLength, null, + -1, Integer.MAX_VALUE); + } catch (InvalidKeyException e) { + throw new KeyStoreException(e.getMessage()); + } + + return P11Key.privateKey(session, + oHandle, + keyType, + keyLength, + null); + + } else if (kType == CKK_DSA) { + + keyType = "DSA"; + + attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_PRIME) }; + token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); + BigInteger prime = attrs[0].getBigInteger(); + keyLength = prime.bitLength(); + + return P11Key.privateKey(session, + oHandle, + keyType, + keyLength, + null); + + } else if (kType == CKK_DH) { + + keyType = "DH"; + + attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_PRIME) }; + token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); + BigInteger prime = attrs[0].getBigInteger(); + keyLength = prime.bitLength(); + + return P11Key.privateKey(session, + oHandle, + keyType, + keyLength, + null); + + } else if (kType == CKK_EC) { + + attrs = new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_EC_PARAMS), + }; + token.p11.C_GetAttributeValue(session.id(), oHandle, attrs); + byte[] encodedParams = attrs[0].getByteArray(); + try { + ECParameterSpec params = + ECUtil.getECParameterSpec(null, encodedParams); + keyLength = params.getCurve().getField().getFieldSize(); + } catch (IOException e) { + // we do not want to accept key with unsupported parameters + throw new KeyStoreException("Unsupported parameters", e); + } + + return P11Key.privateKey(session, oHandle, "EC", keyLength, null); + + } else { + if (debug != null) { + debug.println("unknown key type [" + kType + "]"); + } + throw new KeyStoreException("unknown key type"); + } + } + + + /** + * XXX On ibutton, when you C_SetAttribute(CKA_ID) for a private key + * it not only changes the CKA_ID of the private key, + * it changes the CKA_ID of the corresponding cert too. + * And vice versa. + * + * XXX On ibutton, CKR_DEVICE_ERROR if you C_SetAttribute(CKA_ID) + * for a private key, and then try to delete the corresponding cert. + * So this code reverses the order. + * After the cert is first destroyed (if necessary), + * then the CKA_ID of the private key can be changed successfully. + * + * @param replaceCert if true, then caller is updating alias info for + * existing cert (only update CKA_ID/CKA_LABEL). + * if false, then caller is updating cert chain + * (delete old end cert and add new chain). + */ + private void updatePkey(String alias, + byte[] cka_id, + X509Certificate[] chain, + boolean replaceCert) throws + KeyStoreException, CertificateException, PKCS11Exception { + + // XXX + // + // always set replaceCert to true + // + // NSS does not allow resetting of CKA_LABEL on an existing cert + // (C_SetAttribute call succeeds, but is ignored) + + replaceCert = true; + + Session session = null; + try { + session = token.getOpSession(); + + // first get private key object handle and hang onto it + + THandle h = getTokenObject(session, ATTR_CLASS_PKEY, cka_id, null); + long pKeyHandle; + if (h.type == ATTR_CLASS_PKEY) { + pKeyHandle = h.handle; + } else { + throw new KeyStoreException + ("expected but could not find private key " + + "with CKA_ID " + + getID(cka_id)); + } + + // next find existing end entity cert + + h = getTokenObject(session, ATTR_CLASS_CERT, cka_id, null); + if (h.type != ATTR_CLASS_CERT) { + throw new KeyStoreException + ("expected but could not find certificate " + + "with CKA_ID " + + getID(cka_id)); + } else { + if (replaceCert) { + // replacing existing cert and chain + destroyChain(cka_id); + } else { + // renaming alias for existing cert + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_LABEL, alias), + new CK_ATTRIBUTE(CKA_ID, alias) }; + token.p11.C_SetAttributeValue + (session.id(), h.handle, attrs); + } + } + + // add new chain + + if (replaceCert) { + // add all certs in chain + storeChain(alias, chain); + } else { + // already updated alias info for existing end cert - + // just update CA certs + storeCaCerts(chain, 1); + } + + // finally update CKA_ID for private key + // + // ibutton may have already done this (that is ok) + + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_ID, alias) }; + token.p11.C_SetAttributeValue(session.id(), pKeyHandle, attrs); + + if (debug != null) { + debug.println("updatePkey set new alias [" + + alias + + "] for private key entry"); + } + } finally { + token.releaseSession(session); + } + } + + private void updateP11Pkey(String alias, CK_ATTRIBUTE attribute, P11Key key) + throws PKCS11Exception { + + // if token key, update alias. + // if session key, convert to token key. + + Session session = null; + try { + session = token.getOpSession(); + if (key.tokenObject == true) { + + // token key - set new CKA_ID + + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_ID, alias) }; + token.p11.C_SetAttributeValue + (session.id(), key.keyID, attrs); + if (debug != null) { + debug.println("updateP11Pkey set new alias [" + + alias + + "] for key entry"); + } + } else { + + // session key - convert to token key and set CKA_ID + + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + new CK_ATTRIBUTE(CKA_ID, alias), + }; + if (attribute != null) { + attrs = addAttribute(attrs, attribute); + } + token.p11.C_CopyObject(session.id(), key.keyID, attrs); + if (debug != null) { + debug.println("updateP11Pkey copied private session key " + + "for [" + + alias + + "] to token entry"); + } + } + } finally { + token.releaseSession(session); + } + } + + private void storeCert(String alias, X509Certificate cert) + throws PKCS11Exception, CertificateException { + + ArrayList attrList = new ArrayList(); + attrList.add(ATTR_TOKEN_TRUE); + attrList.add(ATTR_CLASS_CERT); + attrList.add(ATTR_X509_CERT_TYPE); + attrList.add(new CK_ATTRIBUTE(CKA_SUBJECT, + cert.getSubjectX500Principal().getEncoded())); + attrList.add(new CK_ATTRIBUTE(CKA_ISSUER, + cert.getIssuerX500Principal().getEncoded())); + attrList.add(new CK_ATTRIBUTE(CKA_SERIAL_NUMBER, + cert.getSerialNumber().toByteArray())); + attrList.add(new CK_ATTRIBUTE(CKA_VALUE, cert.getEncoded())); + + if (alias != null) { + attrList.add(new CK_ATTRIBUTE(CKA_LABEL, alias)); + attrList.add(new CK_ATTRIBUTE(CKA_ID, alias)); + } else { + // ibutton requires something to be set + // - alias must be unique + attrList.add(new CK_ATTRIBUTE(CKA_ID, + getID(cert.getSubjectX500Principal().getName + (X500Principal.CANONICAL), cert))); + } + + Session session = null; + try { + session = token.getOpSession(); + token.p11.C_CreateObject(session.id(), + attrList.toArray(new CK_ATTRIBUTE[attrList.size()])); + } finally { + token.releaseSession(session); + } + } + + private void storeChain(String alias, X509Certificate[] chain) + throws PKCS11Exception, CertificateException { + + // add new chain + // + // end cert has CKA_LABEL and CKA_ID set to alias. + // other certs in chain have neither set. + + storeCert(alias, chain[0]); + storeCaCerts(chain, 1); + } + + private void storeCaCerts(X509Certificate[] chain, int start) + throws PKCS11Exception, CertificateException { + + // do not add duplicate CA cert if already in token + // + // XXX ibutton stores duplicate CA certs, NSS does not + + Session session = null; + HashSet cacerts = new HashSet(); + try { + session = token.getOpSession(); + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_CERT }; + long[] handles = findObjects(session, attrs); + + // load certs currently on the token + for (long handle : handles) { + cacerts.add(loadCert(session, handle)); + } + } finally { + token.releaseSession(session); + } + + for (int i = start; i < chain.length; i++) { + if (!cacerts.contains(chain[i])) { + storeCert(null, chain[i]); + } else if (debug != null) { + debug.println("ignoring duplicate CA cert for [" + + chain[i].getSubjectX500Principal() + + "]"); + } + } + } + + private void storeSkey(String alias, KeyStore.SecretKeyEntry ske) + throws PKCS11Exception, KeyStoreException { + + SecretKey skey = ske.getSecretKey(); + // No need to specify CKA_CLASS, CKA_KEY_TYPE, CKA_VALUE since + // they are handled in P11SecretKeyFactory.createKey() method. + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + ATTR_SKEY_TOKEN_TRUE, + ATTR_PRIVATE_TRUE, + new CK_ATTRIBUTE(CKA_LABEL, alias), + }; + try { + P11SecretKeyFactory.convertKey(token, skey, null, attrs); + } catch (InvalidKeyException ike) { + // re-throw KeyStoreException to match javadoc + throw new KeyStoreException("Cannot convert to PKCS11 keys", ike); + } + + // update global alias map + aliasMap.put(alias, new AliasInfo(alias)); + + if (debug != null) { + debug.println("storeSkey created token secret key for [" + + alias + "]"); + } + } + + private static CK_ATTRIBUTE[] addAttribute(CK_ATTRIBUTE[] attrs, CK_ATTRIBUTE attr) { + int n = attrs.length; + CK_ATTRIBUTE[] newAttrs = new CK_ATTRIBUTE[n + 1]; + System.arraycopy(attrs, 0, newAttrs, 0, n); + newAttrs[n] = attr; + return newAttrs; + } + + private void storePkey(String alias, KeyStore.PrivateKeyEntry pke) + throws PKCS11Exception, CertificateException, KeyStoreException { + + PrivateKey key = pke.getPrivateKey(); + CK_ATTRIBUTE[] attrs = null; + + // If the key is a token object on this token, update it instead + // of creating a duplicate key object. + // Otherwise, treat a P11Key like any other key, if it is extractable. + if (key instanceof P11Key) { + P11Key p11Key = (P11Key)key; + if (p11Key.tokenObject && (p11Key.token == this.token)) { + updateP11Pkey(alias, null, p11Key); + storeChain(alias, (X509Certificate[])pke.getCertificateChain()); + return; + } + } + + boolean useNDB = token.config.getNssNetscapeDbWorkaround(); + PublicKey publicKey = pke.getCertificate().getPublicKey(); + + if (key instanceof RSAPrivateKey) { + + X509Certificate cert = (X509Certificate)pke.getCertificate(); + attrs = getRsaPrivKeyAttrs + (alias, (RSAPrivateKey)key, cert.getSubjectX500Principal()); + + } else if (key instanceof DSAPrivateKey) { + + DSAPrivateKey dsaKey = (DSAPrivateKey)key; + + CK_ATTRIBUTE[] idAttrs = getIdAttributes(key, publicKey, false, useNDB); + if (idAttrs[0] == null) { + idAttrs[0] = new CK_ATTRIBUTE(CKA_ID, alias); + } + + attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_PKEY, + ATTR_PRIVATE_TRUE, + new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_DSA), + idAttrs[0], + new CK_ATTRIBUTE(CKA_PRIME, dsaKey.getParams().getP()), + new CK_ATTRIBUTE(CKA_SUBPRIME, dsaKey.getParams().getQ()), + new CK_ATTRIBUTE(CKA_BASE, dsaKey.getParams().getG()), + new CK_ATTRIBUTE(CKA_VALUE, dsaKey.getX()), + }; + if (idAttrs[1] != null) { + attrs = addAttribute(attrs, idAttrs[1]); + } + + attrs = token.getAttributes + (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_DSA, attrs); + + if (debug != null) { + debug.println("storePkey created DSA template"); + } + + } else if (key instanceof DHPrivateKey) { + + DHPrivateKey dhKey = (DHPrivateKey)key; + + CK_ATTRIBUTE[] idAttrs = getIdAttributes(key, publicKey, false, useNDB); + if (idAttrs[0] == null) { + idAttrs[0] = new CK_ATTRIBUTE(CKA_ID, alias); + } + + attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_PKEY, + ATTR_PRIVATE_TRUE, + new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_DH), + idAttrs[0], + new CK_ATTRIBUTE(CKA_PRIME, dhKey.getParams().getP()), + new CK_ATTRIBUTE(CKA_BASE, dhKey.getParams().getG()), + new CK_ATTRIBUTE(CKA_VALUE, dhKey.getX()), + }; + if (idAttrs[1] != null) { + attrs = addAttribute(attrs, idAttrs[1]); + } + + attrs = token.getAttributes + (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_DH, attrs); + + } else if (key instanceof ECPrivateKey) { + + ECPrivateKey ecKey = (ECPrivateKey)key; + + CK_ATTRIBUTE[] idAttrs = getIdAttributes(key, publicKey, false, useNDB); + if (idAttrs[0] == null) { + idAttrs[0] = new CK_ATTRIBUTE(CKA_ID, alias); + } + + byte[] encodedParams = + ECUtil.encodeECParameterSpec(null, ecKey.getParams()); + attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_PKEY, + ATTR_PRIVATE_TRUE, + new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_EC), + idAttrs[0], + new CK_ATTRIBUTE(CKA_VALUE, ecKey.getS()), + new CK_ATTRIBUTE(CKA_EC_PARAMS, encodedParams), + }; + if (idAttrs[1] != null) { + attrs = addAttribute(attrs, idAttrs[1]); + } + + attrs = token.getAttributes + (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_EC, attrs); + + if (debug != null) { + debug.println("storePkey created EC template"); + } + + } else if (key instanceof P11Key) { + // sensitive/non-extractable P11Key + P11Key p11Key = (P11Key)key; + if (p11Key.token != this.token) { + throw new KeyStoreException + ("Cannot move sensitive keys across tokens"); + } + CK_ATTRIBUTE netscapeDB = null; + if (useNDB) { + // Note that this currently fails due to an NSS bug. + // They do not allow the CKA_NETSCAPE_DB attribute to be + // specified during C_CopyObject() and fail with + // CKR_ATTRIBUTE_READ_ONLY. + // But if we did not specify it, they would fail with + // CKA_TEMPLATE_INCOMPLETE, so leave this code in here. + CK_ATTRIBUTE[] idAttrs = getIdAttributes(key, publicKey, false, true); + netscapeDB = idAttrs[1]; + } + // Update the key object. + updateP11Pkey(alias, netscapeDB, p11Key); + storeChain(alias, (X509Certificate[])pke.getCertificateChain()); + return; + + } else { + throw new KeyStoreException("unsupported key type: " + key); + } + + Session session = null; + try { + session = token.getOpSession(); + + // create private key entry + token.p11.C_CreateObject(session.id(), attrs); + if (debug != null) { + debug.println("storePkey created token key for [" + + alias + + "]"); + } + } finally { + token.releaseSession(session); + } + + storeChain(alias, (X509Certificate[])pke.getCertificateChain()); + } + + private CK_ATTRIBUTE[] getRsaPrivKeyAttrs(String alias, + RSAPrivateKey key, + X500Principal subject) throws PKCS11Exception { + + // subject is currently ignored - could be used to set CKA_SUBJECT + + CK_ATTRIBUTE[] attrs = null; + if (key instanceof RSAPrivateCrtKey) { + + if (debug != null) { + debug.println("creating RSAPrivateCrtKey attrs"); + } + + RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey)key; + + attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_PKEY, + ATTR_PRIVATE_TRUE, + new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_RSA), + new CK_ATTRIBUTE(CKA_ID, alias), + new CK_ATTRIBUTE(CKA_MODULUS, + rsaKey.getModulus()), + new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT, + rsaKey.getPrivateExponent()), + new CK_ATTRIBUTE(CKA_PUBLIC_EXPONENT, + rsaKey.getPublicExponent()), + new CK_ATTRIBUTE(CKA_PRIME_1, + rsaKey.getPrimeP()), + new CK_ATTRIBUTE(CKA_PRIME_2, + rsaKey.getPrimeQ()), + new CK_ATTRIBUTE(CKA_EXPONENT_1, + rsaKey.getPrimeExponentP()), + new CK_ATTRIBUTE(CKA_EXPONENT_2, + rsaKey.getPrimeExponentQ()), + new CK_ATTRIBUTE(CKA_COEFFICIENT, + rsaKey.getCrtCoefficient()) }; + attrs = token.getAttributes + (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_RSA, attrs); + + } else { + + if (debug != null) { + debug.println("creating RSAPrivateKey attrs"); + } + + RSAPrivateKey rsaKey = key; + + attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_PKEY, + ATTR_PRIVATE_TRUE, + new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_RSA), + new CK_ATTRIBUTE(CKA_ID, alias), + new CK_ATTRIBUTE(CKA_MODULUS, + rsaKey.getModulus()), + new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT, + rsaKey.getPrivateExponent()) }; + attrs = token.getAttributes + (TemplateManager.O_IMPORT, CKO_PRIVATE_KEY, CKK_RSA, attrs); + } + + return attrs; + } + + /** + * Compute the CKA_ID and/or CKA_NETSCAPE_DB attributes that should be + * used for this private key. It uses the same algorithm to calculate the + * values as NSS. The public and private keys MUST match for the result to + * be correct. + * + * It returns a 2 element array with CKA_ID at index 0 and CKA_NETSCAPE_DB + * at index 1. The boolean flags determine what is to be calculated. + * If false or if we could not calculate the value, that element is null. + * + * NOTE that we currently do not use the CKA_ID value calculated by this + * method. + */ + private CK_ATTRIBUTE[] getIdAttributes(PrivateKey privateKey, + PublicKey publicKey, boolean id, boolean netscapeDb) { + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[2]; + if ((id || netscapeDb) == false) { + return attrs; + } + String alg = privateKey.getAlgorithm(); + if (id && alg.equals("RSA") && (publicKey instanceof RSAPublicKey)) { + // CKA_NETSCAPE_DB not needed for RSA public keys + BigInteger n = ((RSAPublicKey)publicKey).getModulus(); + attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(getMagnitude(n))); + } else if (alg.equals("DSA") && (publicKey instanceof DSAPublicKey)) { + BigInteger y = ((DSAPublicKey)publicKey).getY(); + if (id) { + attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(getMagnitude(y))); + } + if (netscapeDb) { + attrs[1] = new CK_ATTRIBUTE(CKA_NETSCAPE_DB, y); + } + } else if (alg.equals("DH") && (publicKey instanceof DHPublicKey)) { + BigInteger y = ((DHPublicKey)publicKey).getY(); + if (id) { + attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(getMagnitude(y))); + } + if (netscapeDb) { + attrs[1] = new CK_ATTRIBUTE(CKA_NETSCAPE_DB, y); + } + } else if (alg.equals("EC") && (publicKey instanceof ECPublicKey)) { + ECPublicKey ecPub = (ECPublicKey)publicKey; + ECPoint point = ecPub.getW(); + ECParameterSpec params = ecPub.getParams(); + byte[] encodedPoint = ECUtil.encodePoint(point, params.getCurve()); + if (id) { + attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(encodedPoint)); + } + if (netscapeDb) { + attrs[1] = new CK_ATTRIBUTE(CKA_NETSCAPE_DB, encodedPoint); + } + } else { + throw new RuntimeException("Unknown key algorithm " + alg); + } + return attrs; + } + + /** + * return true if cert destroyed + */ + private boolean destroyCert(byte[] cka_id) + throws PKCS11Exception, KeyStoreException { + Session session = null; + try { + session = token.getOpSession(); + THandle h = getTokenObject(session, ATTR_CLASS_CERT, cka_id, null); + if (h.type != ATTR_CLASS_CERT) { + return false; + } + + token.p11.C_DestroyObject(session.id(), h.handle); + if (debug != null) { + debug.println("destroyCert destroyed cert with CKA_ID [" + + getID(cka_id) + + "]"); + } + return true; + } finally { + token.releaseSession(session); + } + } + + /** + * return true if chain destroyed + */ + private boolean destroyChain(byte[] cka_id) + throws PKCS11Exception, CertificateException, KeyStoreException { + + Session session = null; + try { + session = token.getOpSession(); + + THandle h = getTokenObject(session, ATTR_CLASS_CERT, cka_id, null); + if (h.type != ATTR_CLASS_CERT) { + if (debug != null) { + debug.println("destroyChain could not find " + + "end entity cert with CKA_ID [0x" + + Functions.toHexString(cka_id) + + "]"); + } + return false; + } + + X509Certificate endCert = loadCert(session, h.handle); + token.p11.C_DestroyObject(session.id(), h.handle); + if (debug != null) { + debug.println("destroyChain destroyed end entity cert " + + "with CKA_ID [" + + getID(cka_id) + + "]"); + } + + // build chain following issuer->subject links + + X509Certificate next = endCert; + while (true) { + + if (next.getSubjectX500Principal().equals + (next.getIssuerX500Principal())) { + // self signed - done + break; + } + + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_CERT, + new CK_ATTRIBUTE(CKA_SUBJECT, + next.getIssuerX500Principal().getEncoded()) }; + long[] ch = findObjects(session, attrs); + + if (ch == null || ch.length == 0) { + // done + break; + } else { + // if more than one found, use first + if (debug != null && ch.length > 1) { + debug.println("destroyChain found " + + ch.length + + " certificate entries for subject [" + + next.getIssuerX500Principal() + + "] in token - using first entry"); + } + + next = loadCert(session, ch[0]); + + // only delete if not part of any other chain + + attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_CERT, + new CK_ATTRIBUTE(CKA_ISSUER, + next.getSubjectX500Principal().getEncoded()) }; + long[] issuers = findObjects(session, attrs); + + boolean destroyIt = false; + if (issuers == null || issuers.length == 0) { + // no other certs with this issuer - + // destroy it + destroyIt = true; + } else if (issuers.length == 1) { + X509Certificate iCert = loadCert(session, issuers[0]); + if (next.equals(iCert)) { + // only cert with issuer is itself (self-signed) - + // destroy it + destroyIt = true; + } + } + + if (destroyIt) { + token.p11.C_DestroyObject(session.id(), ch[0]); + if (debug != null) { + debug.println + ("destroyChain destroyed cert in chain " + + "with subject [" + + next.getSubjectX500Principal() + "]"); + } + } else { + if (debug != null) { + debug.println("destroyChain did not destroy " + + "shared cert in chain with subject [" + + next.getSubjectX500Principal() + "]"); + } + } + } + } + + return true; + + } finally { + token.releaseSession(session); + } + } + + /** + * return true if secret key destroyed + */ + private boolean destroySkey(String alias) + throws PKCS11Exception, KeyStoreException { + Session session = null; + try { + session = token.getOpSession(); + + THandle h = getTokenObject(session, ATTR_CLASS_SKEY, null, alias); + if (h.type != ATTR_CLASS_SKEY) { + if (debug != null) { + debug.println("destroySkey did not find secret key " + + "with CKA_LABEL [" + + alias + + "]"); + } + return false; + } + token.p11.C_DestroyObject(session.id(), h.handle); + return true; + } finally { + token.releaseSession(session); + } + } + + /** + * return true if private key destroyed + */ + private boolean destroyPkey(byte[] cka_id) + throws PKCS11Exception, KeyStoreException { + Session session = null; + try { + session = token.getOpSession(); + + THandle h = getTokenObject(session, ATTR_CLASS_PKEY, cka_id, null); + if (h.type != ATTR_CLASS_PKEY) { + if (debug != null) { + debug.println + ("destroyPkey did not find private key with CKA_ID [" + + getID(cka_id) + + "]"); + } + return false; + } + token.p11.C_DestroyObject(session.id(), h.handle); + return true; + } finally { + token.releaseSession(session); + } + } + + /** + * build [alias + issuer + serialNumber] string from a cert + */ + private String getID(String alias, X509Certificate cert) { + X500Principal issuer = cert.getIssuerX500Principal(); + BigInteger serialNum = cert.getSerialNumber(); + + return alias + + ALIAS_SEP + + issuer.getName(X500Principal.CANONICAL) + + ALIAS_SEP + + serialNum.toString(); + } + + /** + * build CKA_ID string from bytes + */ + private static String getID(byte[] bytes) { + boolean printable = true; + for (int i = 0; i < bytes.length; i++) { + if (!DerValue.isPrintableStringChar((char)bytes[i])) { + printable = false; + break; + } + } + + if (!printable) { + return "0x" + Functions.toHexString(bytes); + } else { + try { + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException uee) { + return "0x" + Functions.toHexString(bytes); + } + } + } + + /** + * find an object on the token + * + * @param type either ATTR_CLASS_CERT, ATTR_CLASS_PKEY, or ATTR_CLASS_SKEY + * @param cka_id the CKA_ID if type is ATTR_CLASS_CERT or ATTR_CLASS_PKEY + * @param cka_label the CKA_LABEL if type is ATTR_CLASS_SKEY + */ + private THandle getTokenObject(Session session, + CK_ATTRIBUTE type, + byte[] cka_id, + String cka_label) + throws PKCS11Exception, KeyStoreException { + + CK_ATTRIBUTE[] attrs; + if (type == ATTR_CLASS_SKEY) { + attrs = new CK_ATTRIBUTE[] { + ATTR_SKEY_TOKEN_TRUE, + new CK_ATTRIBUTE(CKA_LABEL, cka_label), + type }; + } else { + attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + new CK_ATTRIBUTE(CKA_ID, cka_id), + type }; + } + long[] h = findObjects(session, attrs); + if (h.length == 0) { + if (debug != null) { + if (type == ATTR_CLASS_SKEY) { + debug.println("getTokenObject did not find secret key " + + "with CKA_LABEL [" + + cka_label + + "]"); + } else if (type == ATTR_CLASS_CERT) { + debug.println + ("getTokenObject did not find cert with CKA_ID [" + + getID(cka_id) + + "]"); + } else { + debug.println("getTokenObject did not find private key " + + "with CKA_ID [" + + getID(cka_id) + + "]"); + } + } + } else if (h.length == 1) { + + // found object handle - return it + return new THandle(h[0], type); + + } else { + + // found multiple object handles - + // see if token ignored CKA_LABEL during search (e.g. NSS) + + if (type == ATTR_CLASS_SKEY) { + + ArrayList list = new ArrayList(h.length); + for (int i = 0; i < h.length; i++) { + + CK_ATTRIBUTE[] label = new CK_ATTRIBUTE[] + { new CK_ATTRIBUTE(CKA_LABEL) }; + token.p11.C_GetAttributeValue(session.id(), h[i], label); + if (label[0].pValue != null && + cka_label.equals(new String(label[0].getCharArray()))) { + list.add(new THandle(h[i], ATTR_CLASS_SKEY)); + } + } + if (list.size() == 1) { + // yes, there was only one CKA_LABEL that matched + return list.get(0); + } else { + throw new KeyStoreException("invalid KeyStore state: " + + "found " + + list.size() + + " secret keys sharing CKA_LABEL [" + + cka_label + + "]"); + } + } else if (type == ATTR_CLASS_CERT) { + throw new KeyStoreException("invalid KeyStore state: " + + "found " + + h.length + + " certificates sharing CKA_ID " + + getID(cka_id)); + } else { + throw new KeyStoreException("invalid KeyStore state: " + + "found " + + h.length + + " private keys sharing CKA_ID " + + getID(cka_id)); + } + } + return new THandle(NO_HANDLE, null); + } + + /** + * Create a mapping of all key pairs, trusted certs, and secret keys + * on the token into logical KeyStore entries unambiguously + * accessible via an alias. + * + * If the token is removed, the map may contain stale values. + * KeyStore.load should be called to re-create the map. + * + * Assume all private keys and matching certs share a unique CKA_ID. + * + * Assume all secret keys have a unique CKA_LABEL. + * + * @return true if multiple certs found sharing the same CKA_LABEL + * (if so, write capabilities are disabled) + */ + private boolean mapLabels() throws + PKCS11Exception, CertificateException, KeyStoreException { + + CK_ATTRIBUTE[] trustedAttr = new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_TRUSTED) }; + + Session session = null; + try { + session = token.getOpSession(); + + // get all private key CKA_IDs + + ArrayList pkeyIDs = new ArrayList(); + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_PKEY, + }; + long[] handles = findObjects(session, attrs); + + for (long handle : handles) { + attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_ID) }; + token.p11.C_GetAttributeValue(session.id(), handle, attrs); + + if (attrs[0].pValue != null) { + pkeyIDs.add(attrs[0].getByteArray()); + } + } + + // Get all certificates + // + // If cert does not have a CKA_LABEL nor CKA_ID, it is ignored. + // + // Get the CKA_LABEL for each cert + // (if the cert does not have a CKA_LABEL, use the CKA_ID). + // + // Map each cert to the its CKA_LABEL + // (multiple certs may be mapped to a single CKA_LABEL) + + HashMap> certMap = + new HashMap>(); + + attrs = new CK_ATTRIBUTE[] { + ATTR_TOKEN_TRUE, + ATTR_CLASS_CERT, + }; + handles = findObjects(session, attrs); + + for (long handle : handles) { + attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_LABEL) }; + + String cka_label = null; + byte[] cka_id = null; + try { + token.p11.C_GetAttributeValue(session.id(), handle, attrs); + if (attrs[0].pValue != null) { + // there is a CKA_LABEL + cka_label = new String(attrs[0].getCharArray()); + } + } catch (PKCS11Exception pe) { + if (pe.getErrorCode() != CKR_ATTRIBUTE_TYPE_INVALID) { + throw pe; + } + + // GetAttributeValue for CKA_LABEL not supported + // + // XXX SCA1000 + } + + // get CKA_ID + + attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_ID) }; + token.p11.C_GetAttributeValue(session.id(), handle, attrs); + if (attrs[0].pValue == null) { + if (cka_label == null) { + // no cka_label nor cka_id - ignore + continue; + } + } else { + if (cka_label == null) { + // use CKA_ID as CKA_LABEL + cka_label = getID(attrs[0].getByteArray()); + } + cka_id = attrs[0].getByteArray(); + } + + X509Certificate cert = loadCert(session, handle); + + // get CKA_TRUSTED + + boolean cka_trusted = false; + + if (useSecmodTrust) { + cka_trusted = Secmod.getInstance().isTrusted(cert, nssTrustType); + } else { + if (CKA_TRUSTED_SUPPORTED) { + try { + token.p11.C_GetAttributeValue + (session.id(), handle, trustedAttr); + cka_trusted = trustedAttr[0].getBoolean(); + } catch (PKCS11Exception pe) { + if (pe.getErrorCode() == CKR_ATTRIBUTE_TYPE_INVALID) { + // XXX NSS, ibutton, sca1000 + CKA_TRUSTED_SUPPORTED = false; + if (debug != null) { + debug.println + ("CKA_TRUSTED attribute not supported"); + } + } + } + } + } + + HashSet infoSet = certMap.get(cka_label); + if (infoSet == null) { + infoSet = new HashSet(2); + certMap.put(cka_label, infoSet); + } + + // initially create private key entry AliasInfo entries - + // these entries will get resolved into their true + // entry types later + + infoSet.add(new AliasInfo + (cka_label, + cka_id, + cka_trusted, + cert)); + } + + // create list secret key CKA_LABELS - + // if there are duplicates (either between secret keys, + // or between a secret key and another object), + // throw an exception + HashMap sKeyMap = + new HashMap(); + + attrs = new CK_ATTRIBUTE[] { + ATTR_SKEY_TOKEN_TRUE, + ATTR_CLASS_SKEY, + }; + handles = findObjects(session, attrs); + + for (long handle : handles) { + attrs = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_LABEL) }; + token.p11.C_GetAttributeValue(session.id(), handle, attrs); + if (attrs[0].pValue != null) { + + // there is a CKA_LABEL + String cka_label = new String(attrs[0].getCharArray()); + if (sKeyMap.get(cka_label) == null) { + sKeyMap.put(cka_label, new AliasInfo(cka_label)); + } else { + throw new KeyStoreException("invalid KeyStore state: " + + "found multiple secret keys sharing same " + + "CKA_LABEL [" + + cka_label + + "]"); + } + } + } + + // update global aliasMap with alias mappings + ArrayList matchedCerts = + mapPrivateKeys(pkeyIDs, certMap); + boolean sharedLabel = mapCerts(matchedCerts, certMap); + mapSecretKeys(sKeyMap); + + return sharedLabel; + + } finally { + token.releaseSession(session); + } + } + + /** + * for each private key CKA_ID, find corresponding cert with same CKA_ID. + * if found cert, see if cert CKA_LABEL is unique. + * if CKA_LABEL unique, map private key/cert alias to that CKA_LABEL. + * if CKA_LABEL not unique, map private key/cert alias to: + * CKA_LABEL + ALIAS_SEP + ISSUER + ALIAS_SEP + SERIAL + * if cert not found, ignore private key + * (don't support private key entries without a cert chain yet) + * + * @return a list of AliasInfo entries that represents all matches + */ + private ArrayList mapPrivateKeys(ArrayList pkeyIDs, + HashMap> certMap) + throws PKCS11Exception, CertificateException { + + // reset global alias map + aliasMap = new HashMap(); + + // list of matched certs that we will return + ArrayList matchedCerts = new ArrayList(); + + for (byte[] pkeyID : pkeyIDs) { + + // try to find a matching CKA_ID in a certificate + + boolean foundMatch = false; + Set certLabels = certMap.keySet(); + for (String certLabel : certLabels) { + + // get cert CKA_IDs (if present) for each cert + + HashSet infoSet = certMap.get(certLabel); + for (AliasInfo aliasInfo : infoSet) { + if (Arrays.equals(pkeyID, aliasInfo.id)) { + + // found private key with matching cert + + if (infoSet.size() == 1) { + // unique CKA_LABEL - use certLabel as alias + aliasInfo.matched = true; + aliasMap.put(certLabel, aliasInfo); + } else { + // create new alias + aliasInfo.matched = true; + aliasMap.put(getID(certLabel, aliasInfo.cert), + aliasInfo); + } + matchedCerts.add(aliasInfo); + foundMatch = true; + break; + } + } + if (foundMatch) { + break; + } + } + + if (!foundMatch) { + if (debug != null) { + debug.println + ("did not find match for private key with CKA_ID [" + + getID(pkeyID) + + "] (ignoring entry)"); + } + } + } + + return matchedCerts; + } + + /** + * for each cert not matched with a private key but is CKA_TRUSTED: + * if CKA_LABEL unique, map cert to CKA_LABEL. + * if CKA_LABEL not unique, map cert to [label+issuer+serialNum] + * + * if CKA_TRUSTED not supported, treat all certs not part of a chain + * as trusted + * + * @return true if multiple certs found sharing the same CKA_LABEL + */ + private boolean mapCerts(ArrayList matchedCerts, + HashMap> certMap) + throws PKCS11Exception, CertificateException { + + // load all cert chains + for (AliasInfo aliasInfo : matchedCerts) { + Session session = null; + try { + session = token.getOpSession(); + aliasInfo.chain = loadChain(session, aliasInfo.cert); + } finally { + token.releaseSession(session); + } + } + + // find all certs in certMap not part of a cert chain + // - these are trusted + + boolean sharedLabel = false; + + Set certLabels = certMap.keySet(); + for (String certLabel : certLabels) { + HashSet infoSet = certMap.get(certLabel); + for (AliasInfo aliasInfo : infoSet) { + + if (aliasInfo.matched == true) { + // already found a private key match for this cert - + // just continue + aliasInfo.trusted = false; + continue; + } + + // cert in this aliasInfo is not matched yet + // + // if CKA_TRUSTED_SUPPORTED == true, + // then check if cert is trusted + + if (CKA_TRUSTED_SUPPORTED) { + if (aliasInfo.trusted) { + // trusted certificate + if (mapTrustedCert + (certLabel, aliasInfo, infoSet) == true) { + sharedLabel = true; + } + } + continue; + } + + // CKA_TRUSTED_SUPPORTED == false + // + // XXX treat all certs not part of a chain as trusted + // XXX + // XXX Unsupported + // + // boolean partOfChain = false; + // for (AliasInfo matchedInfo : matchedCerts) { + // for (int i = 0; i < matchedInfo.chain.length; i++) { + // if (matchedInfo.chain[i].equals(aliasInfo.cert)) { + // partOfChain = true; + // break; + // } + // } + // if (partOfChain) { + // break; + // } + // } + // + // if (!partOfChain) { + // if (mapTrustedCert(certLabel,aliasInfo,infoSet) == true){ + // sharedLabel = true; + // } + // } else { + // if (debug != null) { + // debug.println("ignoring unmatched/untrusted cert " + + // "that is part of cert chain - cert subject is [" + + // aliasInfo.cert.getSubjectX500Principal().getName + // (X500Principal.CANONICAL) + + // "]"); + // } + // } + } + } + + return sharedLabel; + } + + private boolean mapTrustedCert(String certLabel, + AliasInfo aliasInfo, + HashSet infoSet) { + + boolean sharedLabel = false; + + aliasInfo.type = ATTR_CLASS_CERT; + aliasInfo.trusted = true; + if (infoSet.size() == 1) { + // unique CKA_LABEL - use certLabel as alias + aliasMap.put(certLabel, aliasInfo); + } else { + // create new alias + sharedLabel = true; + aliasMap.put(getID(certLabel, aliasInfo.cert), aliasInfo); + } + + return sharedLabel; + } + + /** + * If the secret key shares a CKA_LABEL with another entry, + * throw an exception + */ + private void mapSecretKeys(HashMap sKeyMap) + throws KeyStoreException { + for (String label : sKeyMap.keySet()) { + if (aliasMap.containsKey(label)) { + throw new KeyStoreException("invalid KeyStore state: " + + "found secret key sharing CKA_LABEL [" + + label + + "] with another token object"); + } + } + aliasMap.putAll(sKeyMap); + } + + private void dumpTokenMap() { + Set aliases = aliasMap.keySet(); + System.out.println("Token Alias Map:"); + if (aliases.isEmpty()) { + System.out.println(" [empty]"); + } else { + for (String s : aliases) { + System.out.println(" " + s + aliasMap.get(s)); + } + } + } + + private void checkWrite() throws KeyStoreException { + if (writeDisabled) { + throw new KeyStoreException + ("This PKCS11KeyStore does not support write capabilities"); + } + } + + private final static long[] LONG0 = new long[0]; + + private static long[] findObjects(Session session, CK_ATTRIBUTE[] attrs) + throws PKCS11Exception { + Token token = session.token; + long[] handles = LONG0; + token.p11.C_FindObjectsInit(session.id(), attrs); + while (true) { + long[] h = token.p11.C_FindObjects(session.id(), FINDOBJECTS_MAX); + if (h.length == 0) { + break; + } + handles = P11Util.concat(handles, h); + } + token.p11.C_FindObjectsFinal(session.id()); + return handles; + } + +}