Java tutorial
/************************************************************************* * * * 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.ByteArrayOutputStream; import java.io.PrintStream; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Provider; import java.security.ProviderException; import java.security.PublicKey; import java.security.Security; import java.security.cert.Certificate; import java.util.Enumeration; import java.util.Hashtable; import java.util.Map; import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.ejbca.config.EjbcaConfiguration; import org.ejbca.core.model.InternalResources; import org.ejbca.core.model.SecConst; import org.ejbca.util.StringTools; import org.ejbca.util.keystore.KeyTools; /** * @author lars * @version $Id: BaseCAToken.java 10358 2010-11-03 18:34:23Z mikekushner $ */ public abstract class BaseCAToken implements ICAToken { /** Log4j instance */ private static final Logger log = Logger.getLogger(BaseCAToken.class); /** Internal localization of logs and errors */ private static final InternalResources intres = InternalResources.getInstance(); /** Used for signatures */ private String mJcaProviderName = null; /** Used for encrypt/decrypt, can be same as for signatures for example for pkcs#11 */ private String mJceProviderName = null; private KeyStrings keyStrings; protected String sSlotLabel = null; private Map<String, KeyPair> mKeys; private String mAuthCode; public BaseCAToken() { super(); } public BaseCAToken(String providerClass) throws InstantiationException { try { Class.forName(providerClass); } catch (ClassNotFoundException e) { throw new InstantiationException("Class not found: " + providerClass); } } protected void autoActivate() { if (this.mKeys == null && this.mAuthCode != null) { try { log.debug("Trying to autoactivate CAToken"); activate(this.mAuthCode); } catch (Exception e) { log.debug(e); } } } /** * do we permit extractable? Only SW keys should be permited to be extractable, * @return false if the key must not be extractable */ protected boolean doPermitExtractablePrivateKey() { return false; } private void testKey(KeyPair pair) throws Exception { if (log.isDebugEnabled()) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(baos); KeyTools.printPublicKeyInfo(pair.getPublic(), ps); ps.flush(); log.debug("Using of " + baos.toString()); } if (!doPermitExtractablePrivateKey() && KeyTools.isPrivateKeyExtractable(pair.getPrivate())) { String msg = intres.getLocalizedMessage("catoken.extractablekey", EjbcaConfiguration.doPermitExtractablePrivateKeys()); if (!EjbcaConfiguration.doPermitExtractablePrivateKeys()) { throw new InvalidKeyException(msg); } log.info(msg); } KeyTools.testKey(pair.getPrivate(), pair.getPublic(), getProvider()); } /** * @param keyStore * @param authCode * @throws Exception */ protected void setKeys(KeyStore keyStore, String authCode) throws Exception { this.mKeys = null; final String keyAliases[] = this.keyStrings.getAllStrings(); final Map<String, KeyPair> mTmp = new Hashtable<String, KeyPair>(); for (int i = 0; i < keyAliases.length; i++) { PrivateKey privateK = (PrivateKey) keyStore.getKey(keyAliases[i], (authCode != null && authCode.length() > 0) ? authCode.toCharArray() : null); if (privateK == null) { log.error(intres.getLocalizedMessage("catoken.noprivate", keyAliases[i])); if (log.isDebugEnabled()) { for (int j = 0; j < keyAliases.length; j++) { log.debug("Existing alias: " + keyAliases[j]); } } } else { PublicKey publicK = readPublicKey(keyStore, keyAliases[i]); if (publicK != null) { KeyPair keyPair = new KeyPair(publicK, privateK); mTmp.put(keyAliases[i], keyPair); } } } for (int i = 0; i < keyAliases.length; i++) { KeyPair pair = mTmp.get(keyAliases[i]); if (log.isDebugEnabled()) { log.debug("Testing keys with alias " + keyAliases[i]); } if (pair == null) { log.info("No keys with alias " + keyAliases[i] + " exists."); } else { testKey(pair); // Test signing for the KeyPair (this could theoretically fail if singing is not allowed by the provider for this key) if (log.isDebugEnabled()) { log.debug("Key with alias " + keyAliases[i] + " tested."); } } } this.mKeys = mTmp; if (getCATokenStatus() != ICAToken.STATUS_ACTIVE) { throw new Exception("Activation test failed"); } } /** * @param keyStore * @param alias * @return * @throws Exception */ protected PublicKey readPublicKey(KeyStore keyStore, String alias) throws Exception { Certificate cert = keyStore.getCertificate(alias); PublicKey pubk = null; if (cert != null) { pubk = cert.getPublicKey(); } else { log.error(intres.getLocalizedMessage("catoken.nopublic", alias)); if (log.isDebugEnabled()) { Enumeration en = keyStore.aliases(); while (en.hasMoreElements()) { log.debug("Existing alias: " + (String) en.nextElement()); } } } return pubk; } protected void init(String sSlotLabelKey, Properties properties, String signaturealgorithm, boolean doAutoActivate) { if (log.isDebugEnabled()) { log.debug(">init: sSlotLabelKey=" + sSlotLabelKey + ", Signaturealg=" + signaturealgorithm); } // Set basic properties that are of dynamic nature updateProperties(properties); // Set properties that can not change dynamically this.sSlotLabel = getSlotLabel(sSlotLabelKey, properties); if (doAutoActivate) { autoActivate(); } if (log.isDebugEnabled()) { log.debug("<init: sSlotLabelKey=" + sSlotLabelKey + ", Signaturealg=" + signaturealgorithm); } } // init /** @see ICAToken#updateProperties(Properties) */ public void updateProperties(Properties properties) { if (log.isDebugEnabled()) { // This is only a sections for debug logging. If we have enabled debug logging we don't want to display any password in the log. // These properties may contain autoactivation PIN codes and we will, only when debug logging, replace this with "hidden". if (properties.containsKey(ICAToken.AUTOACTIVATE_PIN_PROPERTY) || properties.containsKey("PIN")) { Properties prop = new Properties(); prop.putAll(properties); if (properties.containsKey(ICAToken.AUTOACTIVATE_PIN_PROPERTY)) { prop.setProperty(ICAToken.AUTOACTIVATE_PIN_PROPERTY, "hidden"); } if (properties.containsKey("PIN")) { prop.setProperty("PIN", "hidden"); } log.debug("Prop: " + (prop != null ? prop.toString() : "null")); } else { // If no autoactivation PIN codes exists we can debug log everything as original. log.debug("Properties: " + (properties != null ? properties.toString() : "null")); } } // if (log.isDebugEnabled()) this.keyStrings = new KeyStrings(properties); this.mAuthCode = BaseCAToken.getAutoActivatePin(properties); } // updateProperties /** Extracts the slotLabel that is used for many tokens in construction of the provider * * @param sSlotLabelKey which key in the properties that gives us the label * @param properties CA token properties * @return String with the slot label, trimmed from whitespace */ protected static String getSlotLabel(String sSlotLabelKey, Properties properties) { String ret = null; if (sSlotLabelKey != null && properties != null) { ret = properties.getProperty(sSlotLabelKey); if (ret != null) { ret = ret.trim(); } } return ret; } protected static String getAutoActivatePin(Properties properties) { final String pin = properties.getProperty(ICAToken.AUTOACTIVATE_PIN_PROPERTY); if (pin != null) { return StringTools.passwordDecryption(pin, "autoactivation pin"); } if (log.isDebugEnabled()) { log.debug("Not using autoactivation pin"); } return null; } /** Sets auto activation pin in passed in properties. Also returns the string format of the * autoactivation properties: * pin mypassword * * @param properties a Properties bag where to set the auto activation pin, can be null if you only want to create the return string, does not set a null or empty password * @param pin the activation password * @param encrypt if the PIN should be encrypted with a simple built in encryption with only purpose of hiding the password from simple viewing. No strong security from this encryption * @return A string that can be used to "setProperties" of a CAToken or null if pin is null or an empty string, this can safely be ignored if you don't know what to do with it */ public static String setAutoActivatePin(Properties properties, String pin, boolean encrypt) { String ret = null; if (StringUtils.isNotEmpty(pin)) { String authcode = pin; if (encrypt) { try { authcode = StringTools.pbeEncryptStringWithSha256Aes192(pin); } catch (Exception e) { log.error(intres.getLocalizedMessage("catoken.nopinencrypt"), e); authcode = pin; } } if (properties != null) { properties.setProperty(ICAToken.AUTOACTIVATE_PIN_PROPERTY, authcode); } ret = ICAToken.AUTOACTIVATE_PIN_PROPERTY + " " + authcode; } return ret; } /** Sets both signature and encryption providers. If encryption provider is the same as signature provider this * class name can be null. * @param jcaProviderClassName signature provider class name * @param jceProviderClassName encryption provider class name, can be null * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException * @see {@link #setJCAProvider(Provider)} */ protected void setProviders(String jcaProviderClassName, String jceProviderClassName) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Provider jcaProvider = (Provider) Class.forName(jcaProviderClassName).newInstance(); setProvider(jcaProvider); this.mJcaProviderName = jcaProvider.getName(); if (jceProviderClassName != null) { try { Provider jceProvider = (Provider) Class.forName(jceProviderClassName).newInstance(); setProvider(jceProvider); this.mJceProviderName = jceProvider.getName(); } catch (Exception e) { log.error(intres.getLocalizedMessage("catoken.jceinitfail"), e); } } else { this.mJceProviderName = null; } } /** If we only have one provider to handle both JCA and JCE, and perhaps it is not so straightforward to * create the provider (for example PKCS#11 provider), we can create the provider in sub class and set it * here, instead of calling setProviders. * * @param prov the fully constructed Provider * @see #setProviders(String, String) */ protected void setJCAProvider(Provider prov) { setProvider(prov); this.mJcaProviderName = prov != null ? prov.getName() : null; } /** If we don't use any of the methods to set a specific provider, but use some already existing provider * we should set the name of that provider at least. * @param pName the provider name as retriever from Provider.getName() */ protected void setJCAProviderName(String pName) { this.mJcaProviderName = pName; } private void setProvider(Provider prov) { if (prov != null) { String pName = prov.getName(); if (pName.startsWith("LunaJCA")) { // Luna Java provider does not contain support for RSA/ECB/PKCS1Padding but this is // the same as the alias below on small amounts of data prov.put("Alg.Alias.Cipher.RSA/NONE/NoPadding", "RSA//NoPadding"); prov.put("Alg.Alias.Cipher.1.2.840.113549.1.1.1", "RSA//NoPadding"); prov.put("Alg.Alias.Cipher.RSA/ECB/PKCS1Padding", "RSA//PKCS1v1_5"); prov.put("Alg.Alias.Cipher.1.2.840.113549.3.7", "DES3/CBC/PKCS5Padding"); } if (Security.getProvider(pName) == null) { Security.addProvider(prov); } if (Security.getProvider(pName) == null) { throw new ProviderException("Not possible to install provider: " + pName); } } else { if (log.isDebugEnabled()) { log.debug("No provider passed to setProvider()"); } } } /* (non-Javadoc) * @see org.ejbca.core.model.ca.catoken.ICAToken#activate(java.lang.String) */ public abstract void activate(String authCode) throws CATokenOfflineException, CATokenAuthenticationFailedException; /* (non-Javadoc) * @see org.ejbca.core.model.ca.catoken.ICAToken#deactivate() */ public boolean deactivate() throws Exception { String msg = intres.getLocalizedMessage("catoken.deactivate"); log.info(msg); this.mKeys = null; return true; } /* (non-Javadoc) * @see org.ejbca.core.model.ca.catoken.ICAToken#getPrivateKey(int) */ public PrivateKey getPrivateKey(int purpose) throws CATokenOfflineException { autoActivate(); String keystring = this.keyStrings.getString(purpose); KeyPair keyPair = this.mKeys != null ? (KeyPair) this.mKeys.get(keystring) : null; if (keyPair == null) { String msg = intres.getLocalizedMessage("catoken.errornosuchkey", keystring, purpose); throw new CATokenOfflineException(msg); } return keyPair.getPrivate(); } /* (non-Javadoc) * @see org.ejbca.core.model.ca.catoken.ICAToken#getPublicKey(int) */ public PublicKey getPublicKey(int purpose) throws CATokenOfflineException { autoActivate(); String keystring = this.keyStrings.getString(purpose); KeyPair keyPair = this.mKeys != null ? (KeyPair) this.mKeys.get(keystring) : null; if (keyPair == null) { String msg = intres.getLocalizedMessage("catoken.errornosuchkey", keystring, purpose); throw new CATokenOfflineException(msg); } return keyPair.getPublic(); } /* (non-Javadoc) * @see org.ejbca.core.model.ca.catoken.ICAToken#getKeyLabel(int) */ public String getKeyLabel(int purpose) { return this.keyStrings.getString(purpose); } /* (non-Javadoc) * @see org.ejbca.core.model.ca.catoken.ICAToken#getProvider() */ public String getProvider() { return this.mJcaProviderName; } /* (non-Javadoc) * @see org.ejbca.core.model.ca.catoken.ICAToken#getJCEProvider() */ public String getJCEProvider() { // If we don't have a specific JCE provider, it is most likely the same // as the JCA provider if (this.mJceProviderName == null) { return this.mJcaProviderName; } return this.mJceProviderName; } /* (non-Javadoc) * @see org.ejbca.core.model.ca.caadmin.ICAToken#getCATokenStatus() */ public int getCATokenStatus() { if (log.isTraceEnabled()) { log.trace(">getCATokenStatus"); } autoActivate(); int ret = ICAToken.STATUS_OFFLINE; // If we have no keystrings, no point in continuing... if (this.keyStrings != null) { String strings[] = this.keyStrings.getAllStrings(); int i = 0; while (strings != null && i < strings.length && this.mKeys != null && this.mKeys.get(strings[i]) != null) { i++; } // If we don't have any keys for the strings, or we don't have enough keys for the strings, no point in continuing... if (strings != null && i >= strings.length) { PrivateKey privateKey; PublicKey publicKey; try { privateKey = getPrivateKey(SecConst.CAKEYPURPOSE_KEYTEST); publicKey = getPublicKey(SecConst.CAKEYPURPOSE_KEYTEST); } catch (CATokenOfflineException e) { privateKey = null; publicKey = null; if (log.isDebugEnabled()) { log.debug("no test key defined"); } } if (privateKey != null && publicKey != null) { //Check that that the testkey is usable by doing a test signature. try { testKey(new KeyPair(publicKey, privateKey)); // If we can test the testkey, we are finally active! ret = ICAToken.STATUS_ACTIVE; } catch (Throwable th) { log.error(intres.getLocalizedMessage("catoken.activationtestfail"), th); } } } } if (log.isTraceEnabled()) { log.trace("<getCATokenStatus: " + ret); } return ret; } /* (non-Javadoc) * @see org.ejbca.core.model.ca.catoken.ICAToken#reset() */ public void reset() { // do nothing. the implementing class decides whether something could be done to get the HSM working after a failure. } public boolean isActive() { return this.mKeys != null; } }