/*************************************************************************
* *
* EJBCA: The OpenSource Certificate Authority *
* *
* This software 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 any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
package org.ejbca.core.model.ca.catoken;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.ejbca.core.model.InternalResources;
import org.ejbca.core.model.SecConst;
import org.ejbca.core.model.util.AlgorithmTools;
import org.ejbca.cvc.CardVerifiableCertificate;
import org.ejbca.util.Base64;
import org.ejbca.util.CertTools;
import org.ejbca.util.StringTools;
import org.ejbca.util.keystore.KeyStoreContainer;
import org.ejbca.util.keystore.KeyStoreContainerFactory;
import org.ejbca.util.keystore.KeyTools;
/**
* CATokenContainerImpl is a class managing the persistent storage of a CA token.
*
*
* @version $Id: CATokenContainerImpl.java 10397 2010-11-08 14:18:57Z anatom $
*/
public class CATokenContainerImpl extends CATokenContainer {
private static final long serialVersionUID = 3363098236866891317L;
/** Log4j instance */
private static final Logger log = Logger.getLogger(CATokenContainerImpl.class);
/** Internal localization of logs and errors */
private static final InternalResources intres = InternalResources.getInstance();
private ICAToken catoken = null;
final private int caid;
public static final float LATEST_VERSION = 7;
// Default Values
protected static final String CLASSPATH = "classpath";
protected static final String PROPERTYDATA = "propertydata";
/** Class for printing properties (for debug purposes) without revealing any pin properties in the log file
*/
private class PropertiesWithHiddenPIN extends Properties {
/**
*
*/
private static final long serialVersionUID = -2240419700704551683L;
/**
*
*/
public PropertiesWithHiddenPIN() {
}
/**
* @param defaults
*/
public PropertiesWithHiddenPIN(Properties defaults) {
super(defaults);
}
public synchronized String toString() {
int max = size() - 1;
if (max == -1) {
return "{}";
}
final StringBuilder sb = new StringBuilder();
final Iterator it = entrySet().iterator();
sb.append('{');
for (int i = 0; ; i++) {
final Map.Entry e = (Map.Entry)it.next();
final String key = (String)e.getKey();
final String readValue = (String)e.getValue();
final String value = readValue!=null && readValue.length()>0 && key.trim().equalsIgnoreCase(ICAToken.AUTOACTIVATE_PIN_PROPERTY) ? "xxxx" : readValue;
sb.append(key);
sb.append('=');
sb.append(value);
if (i == max) {
return sb.append('}').toString();
}
sb.append(", ");
}
}
}
/**
*
* @param tokentype CATokenInfo.CATOKENTYPE_HSM or similar
*/
/**
* @param catokeninfo info about the token to be created.
* @param _caid unique ID of the user of the token. For EJBCA this is the caid. For the OCSP responder this is fixed since then there is only one user.
*/
public CATokenContainerImpl(CATokenInfo catokeninfo, int _caid){
super();
this.caid = _caid;
updateCATokenInfo(catokeninfo);
}
/**
* @param data that defines the token.
* @param _caid unique ID of the user of the token. For EJBCA this is the caid. For the OCSP responder this is fixed since then there is only one user.
*/
public CATokenContainerImpl(HashMap data, int _caid) {
this.caid = _caid;
loadData(data);
}
// Public Methods
/**
* Returns the current hardcatoken configuration.
*/
public CATokenInfo getCATokenInfo() {
// First make a call to get the CAToken, so we initialize it
getCAToken();
CATokenInfo info = null;
if (catoken instanceof NullCAToken) {
info = new NullCATokenInfo();
}
String classpath = getClassPath();
if (catoken instanceof SoftCAToken) {
SoftCATokenInfo sinfo = new SoftCATokenInfo();
sinfo.setSignKeySpec((String) data.get(SIGNKEYSPEC));
sinfo.setSignKeyAlgorithm((String) data.get(SIGNKEYALGORITHM));
sinfo.setEncKeySpec((String) data.get(ENCKEYSPEC));
sinfo.setEncKeyAlgorithm((String) data.get(ENCKEYALGORITHM));
sinfo.setEncryptionAlgorithm((String) data.get(ENCRYPTIONALGORITHM));
if (StringUtils.isEmpty(classpath)) {
classpath = SoftCAToken.class.getName();
}
sinfo.setClassPath(classpath);
info = sinfo;
} else if (catoken instanceof NullCAToken) {
NullCATokenInfo ninfo = new NullCATokenInfo();
info = ninfo;
} else {
HardCATokenInfo hinfo = new HardCATokenInfo();
info = hinfo;
}
info.setClassPath(getClassPath());
info.setProperties(getPropertyData());
info.setSignatureAlgorithm(getSignatureAlgorithm());
info.setKeySequence(getKeySequence());
info.setKeySequenceFormat(getKeySequenceFormat());
// Set status of the CA token
int status = ICAToken.STATUS_OFFLINE;
if ( catoken != null ){
status = catoken.getCATokenStatus();
}
log.debug("Setting CATokenInfo.status to: "+status);
info.setCATokenStatus(status);
return info;
}
/**
* Returns the type of CA token, from CATokenConstants.
* @return integer one of CATokenConstants.CATOKENTYPE_XXX, or 0 if we don't know the type
* @see CATokenConstants.CATOKENTYPE_XXX
*/
public int getCATokenType() {
int ret = 0;
if (data.get(CATOKENTYPE) != null) {
ret = (Integer)(data.get(CATOKENTYPE));
}
return ret;
}
/**
* Updates the hardcatoken configuration
*/
public void updateCATokenInfo(CATokenInfo catokeninfo) {
boolean changed = false;
// We must be able to upgrade class path
if (catokeninfo.getClassPath() != null) {
this.setClassPath(catokeninfo.getClassPath());
this.catoken = null;
}
// Possible to change signature algorithm as well
String str = catokeninfo.getSignatureAlgorithm();
if ( (str != null) && !StringUtils.equals(getSignatureAlgorithm(), str)) {
this.setSignatureAlgorithm(str);
changed = true;
}
String props = this.getPropertyData();
String newprops = catokeninfo.getProperties();
if ( (newprops != null) && !StringUtils.equals(props, newprops)) {
this.setPropertyData(newprops);
changed = true;
}
if (catokeninfo.getKeySequence() != null) {
this.setKeySequence(catokeninfo.getKeySequence());
}
this.setKeySequenceFormat(catokeninfo.getKeySequenceFormat());
if (catokeninfo instanceof NullCATokenInfo) {
log.debug("CA Token is CATOKENTYPE_NULL");
if (data.get(CATOKENTYPE) == null) {
data.put(CATOKENTYPE, Integer.valueOf(CATokenConstants.CATOKENTYPE_NULL));
changed = true;
}
}
if (catokeninfo instanceof HardCATokenInfo) {
log.debug("CA Token is CATOKENTYPE_HSM");
if (data.get(CATOKENTYPE) == null) {
data.put(CATOKENTYPE, Integer.valueOf(CATokenConstants.CATOKENTYPE_HSM));
changed = true;
}
}
if (catokeninfo instanceof SoftCATokenInfo) {
log.debug("CA Token is CATOKENTYPE_P12");
if (data.get(CATOKENTYPE) == null) {
data.put(CATOKENTYPE, Integer.valueOf(CATokenConstants.CATOKENTYPE_P12));
changed = true;
}
SoftCATokenInfo sinfo = (SoftCATokenInfo) catokeninfo;
// Below for soft CA tokens
str = sinfo.getSignKeySpec();
if ( (str != null) && !StringUtils.equals((String)data.get(SIGNKEYSPEC), str)) {
data.put(SIGNKEYSPEC, str);
changed = true;
}
str = sinfo.getSignKeyAlgorithm();
if ( (str != null) && !StringUtils.equals((String)data.get(SIGNKEYALGORITHM), str)) {
data.put(SIGNKEYALGORITHM, str);
changed = true;
}
str = sinfo.getEncKeySpec();
if ( (str != null) && !StringUtils.equals((String)data.get(ENCKEYSPEC), str)) {
data.put(ENCKEYSPEC, str);
changed = true;
}
str = sinfo.getEncKeyAlgorithm();
if ( (str != null) && !StringUtils.equals((String)data.get(ENCKEYALGORITHM), str)) {
data.put(ENCKEYALGORITHM, str);
changed = true;
}
str = sinfo.getEncryptionAlgorithm();
if ( (str != null) && !StringUtils.equals((String)data.get(ENCRYPTIONALGORITHM), str)) {
data.put(ENCRYPTIONALGORITHM, str);
changed = true;
}
}
if (changed) {
this.catoken = null;
}
}
/**
* @see org.ejbca.core.model.ca.catoken.CATokenContainer#activate(java.lang.String)
*/
public void activate(String authorizationcode) throws CATokenAuthenticationFailedException, CATokenOfflineException {
ICAToken token = getCAToken();
if (token != null) {
token.activate(authorizationcode);
} else {
log.debug("CA token is null and can not be activated.");
}
}
/* (non-Javadoc)
* @see org.ejbca.core.model.ca.catoken.CATokenContainer#deactivate()
*/
public boolean deactivate() throws Exception {
boolean ret = false;
ICAToken token = getCAToken();
if (token != null) {
ret = token.deactivate();
} else {
log.debug("CA token is null and does not need deactivation.");
}
return ret;
}
/**
* @see org.ejbca.core.model.ca.catoken.CATokenContainer#getPrivateKey()
*/
public PrivateKey getPrivateKey(int purpose) throws CATokenOfflineException{
ICAToken token = getCAToken();
if (token == null) {
String msg = intres.getLocalizedMessage("caadmin.errorcreatetoken");
log.warn(msg+" CA token is null.");
throw new CATokenOfflineException(msg);
}
return token.getPrivateKey(purpose);
}
/**
* @see org.ejbca.core.model.ca.catoken.CATokenContainer#getPublicKey()
*/
public PublicKey getPublicKey(int purpose) throws CATokenOfflineException{
ICAToken token = getCAToken();
if (token == null) {
String msg = intres.getLocalizedMessage("caadmin.errorcreatetoken");
log.warn(msg+" CA token is null.");
throw new CATokenOfflineException(msg);
}
return token.getPublicKey(purpose);
}
/**
* @see org.ejbca.core.model.ca.catoken.CATokenContainer#getProvider()
*/
public String getProvider() {
return getCAToken().getProvider();
}
public String getJCEProvider() {
return getCAToken().getJCEProvider();
}
/**
* Method that generates the keys that will be used by the CAToken.
* The method can be used to generate keys for an initial CA token or to renew Certificate signing keys.
* If setstatustowaiting is true and you generate new keys, the new keys will be available as SecConst.CAKEYPURPOSE_CERTSIGN.
* If setstatustowaiting is false and you generate new keys, the new keys will be available as SecConst.CAKEYPURPOSE_CERTSIGN_NEXT.
*
* @param authenticationCode the password used to encrypt the keystore, later needed to activate CA Token
* @param renew flag indicating if the keys are renewed instead of created fresh. Renewing keys does not
* create new encryption keys, since this would make it impossible to decrypt old stuff.
* @param activate flag indicating if the new keys should be activated immediately or or they should be added as "next" signing key.
* Using true here makes it possible to generate certificate renewal requests for external CAs still using the old keys until the response is received.
*/
public void generateKeys(final String authenticationCode, final boolean renew, final boolean activate) throws Exception {
if (log.isTraceEnabled()) {
log.trace(">generateKeys: "+(authenticationCode == null ? "null":"hidden")+", renew="+renew+", activate="+activate);
}
CATokenInfo catokeninfo = getCATokenInfo();
// First we start by setting a new sequence for our new keys
String oldSequence = getKeySequence();
log.debug("Current sequence: "+oldSequence);
String newSequence = StringTools.incrementKeySequence(getCATokenInfo().getKeySequenceFormat(), oldSequence);
// We store the sequence permanently in the object last, when we know everything went well
// If we don't give an authentication code, perhaps we have autoactivation enabled
char[] authCode = getAuthCodeOrAutoactivationPin(authenticationCode);
String tokentype = null;
// Then we can move on to actually generating the keys
if (catokeninfo instanceof SoftCATokenInfo) {
// If we have an existing soft keystore verify that the password is correct
checkSoftKeystorePassword(authenticationCode);
SoftCATokenInfo info = (SoftCATokenInfo) catokeninfo;
Properties properties = getProperties();
PublicKey pubEnc = null;
PrivateKey privEnc = null;
PublicKey previousPubSign = null;
PrivateKey previousPrivSign = null;
PublicKey oldPreviousPubSign = null;
PrivateKey oldPreviousPrivSign = null;
if (!renew) {
log.debug("We are generating initial keys.");
// Generate encryption keys.
// Encryption keys must be RSA still
KeyPair enckeys = KeyTools.genKeys(info.getEncKeySpec(), info.getEncKeyAlgorithm());
pubEnc = enckeys.getPublic();
privEnc = enckeys.getPrivate();
} else {
log.debug("We are renewing keys.");
// Get the already existing encryption and signature keys
ICAToken token = getCAToken();
pubEnc = token.getPublicKey(SecConst.CAKEYPURPOSE_KEYENCRYPT);
privEnc = token.getPrivateKey(SecConst.CAKEYPURPOSE_KEYENCRYPT);
previousPubSign = token.getPublicKey(SecConst.CAKEYPURPOSE_CERTSIGN);
previousPrivSign = token.getPrivateKey(SecConst.CAKEYPURPOSE_CERTSIGN);
oldPreviousPubSign = token.getPublicKey(SecConst.CAKEYPURPOSE_CERTSIGN_PREVIOUS);
oldPreviousPrivSign = token.getPrivateKey(SecConst.CAKEYPURPOSE_CERTSIGN_PREVIOUS);
}
// As first choice we check if the used have specified which type of key should be generated, this can be different from the currently used key
// If the user did not specify this, we try to generate a key with the same specification as the currently used key.
String keyspec = info.getSignKeySpec(); // can be "unknown"
if (StringUtils.equals(keyspec, AlgorithmTools.KEYSPEC_UNKNOWN)) {
keyspec = null;
}
AlgorithmParameterSpec paramspec = KeyTools.getKeyGenSpec(previousPubSign);
if (log.isDebugEnabled()) {
if (keyspec != null) {
log.debug("Generating new Soft key with specified spec "+keyspec+" with label "+SoftCAToken.PRIVATESIGNKEYALIAS);
} else {
int keySize = KeyTools.getKeyLength(previousPubSign);
String alg = previousPubSign.getAlgorithm();
log.debug("Generating new Soft "+alg+" key with spec "+paramspec+" (size="+keySize+") with label "+SoftCAToken.PRIVATESIGNKEYALIAS);
}
}
// Generate signature keys.
KeyPair newsignkeys = KeyTools.genKeys(keyspec, paramspec, info.getSignKeyAlgorithm());
// Create the new keystore and add the new signature keys
KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
keystore.load(null, null);
// PLay with aliases depending on if we activate the new key or not
String newSignKeyAlias = SoftCAToken.PRIVATESIGNKEYALIAS;
String previousSignKeyAlias = SoftCAToken.PREVIOUSPRIVATESIGNKEYALIAS;
if (!activate) {
// If the new keys are not activated we must still use the old key as active signing key (PRIVATESIGNKEYALIAS)
newSignKeyAlias = SoftCAToken.NEXTPRIVATESIGNKEYALIAS;
previousSignKeyAlias = SoftCAToken.PRIVATESIGNKEYALIAS;
}
log.debug("Setting newsignkeys as "+newSignKeyAlias+" in soft CA token.");
Certificate[] certchain = new Certificate[1]; // generate dummy certificate
String sigAlg = (String)AlgorithmTools.getSignatureAlgorithms(newsignkeys.getPublic()).iterator().next();
certchain[0] = CertTools.genSelfCert("CN=dummy", 36500, null, newsignkeys.getPrivate(), newsignkeys.getPublic(), sigAlg, true);
keystore.setKeyEntry(newSignKeyAlias, newsignkeys.getPrivate(), null, certchain);
if (!activate) {
log.debug("Set next sequence: "+newSequence);
properties.setProperty(ICAToken.NEXT_SEQUENCE_PROPERTY, newSequence);
log.debug("Set nextCertSignKey: "+newSignKeyAlias);
properties.setProperty(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_NEXT, newSignKeyAlias);
}
// If we have an old key (i.e. generating new keys), we will store the old one as "previous"
if (previousPrivSign != null) {
log.debug("Setting previousPrivSign as "+previousSignKeyAlias+" in soft CA token.");
sigAlg = (String)AlgorithmTools.getSignatureAlgorithms(previousPubSign).iterator().next();
certchain[0] = CertTools.genSelfCert("CN=dummy2", 36500, null, previousPrivSign, previousPubSign, sigAlg, true);
keystore.setKeyEntry(previousSignKeyAlias, previousPrivSign, null, certchain);
// Now this keystore should have this previous key
if (activate) {
// This key pair is now moved down to previous sign key
properties.setProperty(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_PREVIOUS, previousSignKeyAlias);
// Set previous sequence so we can create link certificates
log.debug("Set previous sequence : "+oldSequence);
properties.setProperty(ICAToken.PREVIOUS_SEQUENCE_PROPERTY, oldSequence);
} else {
// If we have an old previous key and we are not activating the new key, we will keep this one as "previous"
// If the new keys are activate the old previous keys are trashed and replaced by the old active signature key
String prevProp = properties.getProperty(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_PREVIOUS); // If we don't have a previous key don't try to add it
if ( (oldPreviousPrivSign != null) && (prevProp != null) ) {
log.debug("Setting old previousprivatesignkeyalias as "+SoftCAToken.PREVIOUSPRIVATESIGNKEYALIAS+" in soft CA token.");
sigAlg = (String)AlgorithmTools.getSignatureAlgorithms(oldPreviousPubSign).iterator().next();
certchain[0] = CertTools.genSelfCert("CN=dummy3", 36500, null, oldPreviousPrivSign, oldPreviousPubSign, sigAlg, true);
keystore.setKeyEntry(SoftCAToken.PREVIOUSPRIVATESIGNKEYALIAS, oldPreviousPrivSign, null, certchain);
properties.setProperty(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_PREVIOUS, SoftCAToken.PREVIOUSPRIVATESIGNKEYALIAS);
} else {
log.debug("No previousprivatesignkeyalias exists, not setting any previous key.");
}
}
}
// Finally install the old encryption/decryption keys as well
sigAlg = (String)AlgorithmTools.getSignatureAlgorithms(pubEnc).iterator().next();
certchain[0] = CertTools.genSelfCert("CN=dummy2", 36500, null, privEnc, pubEnc, sigAlg, true);
keystore.setKeyEntry(SoftCAToken.PRIVATEDECKEYALIAS, privEnc, null, certchain);
storeSoftKeyStore(authCode, info, properties, keystore);
tokentype = "Soft"; // for logging
} else if (catokeninfo instanceof HardCATokenInfo) {
ICAToken token = getCAToken();
if (token instanceof PKCS11CAToken) {
Properties properties = getProperties();
PublicKey pubK = token.getPublicKey(SecConst.CAKEYPURPOSE_CERTSIGN);
String keyLabel = token.getKeyLabel(SecConst.CAKEYPURPOSE_CERTSIGN);
log.debug("Old key label is: "+keyLabel);
String crlKeyLabel = token.getKeyLabel(SecConst.CAKEYPURPOSE_CRLSIGN);
// The key label to use for the new key
// Remove the old sequence from the end of the key label and replace it with the
// new label. If no label was present just concatenate the new label
String newKeyLabel = StringUtils.removeEnd(keyLabel, oldSequence)+newSequence;
log.debug("New key label is: "+newKeyLabel);
final KeyStore.PasswordProtection pwp = new KeyStore.PasswordProtection(authCode);
// As first choice we check if the used have specified which type of key should be generated, this can be different from the currently used key
// If the user did not specify this, we try to generate a key with the same specification as the currently used key.
String keyspec = properties.getProperty(ICAToken.KEYSPEC_PROPERTY); // can be null, and that is ok
AlgorithmParameterSpec paramspec = KeyTools.getKeyGenSpec(pubK);
if (log.isDebugEnabled()) {
String sharedLibrary = properties.getProperty(PKCS11CAToken.SHLIB_LABEL_KEY);
String slot = properties.getProperty(PKCS11CAToken.SLOT_LABEL_KEY);
String attributesFile = properties.getProperty(PKCS11CAToken.ATTRIB_LABEL_KEY);
if (keyspec != null) {
log.debug("Generating new PKCS#11 key with specified spec "+keyspec+" with label "+newKeyLabel+", on slot "+slot+", using sharedLibrary "+sharedLibrary+", and attributesFile "+attributesFile);
} else {
int keySize = KeyTools.getKeyLength(pubK);
String alg = pubK.getAlgorithm();
log.debug("Generating new PKCS#11 "+alg+" key with spec "+paramspec+" (size="+keySize+") with label "+newKeyLabel+", on slot "+slot+", using sharedLibrary "+sharedLibrary+", and attributesFile "+attributesFile);
}
}
KeyStoreContainer cont = KeyStoreContainerFactory.getInstance(KeyStoreContainer.KEYSTORE_TYPE_PKCS11, token.getProvider(), pwp);
cont.setPassPhraseLoadSave(authCode);
if (keyspec != null) {
log.debug("Generating from string keyspec: "+keyspec);
cont.generate(keyspec, newKeyLabel);
} else {
log.debug("Generating from AlgorithmParameterSpec: "+paramspec);
cont.generate(paramspec, newKeyLabel);
}
// Set properties so that we will start using the new key, or not, depending on the activate argument
KeyStrings kstr = new KeyStrings(properties);
String certsignkeystr = kstr.getKey(SecConst.CAKEYPURPOSE_CERTSIGN);
log.debug("CAKEYPURPOSE_CERTSIGN keystring is: "+certsignkeystr);
String crlsignkeystr = kstr.getKey(SecConst.CAKEYPURPOSE_CRLSIGN);
log.debug("CAKEYPURPOSE_CRLSIGN keystring is: "+crlsignkeystr);
if (!activate) {
// If the new keys are not activated we must still use the old key as active signing key (PRIVATESIGNKEYALIAS)
log.debug("Set nextCertSignKey: "+newKeyLabel);
properties.setProperty(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_NEXT, newKeyLabel);
log.debug("Set next sequence: "+newSequence);
properties.setProperty(ICAToken.NEXT_SEQUENCE_PROPERTY, newSequence);
} else {
properties.setProperty(certsignkeystr, newKeyLabel);
properties.setProperty(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_PREVIOUS, keyLabel);
// If the key strings are not equal, i.e. crtSignKey and crlSignKey was used instead of just defaultKey
// and the keys are the same. Then we need to set both keys to use the new key label
if (!StringUtils.equals(certsignkeystr, crlsignkeystr) && StringUtils.equals(keyLabel, crlKeyLabel)) {
log.debug("Also setting crlsignkeystr to "+newKeyLabel);
properties.setProperty(crlsignkeystr, newKeyLabel);
}
// Also set the previous sequence
properties.setProperty(ICAToken.PREVIOUS_SEQUENCE_PROPERTY, oldSequence);
}
setProperties(properties);
tokentype = "PKCS#11"; // for logging
}
} else {
String msg = intres.getLocalizedMessage("catoken.genkeysnotavail");
log.error(msg);
return;
}
// Store the new sequence permanently. We should not do this earlier, because if an error is thrown generating keys we should not have updated the CA token object
if (activate) {
log.debug("Setting new sequence: "+newSequence);
setKeySequence(newSequence);
}
// Finally reset the token so it will be re-read when we want to use it
this.catoken = null;
String msg = intres.getLocalizedMessage("catoken.generatedkeys", tokentype);
log.info(msg);
if (log.isTraceEnabled()) {
log.trace("<generateKeys");
}
} // generateKeys
/**
* Stores keystore bytes and properties of a soft keystore
* @param authenticationCode
* @param info
* @param properties
* @param keystore
*/
private void storeSoftKeyStore(char[] authenticationCode,
SoftCATokenInfo info, Properties properties, KeyStore keystore)
throws KeyStoreException, IOException, NoSuchAlgorithmException,
CertificateException {
if (log.isTraceEnabled()) {
log.trace(">storeSoftKeyStore");
}
// Store the key store
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
keystore.store(baos, authenticationCode);
String ksbytes = new String(Base64.encode(baos.toByteArray()));
if (log.isDebugEnabled()) {
log.debug("Storing soft keystore of size "+ksbytes.length());
}
data.put(KEYSTORE, ksbytes);
data.put(SIGNKEYSPEC, info.getSignKeySpec());
data.put(SIGNKEYALGORITHM, info.getSignKeyAlgorithm());
data.put(SIGNATUREALGORITHM, info.getSignatureAlgorithm());
data.put(ENCKEYSPEC, info.getEncKeySpec());
data.put(ENCKEYALGORITHM, info.getEncKeyAlgorithm());
data.put(ENCRYPTIONALGORITHM, info.getEncryptionAlgorithm());
// Store any changed properties
if (properties != null) {
setProperties(properties);
}
if (log.isTraceEnabled()) {
log.trace("<storeSoftKeyStore");
}
} // storeSoftKeyStore
/**
*
* @throws CATokenAuthenticationFailedException
* @see org.ejbca.core.model.ca.catoken.CATokenContainer#activateNextSignKey(java.lang.String)
*/
public void activateNextSignKey(String authenticationCode) throws CATokenOfflineException, KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyException, SignatureException, CATokenAuthenticationFailedException {
if (log.isTraceEnabled()) {
log.trace(">activateNextSignKey: "+(authenticationCode == null ? "null":"hidden"));
}
// First make a check that we have a next sign key
CATokenInfo catokeninfo = getCATokenInfo();
String oldSequence = getKeySequence();
log.debug("Current old sequence: "+oldSequence);
// If we don't give an authentication code, perhaps we have autoactivation enabled
char[] authCode = getAuthCodeOrAutoactivationPin(authenticationCode);
Properties properties = getProperties();
String tokentype = null;
// Then we can move on to actually move the keys
if (catokeninfo instanceof SoftCATokenInfo) {
// If we have an existing soft keystore verify that the password is correct
checkSoftKeystorePassword(authenticationCode);
SoftCATokenInfo info = (SoftCATokenInfo) catokeninfo;
ICAToken token = getCAToken();
PublicKey pubEnc = token.getPublicKey(SecConst.CAKEYPURPOSE_KEYENCRYPT);
PrivateKey privEnc = token.getPrivateKey(SecConst.CAKEYPURPOSE_KEYENCRYPT);
PublicKey pubSign = token.getPublicKey(SecConst.CAKEYPURPOSE_CERTSIGN);
PrivateKey privSign = token.getPrivateKey(SecConst.CAKEYPURPOSE_CERTSIGN);
PublicKey nextPubSign = token.getPublicKey(SecConst.CAKEYPURPOSE_CERTSIGN_NEXT);
PrivateKey nextPrivSign = token.getPrivateKey(SecConst.CAKEYPURPOSE_CERTSIGN_NEXT);
// Create the new keystore and add the new signature keys
KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
keystore.load(null, null);
// Insert the old "next" keys into the now active keys
log.debug("Setting nextPrivSign as "+SoftCAToken.PRIVATESIGNKEYALIAS+" in soft CA token.");
Certificate[] certchain = new Certificate[1]; // generate dummy certificate
String sigAlg = (String)AlgorithmTools.getSignatureAlgorithms(nextPubSign).iterator().next();
certchain[0] = CertTools.genSelfCert("CN=dummy", 36500, null, nextPrivSign, nextPubSign, sigAlg, true);
keystore.setKeyEntry(SoftCAToken.PRIVATESIGNKEYALIAS, nextPrivSign, null, certchain);
// Insert the old active keys into the now "old" keys
log.debug("Setting privSign as "+SoftCAToken.PREVIOUSPRIVATESIGNKEYALIAS+" in soft CA token.");
sigAlg = (String)AlgorithmTools.getSignatureAlgorithms(pubSign).iterator().next();
certchain[0] = CertTools.genSelfCert("CN=dummy2", 36500, null, privSign, pubSign, sigAlg, true);
keystore.setKeyEntry(SoftCAToken.PREVIOUSPRIVATESIGNKEYALIAS, privSign, null, certchain);
properties.setProperty(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_PREVIOUS, SoftCAToken.PREVIOUSPRIVATESIGNKEYALIAS);
// Finally install the old encryption/decryption keys as well
sigAlg = (String)AlgorithmTools.getSignatureAlgorithms(pubEnc).iterator().next();
certchain[0] = CertTools.genSelfCert("CN=dummy2", 36500, null, privEnc, pubEnc, sigAlg, true);
keystore.setKeyEntry(SoftCAToken.PRIVATEDECKEYALIAS, privEnc, null, certchain);
activateKeyFixProperties(oldSequence, properties);
// Serialize and store the soft keystore
storeSoftKeyStore(authCode, info, properties, keystore);
tokentype = "Soft"; // for logging
} else if (catokeninfo instanceof HardCATokenInfo) {
ICAToken token = getCAToken();
if (token instanceof PKCS11CAToken) {
String nextKeyLabel = token.getKeyLabel(SecConst.CAKEYPURPOSE_CERTSIGN_NEXT);
String currentKeyLabel = token.getKeyLabel(SecConst.CAKEYPURPOSE_CERTSIGN);
String crlKeyLabel = token.getKeyLabel(SecConst.CAKEYPURPOSE_CRLSIGN);
log.debug("Old key label is: "+currentKeyLabel);
KeyStrings kstr = new KeyStrings(properties);
String certsignkeystr = kstr.getKey(SecConst.CAKEYPURPOSE_CERTSIGN);
log.debug("CAKEYPURPOSE_CERTSIGN keystring is: "+certsignkeystr);
String crlsignkeystr = kstr.getKey(SecConst.CAKEYPURPOSE_CRLSIGN);
log.debug("CAKEYPURPOSE_CRLSIGN keystring is: "+crlsignkeystr);
log.debug("Setting sertsignkeystr to "+nextKeyLabel);
properties.setProperty(certsignkeystr, nextKeyLabel);
if (!StringUtils.equals(certsignkeystr, crlsignkeystr) && StringUtils.equals(currentKeyLabel, crlKeyLabel)) {
log.debug("Also setting crlsignkeystr to "+nextKeyLabel);
properties.setProperty(crlsignkeystr, nextKeyLabel);
}
log.debug("Set previousCertSignKey: "+currentKeyLabel);
properties.setProperty(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_PREVIOUS, currentKeyLabel);
activateKeyFixProperties(oldSequence, properties);
// Store updated properties
setProperties(properties);
tokentype = "PKCS#11"; // for logging
}
}
// Finally reset the token so it will be re-read when we want to use it
this.catoken = null;
String msg = intres.getLocalizedMessage("catoken.activatednextkey", tokentype);
log.info(msg);
if (log.isTraceEnabled()) {
log.trace("<activateNextSignKey");
}
} // activateNextSignKey
private char[] getAuthCodeOrAutoactivationPin(String authenticationCode)
throws IOException, CATokenAuthenticationFailedException {
// Generating new keys on token needs an authentication code
char[] authCode = (authenticationCode!=null && authenticationCode.length()>0)? authenticationCode.toCharArray():null;
if (authCode == null) {
String pin = BaseCAToken.getAutoActivatePin(getProperties());
if (pin == null) {
String msg = intres.getLocalizedMessage("catoken.authcodemissing", Integer.valueOf(caid));
log.info(msg);
throw new CATokenAuthenticationFailedException(msg);
}
authCode = pin.toCharArray();
}
return authCode;
}
/** Verifies the password for soft keystore by trying to load the keystore
*
* @param authenticationCode authentication code for the keystore
* @throws CATokenAuthenticationFailedException
*/
private void checkSoftKeystorePassword(String authenticationCode) throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, CertificateException, CATokenAuthenticationFailedException {
try {
if(data.get(CATokenContainer.KEYSTORE) != null){
byte[] ksdata = Base64.decode(((String) data.get(CATokenContainer.KEYSTORE)).getBytes());
KeyStore keystore=KeyStore.getInstance("PKCS12", "BC");
keystore.load(new java.io.ByteArrayInputStream(ksdata),authenticationCode.toCharArray());
}
} catch (IOException e) {
// Invalid password
String msg = intres.getLocalizedMessage("catoken.wrongauthcode", Integer.valueOf(caid));
log.info(msg);
if (!(e instanceof IOException)) {
// If it was not the wrong password we need to see what went wrong
log.debug("Error: ", e);
}
throw new CATokenAuthenticationFailedException(msg);
}
}
/**
* Common operation for both soft and hard keystores, move the sequences and remove the NEXT key keystring
*
* @param oldSequence the old sequence that will be moved to "previous" sequence in the properties
* @param properties properties parameter content is modified with new and removed properties
*/
private void activateKeyFixProperties(String oldSequence, Properties properties) {
// Set new and previous sequence so we can create link certificates
String nextSequence = properties.getProperty(ICAToken.NEXT_SEQUENCE_PROPERTY);
if (nextSequence != null) {
log.info("Set key sequence from nextSequence: "+nextSequence);
setKeySequence(nextSequence);
}
log.info("Set previous sequence: "+oldSequence);
properties.setProperty(ICAToken.PREVIOUS_SEQUENCE_PROPERTY, oldSequence);
log.info("Remove nextSequence and nextCertSignKey");
properties.remove(ICAToken.NEXT_SEQUENCE_PROPERTY);
properties.remove(KeyStrings.CAKEYPURPOSE_CERTSIGN_STRING_NEXT);
} // activateKeyFixProperties
/**
* Method that import CA token keys from a P12 file. Was originally used when upgrading from
* old EJBCA versions. Only supports SHA1 and SHA256 with RSA or ECDSA and SHA1 with DSA.
*/
public void importKeys(String authenticationCode, PrivateKey privatekey, PublicKey publickey, PrivateKey privateEncryptionKey,
PublicKey publicEncryptionKey, Certificate[] caSignatureCertChain) throws Exception{
// If we don't give an authentication code, perhaps we have autoactivation enabled
char[] authCode = getAuthCodeOrAutoactivationPin(authenticationCode);
// Currently only RSA keys are supported
KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
keystore.load(null,null);
// The CAs certificate is first in chain
Certificate cacert = caSignatureCertChain[0];
// Assume that the same hash algorithm is used for signing that was used to sign this CA cert
String signatureAlgorithm = CertTools.getSignatureAlgorithm(cacert);
String keyAlg = AlgorithmTools.getKeyAlgorithm(publickey);
if (keyAlg == null) {
throw new Exception("Unknown public key type: " + publickey.getAlgorithm() + " (" + publickey.getClass() + ")");
}
// If this is a CVC CA we need to find out the sequence
if (cacert instanceof CardVerifiableCertificate) {
CardVerifiableCertificate cvccacert = (CardVerifiableCertificate) cacert;
log.debug("Getting sequence from holderRef in CV certificate.");
String sequence = cvccacert.getCVCertificate().getCertificateBody().getHolderReference().getSequence();
log.debug("Setting sequence "+sequence);
setKeySequence(sequence);
log.debug("Setting default sequence format "+StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
setKeySequenceFormat(StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
} else {
log.debug("Setting default sequence "+CATokenConstants.DEFAULT_KEYSEQUENCE);
setKeySequence(CATokenConstants.DEFAULT_KEYSEQUENCE);
log.debug("Setting default sequence format "+StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
setKeySequenceFormat(StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
}
// import sign keys.
String keyspec = AlgorithmTools.getKeySpecification(publickey);
Certificate[] certchain = new Certificate[1];
certchain[0] = CertTools.genSelfCert("CN=dummy", 36500, null, privatekey, publickey, signatureAlgorithm, true);
keystore.setKeyEntry(SoftCAToken.PRIVATESIGNKEYALIAS, privatekey, null, certchain);
// generate enc keys.
// Encryption keys must be RSA still
String encryptionSignatureAlgorithm = AlgorithmTools.getEncSigAlgFromSigAlg(signatureAlgorithm);
keyAlg = AlgorithmTools.getKeyAlgorithmFromSigAlg(encryptionSignatureAlgorithm);
keyspec = "2048";
KeyPair enckeys = null;
if ( publicEncryptionKey == null || privateEncryptionKey == null ) {
enckeys = KeyTools.genKeys(keyspec, keyAlg);
}
else {
enckeys = new KeyPair(publicEncryptionKey, privateEncryptionKey);
}
// generate dummy certificate
certchain[0] = CertTools.genSelfCert("CN=dummy2", 36500, null, enckeys.getPrivate(), enckeys.getPublic(), encryptionSignatureAlgorithm, true);
keystore.setKeyEntry(SoftCAToken.PRIVATEDECKEYALIAS,enckeys.getPrivate(),null,certchain);
// Store keystore
SoftCATokenInfo info = new SoftCATokenInfo();
info.setEncKeyAlgorithm(keyAlg);
info.setEncKeySpec(keyspec);
info.setEncryptionAlgorithm(encryptionSignatureAlgorithm);
info.setSignKeyAlgorithm(keyAlg);
info.setSignKeySpec(keyspec);
info.setSignatureAlgorithm(signatureAlgorithm);
storeSoftKeyStore(authCode, info, null, keystore);
// Finally reset the token so it will be re-read when we want to use it
this.catoken = null;
} // importKeys
//
// Private methods
//
/**
* Returns the class path of a CA Token.
*/
private String getClassPath(){
return (String) data.get(CLASSPATH);
}
/**
* Sets the class path of a CA Token.
*/
private void setClassPath(String classpath){
data.put(CLASSPATH, classpath);
}
/**
* Returns the SignatureAlgoritm
*/
private String getSignatureAlgorithm(){
return (String) data.get(SIGNATUREALGORITHM);
}
/**
* Sets the SignatureAlgoritm
*/
private void setSignatureAlgorithm(String signaturealgoritm){
data.put(SIGNATUREALGORITHM, signaturealgoritm);
}
/**
* Returns the Sequence, that is a sequence that is updated when keys are re-generated
*/
private String getKeySequence(){
Object seq = data.get(SEQUENCE);
if (seq == null) {
seq = new String(CATokenConstants.DEFAULT_KEYSEQUENCE);
}
return (String)seq;
}
/**
* Sets the key sequence
*/
private void setKeySequence(String sequence){
data.put(SEQUENCE, sequence);
}
/**
* Sets the SequenceFormat
*/
private void setKeySequenceFormat(int sequence){
data.put(SEQUENCE_FORMAT, sequence);
}
/**
* Returns the Sequence format, that is the format of the key sequence
*/
private int getKeySequenceFormat(){
Object seqF = data.get(SEQUENCE_FORMAT);
if (seqF == null) {
seqF = Integer.valueOf(StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
}
return (Integer)seqF;
}
/**
* Returns the propertydata used to configure this CA Token.
*/
private String getPropertyData(){
String ret = null;
if (data != null) {
ret = (String)data.get(PROPERTYDATA);
}
return ret;
}
/**
* Sets the propertydata used to configure this CA Token.
*/
private void setPropertyData(String propertydata){
data.put(PROPERTYDATA, propertydata);
}
private void setProperties(Properties prop) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
prop.store(baos, null);
baos.close(); // this has no effect according to javadoc
setPropertyData(baos.toString());
// Update the properties if we have set new keystrings
if (catoken != null) {
catoken.updateProperties(prop);
}
}
private Properties getProperties() throws IOException{
Properties prop = new PropertiesWithHiddenPIN();
String pdata = getPropertyData();
if (pdata != null) {
prop.load(new ByteArrayInputStream(pdata.getBytes()));
}
return prop;
}
private ICAToken getCAToken() {
if(catoken == null){
try{
Class implClass = Class.forName( getClassPath());
Object obj = implClass.newInstance();
this.catoken = (ICAToken) obj;
this.catoken.init(getProperties(), data, getSignatureAlgorithm(), this.caid);
}catch(Throwable e){
log.error("Error contructing CA Token (setting to null): ", e);
catoken = null;
}
}
return catoken;
}
//
// Methods for implementing the UpgradeableDataHashMap
//
/**
* @see org.ejbca.core.model.ca.publisher.BasePublisher#getLatestVersion()
*/
public float getLatestVersion() {
return LATEST_VERSION;
}
public void upgrade() {
if(Float.compare(LATEST_VERSION, getVersion()) != 0) {
// New version of the class, upgrade
String msg = intres.getLocalizedMessage("catoken.upgrade", new Float(getVersion()));
log.info(msg);
if(data.get(SIGNKEYALGORITHM) == null) {
String oldKeyAlg = (String)data.get(KEYALGORITHM);
if (oldKeyAlg != null) {
data.put(SIGNKEYALGORITHM, oldKeyAlg);
data.put(ENCKEYALGORITHM, oldKeyAlg);
}
}
if(data.get(SIGNKEYSPEC) == null) {
Integer oldKeySize = ((Integer) data.get(KEYSIZE));
if (oldKeySize != null) {
data.put(SIGNKEYSPEC, oldKeySize.toString());
data.put(ENCKEYSPEC, oldKeySize.toString());
}
}
if(data.get(ENCRYPTIONALGORITHM) == null) {
String signAlg = (String)data.get(SIGNATUREALGORITHM);
data.put(ENCRYPTIONALGORITHM, signAlg);
}
if (data.get(CLASSPATH) == null) {
String classpath = SoftCAToken.class.getName();
if (data.get(KEYSTORE) == null) {
classpath = NullCAToken.class.getName();
}
log.info("Adding new classpath to CA Token data: "+classpath);
data.put(CLASSPATH, classpath);
}
if (data.get(SEQUENCE) == null) {
String sequence = CATokenConstants.DEFAULT_KEYSEQUENCE;
log.info("Adding new sequence to CA Token data: "+sequence);
data.put(SEQUENCE, sequence);
}
if (data.get(SEQUENCE_FORMAT) == null) { // v7
log.info("Adding new sequence format to CA Token data: "+StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
data.put(SEQUENCE_FORMAT, StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
}
data.put(VERSION, new Float(LATEST_VERSION));
}
}
}