/***********************************************************
* $Id: PKCS11KeyStoreSpi.java 44 2007-01-28 20:29:17Z wolfgang.glas $
*
* PKCS11 provider of the OpenSC project http://www.opensc-project.org
*
* Copyright (C) 2006 ev-i Informationstechnologie GmbH
*
* Created: Jul 16, 2006
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
***********************************************************/
package org.opensc.pkcs11.spi;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.ProviderException;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.KeyStore.Entry;
import java.security.KeyStore.LoadStoreParameter;
import java.security.KeyStore.PasswordProtection;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.security.auth.x500.X500Principal;
import org.opensc.pkcs11.PKCS11LoadStoreParameter;
import org.opensc.pkcs11.PKCS11Provider;
import org.opensc.pkcs11.PKCS11SessionStore;
import org.opensc.pkcs11.wrap.PKCS11Certificate;
import org.opensc.pkcs11.wrap.PKCS11Exception;
import org.opensc.pkcs11.wrap.PKCS11PrivateKey;
import org.opensc.util.PKCS11Id;
import de.kp.logging.Log;
import de.kp.logging.LogFactory;
/**
* This is a JAVA KeyStore, which accesses a slot on a PKCS#11 cryptographic token.
*
* @author wglas
* @author Stefan Krusche (krusche@dr-kruscheundpartner.de)
*
*/
public class PKCS11KeyStoreSpi extends KeyStoreSpi
{
static private final Log log = LogFactory.getLog(PKCS11KeyStoreSpi.class);
static private final int MAX_SIMILAR_CERTIFICATES = 32;
private class PKCS11KSEntry implements Entry
{
public Date creationDate;
public PKCS11Certificate certificate;
private Certificate decodedCertificate;
public PKCS11PrivateKey privateKey;
PKCS11KSEntry(PKCS11PrivateKey privateKey)
{
this.creationDate = new Date();
this.privateKey = privateKey;
}
PKCS11KSEntry(PKCS11Certificate certificate)
{
this.creationDate = new Date();
this.certificate = certificate;
}
public Certificate getDecodedCertificate() throws PKCS11Exception, CertificateException
{
if (this.decodedCertificate == null && this.certificate != null)
this.decodedCertificate = this.certificate.getCertificate();
return this.decodedCertificate;
}
}
private final PKCS11Provider provider;
private PKCS11SessionStore sessionStore;
private boolean needToCloseSessionStore;
private Map<String,PKCS11KSEntry> entries;
/**
* Contruct a PKCS11 KeyStore.
*/
public PKCS11KeyStoreSpi(PKCS11Provider provider, String algorithm)
{
super();
this.provider = provider;
this.sessionStore = null;
this.entries = null;
this.needToCloseSessionStore = false;
if (algorithm != "PKCS11")
throw new ProviderException("Algorithm for PKCS11 KeyStore can only be \"PKCS11\".");
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineGetKey(java.lang.String, char[])
*/
@Override
public Key engineGetKey(String name, char[] pin)
throws NoSuchAlgorithmException, UnrecoverableKeyException
{
PKCS11KSEntry entry = this.entries.get(name);
if (entry == null) return null;
return entry.privateKey;
}
/**
* Returns all certificates for the given X500Principal.
*
* @param subject The subject to search for.
* @return All certificates, which match this subject.
*/
private Map<String,PKCS11KSEntry> getAllCertificatesForSubject(X500Principal subject)
{
Map<String,PKCS11KSEntry> ret = new HashMap<String,PKCS11KSEntry>();
String subj = subject.toString();
PKCS11KSEntry entry = this.entries.get(subj);
if (entry != null)
{
ret.put(subj,entry);
int i = 1;
do
{
++i;
String name = String.format("%s_%02X",subj,i);
entry = this.entries.get(name);
if (entry != null) ret.put(name,entry);
}
while (entry != null && i < MAX_SIMILAR_CERTIFICATES);
}
return ret;
}
private static boolean isRootCA(X509Certificate cert) throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException
{
if (!cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()))
return false;
cert.verify(cert.getPublicKey());
return true;
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineGetCertificateChain(java.lang.String)
*/
@Override
public Certificate[] engineGetCertificateChain(String name)
{
Certificate endEntity = engineGetCertificate(name);
if (endEntity == null) return null;
if (!(endEntity instanceof X509Certificate))
{
log.error("engineGetCertificateChain: Only X.509 certificates are supported.");
return null;
}
List<Certificate> ret = new ArrayList<Certificate>();
ret.add(endEntity);
X509Certificate x509Certificate = (X509Certificate)endEntity;
try
{
// OK ,this is acrude form of certificate chain evaluation.
// Assuming, that the upper layer does a more detailed anlysis of the
// validity period and key extensions, we only search the chain by
// finding the issuing certificate on the token using the issuer DN
// and trying to check the Signature on the certificate using the
// public key on the next certificate.
while (!isRootCA(x509Certificate))
{
Map<String,PKCS11KSEntry> centries =
getAllCertificatesForSubject(x509Certificate.getIssuerX500Principal());
X509Certificate x509NextCert = null;
for (PKCS11KSEntry entry : centries.values())
{
Certificate next = entry.getDecodedCertificate();
X509Certificate x509Next = (X509Certificate)next;
if (!x509Next.getSubjectX500Principal().equals(x509Certificate.getIssuerX500Principal()))
continue;
try {
x509Certificate.verify(x509Next.getPublicKey());
x509NextCert = x509Next;
break;
}
catch (Exception e) {
log.warn("Exception during evaluation of certificate chain:",e);
}
}
if (x509NextCert == null)
{
throw new CertificateException("Cannot find the issuing CA for certificate ["+x509Certificate+"].");
}
x509Certificate = x509NextCert;
ret.add(x509Certificate);
}
return ret.toArray(new Certificate[0]);
} catch (Exception e)
{
log.error("Exception caught during analysis of the certificate chain:",e);
}
return null;
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineGetCertificate(java.lang.String)
*/
@Override
public Certificate engineGetCertificate(String name)
{
PKCS11KSEntry entry = this.entries.get(name);
if (entry == null) return null;
try
{
return entry.getDecodedCertificate();
} catch (PKCS11Exception e)
{
log.error("PKCS11 Error decoding Certificate for entry "+name+":",e);
} catch (CertificateException e)
{
log.error("Certificate Error decoding Certificate for entry "+name+":",e);
}
return null;
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineGetCreationDate(java.lang.String)
*/
@Override
public Date engineGetCreationDate(String name)
{
PKCS11KSEntry entry = this.entries.get(name);
if (entry == null) return null;
return entry.creationDate;
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineSetKeyEntry(java.lang.String, java.security.Key, char[], java.security.cert.Certificate[])
*/
@Override
public void engineSetKeyEntry(String name, Key key, char[] pin,
Certificate[] certificateChain) throws KeyStoreException
{
throw new KeyStoreException("setKeyEntry is unimplmented.");
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineSetKeyEntry(java.lang.String, byte[], java.security.cert.Certificate[])
*/
@Override
public void engineSetKeyEntry(String name, byte[] pin, Certificate[] certificateChain)
throws KeyStoreException
{
throw new KeyStoreException("setKeyEntry is unimplmented.");
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineSetCertificateEntry(java.lang.String, java.security.cert.Certificate)
*/
@Override
public void engineSetCertificateEntry(String name, Certificate certificate)
throws KeyStoreException
{
try
{
PKCS11Certificate cert =
PKCS11Certificate.storeCertificate(this.sessionStore.getSession(),
certificate, name, true);
PKCS11KSEntry entry = new PKCS11KSEntry(cert);
String keyName = "ID_" + cert.getId();
PKCS11KSEntry pk_entry = this.entries.get(keyName);
if (pk_entry != null)
{
entry.privateKey = pk_entry.privateKey;
this.entries.remove(keyName);
}
if (name == null)
this.entries.put(cert.getSubject().toString(),entry);
else
this.entries.put(name,entry);
} catch (CertificateEncodingException e)
{
throw new KeyStoreException("Error encoding certificate",e);
} catch (PKCS11Exception e)
{
throw new KeyStoreException("Error storing certificate on the token",e);
}
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineDeleteEntry(java.lang.String)
*/
@Override
public void engineDeleteEntry(String name) throws KeyStoreException
{
throw new KeyStoreException("deleteEntry is unimplemented.");
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineAliases()
*/
@Override
public Enumeration<String> engineAliases()
{
// Enumeration is efinitely a misconception, as you can see
// by the code below...
Set<String> keys = this.entries.keySet();
Vector<String> sv = new Vector<String>(keys.size());
sv.addAll(keys);
return sv.elements();
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineContainsAlias(java.lang.String)
*/
@Override
public boolean engineContainsAlias(String name)
{
return this.entries.containsKey(name);
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineSize()
*/
@Override
public int engineSize()
{
return this.entries.size();
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineIsKeyEntry(java.lang.String)
*/
@Override
public boolean engineIsKeyEntry(String name)
{
PKCS11KSEntry entry = this.entries.get(name);
if (entry == null) return false;
return entry.privateKey != null;
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineIsCertificateEntry(java.lang.String)
*/
@Override
public boolean engineIsCertificateEntry(String name)
{
PKCS11KSEntry entry = this.entries.get(name);
if (entry == null) return false;
return entry.certificate != null;
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineGetCertificateAlias(java.security.cert.Certificate)
*/
@Override
public String engineGetCertificateAlias(Certificate certificate)
{
if (! (certificate instanceof X509Certificate))
{
log.error("engineGetCertificateAlias: Only X.509 certificates are supported.");
}
X509Certificate x509Certificate = (X509Certificate)certificate;
X500Principal subject = x509Certificate.getSubjectX500Principal();
Map<String,PKCS11KSEntry> centries = getAllCertificatesForSubject(subject);
for (String name : centries.keySet())
{
try
{
PKCS11KSEntry entry = centries.get(name);
if (entry.certificate != null &&
entry.getDecodedCertificate().equals(certificate))
return name;
} catch (PKCS11Exception e)
{
log.error("PKCS11 Error decoding Certificate for entry "+name+":",e);
} catch (CertificateException e)
{
log.error("Certificate Error decoding Certificate for entry "+name+":",e);
}
}
return null;
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineStore(java.io.OutputStream, char[])
*/
@Override
public void engineStore(OutputStream arg0, char[] arg1) throws IOException,
NoSuchAlgorithmException, CertificateException
{
throw new NoSuchAlgorithmException("PKCS11 key store does not support a store operation.");
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineLoad(java.io.InputStream, char[])
*/
@Override
public void engineLoad(InputStream file, char[] pin) throws IOException,
NoSuchAlgorithmException, CertificateException
{
if (file != null)
throw new IOException ("PKCS11 Key Store requires a null InputStream a the first argument.");
PKCS11LoadStoreParameter param = new PKCS11LoadStoreParameter();
param.setProtectionParameter(new PasswordProtection(pin));
engineLoad(param);
}
/* (non-Javadoc)
* @see java.security.KeyStoreSpi#engineLoad(java.security.KeyStore.LoadStoreParameter)
*/
@Override
public void engineLoad(LoadStoreParameter param) throws IOException,
NoSuchAlgorithmException, CertificateException
{
if (this.sessionStore != null)
{
if (this.needToCloseSessionStore)
this.sessionStore.close();
}
if (param instanceof PKCS11SessionStore)
{
this.sessionStore = (PKCS11SessionStore)param;
this.needToCloseSessionStore = false;
}
else
{
this.sessionStore = new PKCS11SessionStore();
this.needToCloseSessionStore = true;
this.sessionStore.open(this.provider, param);
}
// OK, the session is up and running, now get the certificates
// and keys.
this.entries = new HashMap<String,PKCS11KSEntry>();
List<PKCS11PrivateKey> privKeys =
PKCS11PrivateKey.getPrivateKeys(this.sessionStore.getSession());
Map<PKCS11Id,PKCS11KSEntry> privKeysById =
new HashMap<PKCS11Id,PKCS11KSEntry>();
for (PKCS11PrivateKey privKey : privKeys)
{
privKeysById.put(privKey.getId(),new PKCS11KSEntry(privKey));
}
List<PKCS11Certificate> certificates =
PKCS11Certificate.getCertificates(this.sessionStore.getSession());
for (PKCS11Certificate certificate : certificates)
{
// contruct a unique name for certificate entries.
String subj = certificate.getSubject().toString();
String name = subj;
name = subj;
int i = 1;
while (this.entries.containsKey(name) && i < MAX_SIMILAR_CERTIFICATES)
{
++i;
name = String.format("%s_%02X",subj,i);
}
if (i >= MAX_SIMILAR_CERTIFICATES) {
throw new CertificateException("More than "+MAX_SIMILAR_CERTIFICATES+
" instances of the same certificate subject ["+subj+
"]found on the token.");
}
PKCS11KSEntry entry = new PKCS11KSEntry(certificate);
PKCS11KSEntry pk_entry = privKeysById.get(certificate.getId());
if (pk_entry != null)
{
entry.privateKey = pk_entry.privateKey;
pk_entry.certificate = certificate;
}
this.entries.put(name,entry);
}
for (PKCS11Id id : privKeysById.keySet())
{
PKCS11KSEntry entry = privKeysById.get(id);
if (entry.certificate != null) continue;
String name = "ID_"+id;
this.entries.put(name,entry);
}
}
}