mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
2690 lines
99 KiB
Java
2690 lines
99 KiB
Java
/*
|
|
* Copyright (c) 2003, 2019, 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<String, AliasInfo> 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
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
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 <code>setKeyEntry</code>,
|
|
* or by a call to <code>setEntry</code> with a
|
|
* <code>PrivateKeyEntry</code> or <code>SecretKeyEntry</code>.
|
|
*
|
|
* @param alias the alias name
|
|
* @param password the password, which must be <code>null</code>
|
|
*
|
|
* @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 <code>setKeyEntry</code>,
|
|
* or by a call to <code>setEntry</code> with a
|
|
* <code>PrivateKeyEntry</code>.
|
|
*
|
|
* @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.
|
|
*
|
|
* <p> If the given alias name identifies an entry
|
|
* created by a call to <code>setCertificateEntry</code>,
|
|
* or created by a call to <code>setEntry</code> with a
|
|
* <code>TrustedCertificateEntry</code>,
|
|
* then the trusted certificate contained in that entry is returned.
|
|
*
|
|
* <p> If the given alias name identifies an entry
|
|
* created by a call to <code>setKeyEntry</code>,
|
|
* or created by a call to <code>setEntry</code> with a
|
|
* <code>PrivateKeyEntry</code>,
|
|
* 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.
|
|
*
|
|
* <p>If the given key is of type <code>java.security.PrivateKey</code>,
|
|
* it must be accompanied by a certificate chain certifying the
|
|
* corresponding public key.
|
|
*
|
|
* <p>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
|
|
* <code>java.security.PrivateKey</code>).
|
|
*
|
|
* @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.
|
|
*
|
|
* <p>If the protected key is of type
|
|
* <code>java.security.PrivateKey</code>,
|
|
* it must be accompanied by a certificate chain certifying the
|
|
* corresponding public key.
|
|
*
|
|
* <p>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
|
|
* <code>java.security.PrivateKey</code>).
|
|
*
|
|
* @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.
|
|
*
|
|
* <p> If the given alias identifies an existing entry
|
|
* created by a call to <code>setCertificateEntry</code>,
|
|
* or created by a call to <code>setEntry</code> with a
|
|
* <code>TrustedCertificateEntry</code>,
|
|
* 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<String> 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<String>(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 <code>setKeyEntry</code>,
|
|
* or created by a call to <code>setEntry</code> with a
|
|
* <code>PrivateKeyEntry</code> or a <code>SecretKeyEntry</code>.
|
|
*
|
|
* @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 <code>setCertificateEntry</code>,
|
|
* or created by a call to <code>setEntry</code> with a
|
|
* <code>TrustedCertificateEntry</code>.
|
|
*
|
|
* @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.
|
|
*
|
|
* <p>This method attempts to match the given certificate with each
|
|
* keystore entry. If the entry being considered was
|
|
* created by a call to <code>setCertificateEntry</code>,
|
|
* or created by a call to <code>setEntry</code> with a
|
|
* <code>TrustedCertificateEntry</code>,
|
|
* then the given certificate is compared to that entry's certificate.
|
|
*
|
|
* <p> If the entry being considered was
|
|
* created by a call to <code>setKeyEntry</code>,
|
|
* or created by a call to <code>setEntry</code> with a
|
|
* <code>PrivateKeyEntry</code>,
|
|
* 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<String> 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 <code>null</code>
|
|
* @param password this must be <code>null</code>
|
|
*/
|
|
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 <code>null</code>
|
|
*
|
|
* @exception IllegalArgumentException if the given
|
|
* <code>KeyStore.LoadStoreParameter</code>
|
|
* input is not <code>null</code>
|
|
*/
|
|
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 <code>null</code>
|
|
* @param password the password used to unlock the keystore,
|
|
* or <code>null</code> if the token supports a
|
|
* CKF_PROTECTED_AUTHENTICATION_PATH
|
|
*
|
|
* @exception IOException if the given <code>stream</code> is not
|
|
* <code>null</code>, 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();
|
|
debug.println("P11KeyStore load. Entry count: " +
|
|
aliasMap.size());
|
|
}
|
|
} catch (KeyStoreException | PKCS11Exception e) {
|
|
throw new IOException("load failed", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the keystore using the given
|
|
* <code>KeyStore.LoadStoreParameter</code>.
|
|
*
|
|
* <p> The <code>LoadStoreParameter.getProtectionParameter()</code>
|
|
* method is expected to return a <code>KeyStore.PasswordProtection</code>
|
|
* object. The password is retrieved from that object and used
|
|
* to unlock the PKCS#11 token.
|
|
*
|
|
* <p> If the token supports a CKF_PROTECTED_AUTHENTICATION_PATH
|
|
* then the provided password must be <code>null</code>.
|
|
*
|
|
* @param param the <code>KeyStore.LoadStoreParameter</code>
|
|
*
|
|
* @exception IllegalArgumentException if the given
|
|
* <code>KeyStore.LoadStoreParameter</code> is <code>null</code>,
|
|
* or if that parameter returns a <code>null</code>
|
|
* <code>ProtectionParameter</code> 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 <code>KeyStore.Entry</code> for the specified alias
|
|
*
|
|
* @param alias get the <code>KeyStore.Entry</code> for this alias
|
|
* @param protParam this must be <code>null</code>
|
|
*
|
|
* @return the <code>KeyStore.Entry</code> for the specified alias,
|
|
* or <code>null</code> 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
|
|
* <code>protParam</code> 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 <code>KeyStore.Entry</code> under the specified alias.
|
|
*
|
|
* <p> If an entry already exists for the specified alias,
|
|
* it is overridden.
|
|
*
|
|
* <p> 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.
|
|
*
|
|
* <p> Entries are immediately stored on the token.
|
|
*
|
|
* @param alias save the <code>KeyStore.Entry</code> under this alias
|
|
* @param entry the <code>Entry</code> to save
|
|
* @param protParam this must be <code>null</code>
|
|
*
|
|
* @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"));
|
|
}
|
|
Secmod.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<String> 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 <code>Entry</code> for the specified
|
|
* <code>alias</code> is an instance or subclass of the specified
|
|
* <code>entryClass</code>.
|
|
*
|
|
* @param alias the alias name
|
|
* @param entryClass the entry class
|
|
*
|
|
* @return true if the keystore <code>Entry</code> for the specified
|
|
* <code>alias</code> is an instance or subclass of the
|
|
* specified <code>entryClass</code>, false otherwise
|
|
*/
|
|
public synchronized boolean engineEntryInstanceOf
|
|
(String alias, Class<? extends KeyStore.Entry> 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<X509Certificate> lChain = null;
|
|
|
|
if (endCert.getSubjectX500Principal().equals
|
|
(endCert.getIssuerX500Principal())) {
|
|
// self signed
|
|
return new X509Certificate[] { endCert };
|
|
} else {
|
|
lChain = new ArrayList<X509Certificate>();
|
|
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);
|
|
}
|
|
}
|
|
|
|
// retrieves the native key handle and either update it directly or make a copy
|
|
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;
|
|
long keyID = key.getKeyID();
|
|
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(), 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);
|
|
}
|
|
// creates a new token key with the desired CKA_ID
|
|
token.p11.C_CopyObject(session.id(), keyID, attrs);
|
|
if (debug != null) {
|
|
debug.println("updateP11Pkey copied private session key " +
|
|
"for [" +
|
|
alias +
|
|
"] to token entry");
|
|
}
|
|
}
|
|
} finally {
|
|
token.releaseSession(session);
|
|
key.releaseKeyID();
|
|
}
|
|
}
|
|
|
|
private void storeCert(String alias, X509Certificate cert)
|
|
throws PKCS11Exception, CertificateException {
|
|
|
|
ArrayList<CK_ATTRIBUTE> attrList = new ArrayList<CK_ATTRIBUTE>();
|
|
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<X509Certificate> cacerts = new HashSet<X509Certificate>();
|
|
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 (alg.equals("RSA") && (publicKey instanceof RSAPublicKey)) {
|
|
if (id) {
|
|
BigInteger n = ((RSAPublicKey)publicKey).getModulus();
|
|
attrs[0] = new CK_ATTRIBUTE(CKA_ID, sha1(getMagnitude(n)));
|
|
}
|
|
// CKA_NETSCAPE_DB not needed for RSA public keys
|
|
} 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<THandle> list = new ArrayList<THandle>(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<byte[]> pkeyIDs = new ArrayList<byte[]>();
|
|
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<String, HashSet<AliasInfo>> certMap =
|
|
new HashMap<String, HashSet<AliasInfo>>();
|
|
|
|
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<AliasInfo> infoSet = certMap.get(cka_label);
|
|
if (infoSet == null) {
|
|
infoSet = new HashSet<AliasInfo>(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<String, AliasInfo> sKeyMap =
|
|
new HashMap<String, AliasInfo>();
|
|
|
|
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<AliasInfo> 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<AliasInfo> mapPrivateKeys(ArrayList<byte[]> pkeyIDs,
|
|
HashMap<String, HashSet<AliasInfo>> certMap)
|
|
throws PKCS11Exception, CertificateException {
|
|
|
|
// reset global alias map
|
|
aliasMap = new HashMap<String, AliasInfo>();
|
|
|
|
// list of matched certs that we will return
|
|
ArrayList<AliasInfo> matchedCerts = new ArrayList<AliasInfo>();
|
|
|
|
for (byte[] pkeyID : pkeyIDs) {
|
|
|
|
// try to find a matching CKA_ID in a certificate
|
|
|
|
boolean foundMatch = false;
|
|
Set<String> certLabels = certMap.keySet();
|
|
for (String certLabel : certLabels) {
|
|
|
|
// get cert CKA_IDs (if present) for each cert
|
|
|
|
HashSet<AliasInfo> 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<AliasInfo> matchedCerts,
|
|
HashMap<String, HashSet<AliasInfo>> 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<String> certLabels = certMap.keySet();
|
|
for (String certLabel : certLabels) {
|
|
HashSet<AliasInfo> 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<AliasInfo> 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<String, AliasInfo> 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<String> 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;
|
|
}
|
|
|
|
}
|