--- old/src/jdk.crypto.token/share/classes/sun/security/pkcs11/P11KeyStore.java 2017-01-20 09:52:34.562289099 -0800 +++ /dev/null 2017-01-18 09:30:05.425422781 -0800 @@ -1,2683 +0,0 @@ -/* - * 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; - } - -} --- /dev/null 2017-01-18 09:30:05.425422781 -0800 +++ new/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyStore.java 2017-01-20 09:52:34.346289107 -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; + } + +}