org.ejbca.core.model.ca.catoken.BaseCAToken.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.core.model.ca.catoken.BaseCAToken.java

Source

/*************************************************************************
 *                                                                       *
 *  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;
    }
}