org.cesecore.keys.token.SoftCryptoToken.java Source code

Java tutorial

Introduction

Here is the source code for org.cesecore.keys.token.SoftCryptoToken.java

Source

/*************************************************************************
 *                                                                       *
 *  CESeCore: CE Security Core                                           *
 *                                                                       *
 *  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.cesecore.keys.token;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Properties;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Hex;
import org.cesecore.config.CesecoreConfiguration;
import org.cesecore.internal.InternalResources;
import org.cesecore.keys.util.KeyStoreTools;
import org.cesecore.util.CryptoProviderTools;
import org.cesecore.util.StringTools;

/**
 * Handles maintenance of the soft devices producing signatures and handling the private key and stored in database.
 * 
 * @version $Id: SoftCryptoToken.java 20895 2015-03-12 15:31:48Z jeklund $
 */
public class SoftCryptoToken extends BaseCryptoToken {
    private static final long serialVersionUID = 387950849444619646L;

    /** Log4j instance */
    private static final Logger log = Logger.getLogger(SoftCryptoToken.class);
    /** Internal localization of logs and errors */
    private static final InternalResources intres = InternalResources.getInstance();

    /**
     * When upgrading this version, you must up the version of the CA as well, otherwise the upgraded CA token will not be stored in the database.
     */
    public static final float LATEST_VERSION = 3;

    private static final String PROVIDER = "BC";

    public static final String NODEFAULTPWD = "NODEFAULTPWD";

    private byte[] keystoreData;
    private char[] keyStorePass;

    public SoftCryptoToken() {
        super();
        if (log.isDebugEnabled()) {
            log.debug("Creating SoftCryptoToken");
        }
    }

    /**
     * Sets up some basic properties used in soft keystores and calls init on BaseCryptoToken in order to set up all key string etc.
     */
    @Override
    public void init(Properties properties, final byte[] data, final int cryptoTokenId) {
        super.setJCAProviderName(PROVIDER);
        this.keystoreData = data;
        if (properties == null) {
            properties = new Properties();
        }
        // If we don't have an auto activation password set, we try to use the default one if it works to load the keystore with it
        String autoPwd = BaseCryptoToken.getAutoActivatePin(properties);
        if ((autoPwd == null) && (properties.getProperty(NODEFAULTPWD) == null)) {
            final String keystorepass = StringTools.passwordDecryption(CesecoreConfiguration.getCaKeyStorePass(),
                    "ca.keystorepass");
            // Test it first, don't set an incorrect password as autoa-ctivate password
            boolean okPwd = checkSoftKeystorePassword(keystorepass.toCharArray(), cryptoTokenId);
            if (okPwd) {
                log.debug("Succeded to load keystore with password");
                BaseCryptoToken.setAutoActivatePin(properties, keystorepass, true);
            }
        } else if (autoPwd != null) {
            log.debug("Soft Crypto Token has autoactivation property set.");
        } else if (properties.getProperty(NODEFAULTPWD) != null) {
            log.debug("No default pwd allowed for this soft crypto token.");
        }
        boolean autoActivate = autoPwd != null || properties.getProperty(NODEFAULTPWD) == null;
        init(properties, autoActivate, cryptoTokenId);
    }

    @Override
    public void activate(char[] authCode)
            throws CryptoTokenAuthenticationFailedException, CryptoTokenOfflineException {
        if (keyStore != null) {
            log.debug("Ignoring activation request for already active CryptoToken: " + getId());
            return;
        }
        // If we use auto-activation, we will override whatever is used as parameter (probably null)
        final String autoPwd = BaseCryptoToken.getAutoActivatePin(getProperties());
        if (autoPwd != null) {
            authCode = autoPwd.toCharArray();
        }
        if (keystoreData != null) {
            try {
                KeyStore keystore = loadKeyStore(keystoreData, authCode);
                setKeyStore(keystore);
                // If everything was OK we cache the load/save password so we can store the keystore
                keyStorePass = authCode;
            } catch (IOException e) {
                String msg = intres.getLocalizedMessage("token.erroractivate", getId(), e.getMessage());
                log.info(msg, e);
                CryptoTokenAuthenticationFailedException oe = new CryptoTokenAuthenticationFailedException(
                        e.getMessage());
                oe.initCause(e);
                throw oe;
            } catch (Exception e) {
                String msg = intres.getLocalizedMessage("token.erroractivate", getId(), e.getMessage());
                log.info(msg, e);
                CryptoTokenOfflineException oe = new CryptoTokenOfflineException(e.getMessage());
                oe.initCause(e);
                throw oe;
            }
            String msg = intres.getLocalizedMessage("token.activated", getId());
            log.info(msg);
        } else {
            String msg = intres.getLocalizedMessage("token.erroractivate", getId(),
                    "No keystore data available yet, creating new PKCS#12 keystore.");
            log.info(msg);
            try {
                KeyStore keystore = KeyStore.getInstance("PKCS12", PROVIDER);
                keystore.load(null, null);
                //keystore.load(null, authCode);
                setKeyStore(keystore);
                // If everything was OK we cache the load/save password so we can store the keystore
                keyStorePass = authCode;
                storeKeyStore();
            } catch (KeyStoreException e) {
                log.error(e);
                throw new CryptoTokenAuthenticationFailedException(e.getMessage());
            } catch (NoSuchProviderException e) {
                log.error(e);
                throw new CryptoTokenAuthenticationFailedException(e.getMessage());
            } catch (NoSuchAlgorithmException e) {
                log.error(e);
                throw new CryptoTokenAuthenticationFailedException(e.getMessage());
            } catch (CertificateException e) {
                log.error(e);
                throw new CryptoTokenAuthenticationFailedException(e.getMessage());
            } catch (IOException e) {
                log.error(e);
                throw new CryptoTokenAuthenticationFailedException(e.getMessage());
            }
        }
    }

    /**
     * Throws an exception if the export of this crypto token should be denied.
     * 
     * @param authCode
     * @throws CryptoTokenAuthenticationFailedException if the authentication code is incorrect.
     * @throws CryptoTokenOfflineException if the crypto token is offline or an unknown error occurs.
     * @throws PrivateKeyNotExtractableException if the crypto tokens does not allow it's keys to be extracted.
     */
    public void checkPasswordBeforeExport(char[] authCode) throws CryptoTokenAuthenticationFailedException,
            CryptoTokenOfflineException, PrivateKeyNotExtractableException {
        if (!doPermitExtractablePrivateKey()) {
            final String msg = intres.getLocalizedMessage("token.errornotextractable_allkeys", getId());
            throw new PrivateKeyNotExtractableException(msg);
        }
        try {
            if (authCode == null || authCode.length == 0) {
                final String defaultpass = StringTools.passwordDecryption(CesecoreConfiguration.getCaKeyStorePass(),
                        "ca.keystorepass");
                loadKeyStore(keystoreData, defaultpass.toCharArray());
            } else {
                loadKeyStore(keystoreData, authCode);
            }
        } catch (IOException e) {
            String msg = intres.getLocalizedMessage("token.wrongauthcode", getId(), e.getMessage());
            log.info(msg, e);
            CryptoTokenAuthenticationFailedException oe = new CryptoTokenAuthenticationFailedException(
                    e.getMessage());
            oe.initCause(e);
            throw oe;
        } catch (Exception e) {
            String msg = intres.getLocalizedMessage("token.erroractivate", getId(), e.getMessage());
            log.info(msg, e);
            CryptoTokenOfflineException oe = new CryptoTokenOfflineException(e.getMessage());
            oe.initCause(e);
            throw oe;
        }
    }

    private KeyStore loadKeyStore(final byte[] ksdata, final char[] keystorepass) throws NoSuchAlgorithmException,
            CertificateException, IOException, KeyStoreException, NoSuchProviderException {
        CryptoProviderTools.installBCProviderIfNotAvailable();
        KeyStore keystore = KeyStore.getInstance("PKCS12", PROVIDER);
        if (log.isDebugEnabled()) {
            log.debug("Loading keystore data of size: " + (ksdata == null ? "null" : ksdata.length));
        }
        keystore.load(new ByteArrayInputStream(ksdata), keystorepass);
        return keystore;
    }

    @Override
    public void deactivate() {
        storeKeyStore();
        try {
            setKeyStore(null);
        } catch (KeyStoreException e) {
            // Exception should only be thrown if loading a non-null KeyStore fails
            throw new IllegalStateException("This should never happen.");
        }
        String msg = intres.getLocalizedMessage("token.deactivate", getId());
        log.info(msg);
    }

    void storeKeyStore() {
        // Store keystore at data first so we can activate again
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            if (keyStore != null) {
                this.keyStore.store(baos, keyStorePass);
                this.keystoreData = baos.toByteArray();
            }
        } catch (KeyStoreException e) {
            log.error(e);
        } catch (NoSuchAlgorithmException e) {
            log.error(e);
        } catch (CertificateException e) {
            log.error(e);
        } catch (IOException e) {
            log.error(e);
        }
        if (log.isDebugEnabled()) {
            log.debug("Storing soft keystore of size " + (keystoreData == null ? "null" : keystoreData.length));
        }
    }

    @Override
    public byte[] getTokenData() {
        storeKeyStore();
        return keystoreData;
    }

    /**
     * Verifies the password for soft keystore by trying to load the keystore
     * 
     * @param authenticationCode
     *            authentication code for the keystore
     * @return true if verification was ok
     */
    private boolean checkSoftKeystorePassword(final char[] authenticationCode, int cryptoTokenId) {
        try {
            if (keystoreData != null) {
                KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
                keystore.load(new java.io.ByteArrayInputStream(keystoreData), authenticationCode);
            }
            return true;
        } catch (Exception e) {
            // If it was not the wrong password we need to see what went wrong
            log.debug("Error: ", e);
            // Invalid password
            log.info(intres.getLocalizedMessage("token.wrongauthcode", cryptoTokenId));
        }
        return false;
    }

    @Override
    public void deleteEntry(final String alias) throws KeyStoreException, NoSuchAlgorithmException,
            CertificateException, IOException, CryptoTokenOfflineException {
        if (StringUtils.isNotEmpty(alias)) {
            KeyStoreTools cont = new KeyStoreTools(getKeyStore(), getSignProviderName());
            try {
                cont.deleteEntry(alias);
                String msg = intres.getLocalizedMessage("token.deleteentry", alias, getId());
                log.info(msg);
            } catch (KeyStoreException e) { // NOPMD
                // P12 keystore throws when the alias can not be found, in contrary to PKCS#11 keystores
                // Java API is vague about what should happen so...
            }
            storeKeyStore();
        } else {
            log.debug("Trying to delete keystore entry with empty alias.");
        }
    }

    @Override
    public void generateKeyPair(final String keySpec, final String alias)
            throws InvalidAlgorithmParameterException, CryptoTokenOfflineException {
        if (StringUtils.isNotEmpty(alias)) {
            KeyStoreTools cont = new KeyStoreTools(getKeyStore(), getSignProviderName());
            cont.generateKeyPair(keySpec, alias);
            storeKeyStore();
        } else {
            log.debug("Trying to generate keys with empty alias.");
        }
    }

    @Override
    public void generateKey(final String algorithm, final int keysize, final String alias)
            throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException,
            CryptoTokenOfflineException, InvalidKeyException, InvalidAlgorithmParameterException,
            SignatureException, CertificateException, IOException, NoSuchPaddingException,
            IllegalBlockSizeException {
        if (StringUtils.isNotEmpty(alias)) {
            // Soft crypto tokens must do very special things for secret keys, since PKCS#12 keystores are ot designed to hold
            // symmetric keys, we wrap the symmetric key with an RSA key and store it in properties

            // Generate the key
            KeyGenerator generator = KeyGenerator.getInstance(algorithm, getEncProviderName());
            generator.init(keysize);
            Key key = generator.generateKey();
            // Wrap it
            // Find wrapping key
            PublicKey pubK = null;
            try {
                pubK = getPublicKey("symwrap");
            } catch (CryptoTokenOfflineException e) {
                // No such key, generate it
                generateKeyPair("2048", "symwrap");
                pubK = getPublicKey("symwrap");
            }

            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", getEncProviderName());
            cipher.init(Cipher.WRAP_MODE, pubK);
            byte[] out = cipher.wrap(key);

            String str = new String(Hex.encode(out));
            Properties prop = getProperties();
            prop.setProperty(alias, str);
            setProperties(prop);
        } else {
            log.debug("Trying to generate keys with empty alias.");
        }
    }

    @Override
    public void generateKeyPair(final AlgorithmParameterSpec spec, final String alias)
            throws InvalidAlgorithmParameterException, CertificateException, IOException,
            CryptoTokenOfflineException {
        if (StringUtils.isNotEmpty(alias)) {
            KeyStoreTools cont = new KeyStoreTools(getKeyStore(), getSignProviderName());
            cont.generateKeyPair(spec, alias);
            storeKeyStore();
        } else {
            log.debug("Trying to generate keys with empty alias.");
        }
    }

    @Override
    public boolean permitExtractablePrivateKeyForTest() {
        return true;
    }

}