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

Java tutorial

Introduction

Here is the source code for org.cesecore.keys.token.CryptoTokenManagementSessionBean.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.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Hex;
import org.cesecore.audit.enums.EventStatus;
import org.cesecore.audit.enums.EventTypes;
import org.cesecore.audit.enums.ModuleTypes;
import org.cesecore.audit.enums.ServiceTypes;
import org.cesecore.audit.log.AuditRecordStorageException;
import org.cesecore.audit.log.SecurityEventsLoggerSessionLocal;
import org.cesecore.authentication.tokens.AuthenticationToken;
import org.cesecore.authorization.AuthorizationDeniedException;
import org.cesecore.authorization.control.AccessControlSessionLocal;
import org.cesecore.authorization.control.CryptoTokenRules;
import org.cesecore.certificates.util.AlgorithmConstants;
import org.cesecore.certificates.util.AlgorithmTools;
import org.cesecore.internal.InternalResources;
import org.cesecore.jndi.JndiConstants;
import org.cesecore.keys.token.p11.exception.NoSuchSlotException;
import org.cesecore.keys.util.KeyTools;
import org.cesecore.keys.util.PublicKeyWrapper;
import org.cesecore.util.CryptoProviderTools;

/**
 * @see CryptoTokenManagementSession
 * @version $Id: CryptoTokenManagementSessionBean.java 20728 2015-02-20 14:55:55Z mikekushner $
 */
@Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "CryptoTokenManagementSessionRemote")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CryptoTokenManagementSessionBean
        implements CryptoTokenManagementSessionLocal, CryptoTokenManagementSessionRemote {

    private static final Logger log = Logger.getLogger(CryptoTokenManagementSessionBean.class);
    /** Internal localization of logs and errors */
    private static final InternalResources INTRES = InternalResources.getInstance();
    private static final Random rnd = new SecureRandom();

    @EJB
    private AccessControlSessionLocal accessControlSessionSession;
    @EJB
    private SecurityEventsLoggerSessionLocal securityEventsLoggerSession;
    @EJB
    private CryptoTokenSessionLocal cryptoTokenSession;

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public List<Integer> getCryptoTokenIds(final AuthenticationToken authenticationToken) {
        final List<Integer> allCryptoTokenIds = cryptoTokenSession.getCryptoTokenIds();
        final List<Integer> auhtorizedCryptoTokenIds = new ArrayList<Integer>();
        for (final Integer current : allCryptoTokenIds) {
            if (accessControlSessionSession.isAuthorizedNoLogging(authenticationToken,
                    CryptoTokenRules.VIEW.resource() + "/" + current.toString())) {
                auhtorizedCryptoTokenIds.add(current);
            }
        }
        return auhtorizedCryptoTokenIds;
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public CryptoToken getCryptoToken(final int cryptoTokenId) {
        return cryptoTokenSession.getCryptoToken(cryptoTokenId);
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public CryptoTokenInfo getCryptoTokenInfo(final AuthenticationToken authenticationToken,
            final int cryptoTokenId) throws AuthorizationDeniedException {
        if (!accessControlSessionSession.isAuthorized(authenticationToken,
                CryptoTokenRules.VIEW.resource() + "/" + cryptoTokenId)) {
            final String msg = INTRES.getLocalizedMessage("authorization.notuathorizedtoresource",
                    CryptoTokenRules.VIEW.resource(), authenticationToken.toString());
            throw new AuthorizationDeniedException(msg);
        }
        return getCryptoTokenInfo(cryptoTokenId);
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public List<CryptoTokenInfo> getCryptoTokenInfos(final AuthenticationToken authenticationToken) {
        final List<CryptoTokenInfo> cryptoTokenInfos = new ArrayList<CryptoTokenInfo>();
        for (final Integer cryptoTokenId : getCryptoTokenIds(authenticationToken)) {
            cryptoTokenInfos.add(getCryptoTokenInfo(cryptoTokenId));
        }
        return cryptoTokenInfos;
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public CryptoTokenInfo getCryptoTokenInfo(final int cryptoTokenId) {
        final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(cryptoTokenId);
        if (cryptoToken == null) {
            return null;
        }
        final boolean isActive = cryptoToken.getTokenStatus() == CryptoToken.STATUS_ACTIVE;
        final Properties cryptoTokenProperties = cryptoToken.getProperties();
        final boolean autoActivation = BaseCryptoToken.getAutoActivatePin(cryptoTokenProperties) != null;
        return new CryptoTokenInfo(cryptoTokenId, cryptoToken.getTokenName(), isActive, autoActivation,
                cryptoToken.getClass(), cryptoTokenProperties);
    }

    @Override
    public void createCryptoToken(final AuthenticationToken authenticationToken, final String tokenName,
            final Integer cryptoTokenId, final String className, final Properties properties, final byte[] data,
            final char[] authenticationCode) throws AuthorizationDeniedException, CryptoTokenNameInUseException,
            CryptoTokenOfflineException, CryptoTokenAuthenticationFailedException, NoSuchSlotException {
        if (log.isTraceEnabled()) {
            log.trace(">createCryptoToken: " + tokenName + ", " + className);
        }
        assertAuthorizedToModifyCryptoTokens(authenticationToken);
        if (CryptoTokenFactory.instance().getAvailableCryptoToken(className) == null) {
            throw new CryptoTokenClassNotFoundException("Invalid token class name: " + className);
        }

        // Note: if data is null, a new empty keystore will be created
        final CryptoToken cryptoToken = CryptoTokenFactory.createCryptoToken(className, properties, data,
                cryptoTokenId.intValue(), tokenName);
        if (authenticationCode != null) {
            if (log.isDebugEnabled()) {
                log.debug("Activating new crypto token using supplied authentication code.");
            }
            cryptoToken.activate(authenticationCode);
        }

        // This property is used only once during crypto token creation 
        properties.remove(CryptoToken.ALLOW_NONEXISTING_SLOT_PROPERTY);

        final Map<String, Object> details = new LinkedHashMap<String, Object>();
        details.put("msg", "Created CryptoToken with id " + cryptoTokenId);
        details.put("name", cryptoToken.getTokenName());
        details.put("encProviderName", cryptoToken.getEncProviderName());
        details.put("signProviderName", cryptoToken.getSignProviderName());
        putDelta(new Properties(), cryptoToken.getProperties(), details);
        cryptoTokenSession.mergeCryptoToken(cryptoToken);
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_CREATE, EventStatus.SUCCESS, ModuleTypes.CRYPTOTOKEN,
                ServiceTypes.CORE, authenticationToken.toString(), String.valueOf(cryptoTokenId), null, null,
                details);
        if (log.isTraceEnabled()) {
            log.trace("<createCryptoToken: " + tokenName + ", " + className);
        }
    }

    @Override
    public int createCryptoToken(final AuthenticationToken authenticationToken, final String tokenName,
            final String className, final Properties properties, final byte[] data, final char[] authenticationCode)
            throws AuthorizationDeniedException, CryptoTokenOfflineException,
            CryptoTokenAuthenticationFailedException, CryptoTokenNameInUseException, NoSuchSlotException,
            AuditRecordStorageException {
        final List<Integer> allCryptoTokenIds = cryptoTokenSession.getCryptoTokenIds();
        Integer cryptoTokenId = null;
        for (int i = 0; i < 100; i++) {
            final int current = Integer.valueOf(rnd.nextInt());
            if (!allCryptoTokenIds.contains(current)) {
                cryptoTokenId = current;
                break;
            }
        }
        if (cryptoTokenId == null) {
            throw new RuntimeException("Failed to allocate a new cryptoTokenId.");
        }
        createCryptoToken(authenticationToken, tokenName, cryptoTokenId, className, properties, data,
                authenticationCode);
        return cryptoTokenId.intValue();
    }

    /**
     * Asserts if an authentication token is authorized to modify crypto tokens
     * 
     * @param authenticationToken the authentication token to check
     * @throws AuthorizationDeniedException thrown if authorization was denied.
     */
    private void assertAuthorizedToModifyCryptoTokens(AuthenticationToken authenticationToken)
            throws AuthorizationDeniedException {
        if (!accessControlSessionSession.isAuthorized(authenticationToken,
                CryptoTokenRules.MODIFY_CRYPTOTOKEN.resource())) {
            final String msg = INTRES.getLocalizedMessage("authorization.notuathorizedtoresource",
                    CryptoTokenRules.MODIFY_CRYPTOTOKEN.resource(), authenticationToken.toString());
            throw new AuthorizationDeniedException(msg);
        }
    }

    @Override
    public void saveCryptoToken(AuthenticationToken authenticationToken, int cryptoTokenId, String tokenName,
            Properties properties, char[] authenticationCode)
            throws AuthorizationDeniedException, CryptoTokenOfflineException,
            CryptoTokenAuthenticationFailedException, CryptoTokenNameInUseException, NoSuchSlotException {
        if (log.isTraceEnabled()) {
            log.trace(">saveCryptoToken: " + tokenName + ", " + cryptoTokenId);
        }
        // Note that an admin that is authorized to modify a token could gain access to another HSM slot etc..
        if (!accessControlSessionSession.isAuthorized(authenticationToken,
                CryptoTokenRules.MODIFY_CRYPTOTOKEN.resource())) {
            final String msg = INTRES.getLocalizedMessage("authorization.notuathorizedtoresource",
                    CryptoTokenRules.MODIFY_CRYPTOTOKEN.resource(), authenticationToken.toString());
            throw new AuthorizationDeniedException(msg);
        }
        final CryptoToken currentCryptoToken = cryptoTokenSession.getCryptoToken(cryptoTokenId);
        final String className = currentCryptoToken.getClass().getName();
        final byte[] tokendata = currentCryptoToken.getTokenData();
        // Handle presence of auto-activation indicators
        boolean keepAutoActivateIfPresent = Boolean
                .valueOf(String.valueOf(properties.get(CryptoTokenManagementSession.KEEP_AUTO_ACTIVATION_PIN)));
        properties.remove(CryptoTokenManagementSession.KEEP_AUTO_ACTIVATION_PIN);
        final String newPin = BaseCryptoToken.getAutoActivatePin(properties);
        if (newPin != null) {
            BaseCryptoToken.setAutoActivatePin(properties, newPin, true);
            authenticationCode = newPin.toCharArray();
        } else if (keepAutoActivateIfPresent) {
            final String currentPin = BaseCryptoToken.getAutoActivatePin(currentCryptoToken.getProperties());
            if (currentPin != null) {
                BaseCryptoToken.setAutoActivatePin(properties, currentPin, true);
                authenticationCode = null; // We have an auto-activation pin and it didn't change;
            }
        } else if (authenticationCode == null || authenticationCode.length == 0) {
            // Check if the token was auto-activated before. it is now manually activated, so use the auto-activation code one last time
            final String currentPin = BaseCryptoToken.getAutoActivatePin(currentCryptoToken.getProperties());
            if (currentPin != null) {
                authenticationCode = currentPin.toCharArray();
            }
        }
        // TODO: If the current token is active we would like to dig out the code used to activate it and activate the new one as well..
        // For SoftCryptoTokens, a new secret means that we should change it and it can only be done if the token is active
        final CryptoToken newCryptoToken = CryptoTokenFactory.createCryptoToken(className, properties, tokendata,
                cryptoTokenId, tokenName);
        // If a new authenticationCode is provided we should verify it before we go ahead and merge
        if (authenticationCode != null && authenticationCode.length > 0) {
            newCryptoToken.deactivate();
            newCryptoToken.activate(authenticationCode);
        }
        final Map<String, Object> details = new LinkedHashMap<String, Object>();
        details.put("msg", "Modified CryptoToken with id " + cryptoTokenId);
        putDelta("name", currentCryptoToken.getTokenName(), newCryptoToken.getTokenName(), details);
        putDelta("encProviderName", currentCryptoToken.getEncProviderName(), newCryptoToken.getEncProviderName(),
                details);
        putDelta("signProviderName", currentCryptoToken.getSignProviderName(), newCryptoToken.getSignProviderName(),
                details);
        putDelta(currentCryptoToken.getProperties(), newCryptoToken.getProperties(), details);
        cryptoTokenSession.mergeCryptoToken(newCryptoToken);
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_EDIT, EventStatus.SUCCESS, ModuleTypes.CRYPTOTOKEN,
                ServiceTypes.CORE, authenticationToken.toString(), String.valueOf(cryptoTokenId), null, null,
                details);
        if (log.isTraceEnabled()) {
            log.trace("<saveCryptoToken: " + tokenName + ", " + cryptoTokenId);
        }
    }

    // Only removes reference
    @Override
    public void deleteCryptoToken(final AuthenticationToken authenticationToken, final int cryptoTokenId)
            throws AuthorizationDeniedException {
        if (!accessControlSessionSession.isAuthorized(authenticationToken,
                CryptoTokenRules.DELETE_CRYPTOTOKEN.resource() + "/" + cryptoTokenId)) {
            throw new AuthorizationDeniedException();
        }
        if (cryptoTokenSession.removeCryptoToken(cryptoTokenId)) {
            securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_DELETION, EventStatus.SUCCESS,
                    ModuleTypes.CRYPTOTOKEN, ServiceTypes.CORE, authenticationToken.toString(),
                    String.valueOf(cryptoTokenId), null, null, "Deleted CryptoToken with id " + cryptoTokenId);
        } else if (log.isDebugEnabled()) {
            log.debug("Crypto token with id " + cryptoTokenId + " does not exist and can not be deleted.");
        }
    }

    @Override
    public boolean isCryptoTokenStatusActive(AuthenticationToken authenticationToken, int cryptoTokenId)
            throws AuthorizationDeniedException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.VIEW.resource() + "/" + cryptoTokenId);
        return isCryptoTokenStatusActive(cryptoTokenId);
    }

    @Override
    public boolean isCryptoTokenStatusActive(int cryptoTokenId) {
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        return cryptoToken.getTokenStatus() == CryptoToken.STATUS_ACTIVE;
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public void activate(final AuthenticationToken authenticationToken, final int cryptoTokenId,
            final char[] authenticationCode) throws AuthorizationDeniedException, CryptoTokenOfflineException,
            CryptoTokenAuthenticationFailedException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.ACTIVATE.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        cryptoToken.activate(authenticationCode);
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_ACTIVATION, EventStatus.SUCCESS,
                ModuleTypes.CRYPTOTOKEN, ServiceTypes.CORE, authenticationToken.toString(),
                String.valueOf(cryptoTokenId), null, null,
                "Activated CryptoToken '" + cryptoToken.getTokenName() + "' with id " + cryptoTokenId);
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public void deactivate(final AuthenticationToken authenticationToken, final int cryptoTokenId)
            throws AuthorizationDeniedException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.DEACTIVATE.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        cryptoToken.deactivate();
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_DEACTIVATION, EventStatus.SUCCESS,
                ModuleTypes.CRYPTOTOKEN, ServiceTypes.CORE, authenticationToken.toString(),
                String.valueOf(cryptoTokenId), null, null,
                "Deactivated CryptoToken '" + cryptoToken.getTokenName() + "' with id " + cryptoTokenId);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public boolean updatePin(AuthenticationToken authenticationToken, Integer cryptoTokenId,
            char[] currentAuthenticationCode, char[] newAuthenticationCode, boolean updateOnly)
            throws AuthorizationDeniedException, CryptoTokenAuthenticationFailedException,
            CryptoTokenOfflineException {
        final String[] requiredAuthorization = new String[] {
                CryptoTokenRules.MODIFY_CRYPTOTOKEN.resource() + "/" + cryptoTokenId,
                CryptoTokenRules.ACTIVATE.resource() + "/" + cryptoTokenId,
                CryptoTokenRules.DEACTIVATE.resource() + "/" + cryptoTokenId };
        if (!accessControlSessionSession.isAuthorized(authenticationToken, requiredAuthorization)) {
            final String msg = INTRES.getLocalizedMessage("authorization.notuathorizedtoresource",
                    Arrays.toString(requiredAuthorization), authenticationToken.toString());
            throw new AuthorizationDeniedException(msg);
        }
        CryptoToken cryptoToken = getCryptoToken(cryptoTokenId);
        final Properties cryptoTokenProperties = cryptoToken.getProperties();
        // Get current auto-activation pin (if any)
        final String oldAutoActivationPin = BaseCryptoToken.getAutoActivatePin(cryptoTokenProperties);
        if (oldAutoActivationPin == null && (updateOnly || newAuthenticationCode == null)) {
            // This is a NOOP call that will not lead to any change
            return false;
        }
        if (SoftCryptoToken.class.getName().equals(cryptoToken.getClass().getName())) {
            CryptoProviderTools.installBCProviderIfNotAvailable();
            final KeyStore keystore;
            try {
                keystore = KeyStore.getInstance("PKCS12", "BC");
                keystore.load(new ByteArrayInputStream(cryptoToken.getTokenData()), currentAuthenticationCode);
            } catch (Exception e) {
                final String msg = "Failed to use supplied current PIN." + " " + e;
                log.info(msg);
                throw new CryptoTokenAuthenticationFailedException(msg);
            }
            if (newAuthenticationCode == null) {
                // When no new pin is supplied, we will not modify the key-store and just remove the current auto-activation pin
                cryptoTokenProperties.remove(CryptoToken.AUTOACTIVATE_PIN_PROPERTY);
                cryptoToken.setProperties(cryptoTokenProperties);
            } else {
                try {
                    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    keystore.store(baos, newAuthenticationCode);
                    baos.close();
                    if (oldAutoActivationPin != null || !updateOnly) {
                        BaseCryptoToken.setAutoActivatePin(cryptoTokenProperties, new String(newAuthenticationCode),
                                true);
                    } else {
                        log.debug(
                                "Auto-activation will not be used. Only changing pin for soft CryptoToken keystore.");
                    }
                    cryptoToken = CryptoTokenFactory.createCryptoToken(SoftCryptoToken.class.getName(),
                            cryptoTokenProperties, baos.toByteArray(), cryptoTokenId, cryptoToken.getTokenName());
                } catch (Exception e) {
                    log.info("Unable to store soft keystore with new PIN: " + e);
                    throw new CryptoTokenAuthenticationFailedException(
                            "Unable to store soft keystore with new PIN");
                }
            }
        } else {
            if (oldAutoActivationPin != null) {
                // If we have an old auto-activation pin we will compare the "current" with this value to avoid deactivating the token
                if (!oldAutoActivationPin.equals(new String(currentAuthenticationCode))) {
                    final String msg = "Supplied PIN did not match auto-activation PIN.";
                    log.info(msg);
                    throw new CryptoTokenAuthenticationFailedException(msg);
                } else {
                    log.debug(
                            "Successfully verified the PIN for non-soft CryptoToken by comparing supplied PIN to auto-activation PIN.");
                }
            } else {
                // If we don't have an auto-activation pin to compare the supplied PIN to, we need to verify the supplied
                // PIN can be used in a de-activation/activation cycle.
                final boolean wasInactive = !isCryptoTokenStatusActive(authenticationToken, cryptoTokenId);
                cryptoToken.deactivate();
                cryptoToken.activate(currentAuthenticationCode);
                if (wasInactive) {
                    // Note that there is a small glitch here where the token was active, but we have no other options to verify the pin
                    cryptoToken.deactivate();
                }
            }
            if (newAuthenticationCode == null) {
                cryptoTokenProperties.remove(CryptoToken.AUTOACTIVATE_PIN_PROPERTY);
            } else {
                BaseCryptoToken.setAutoActivatePin(cryptoTokenProperties, new String(newAuthenticationCode), true);
            }
            cryptoToken.setProperties(cryptoTokenProperties);
        }
        // Save the modified CryptoToken
        try {
            cryptoTokenSession.mergeCryptoToken(cryptoToken);
        } catch (CryptoTokenNameInUseException e) {
            // This should not happen here since we use the same name and id
            throw new RuntimeException(e);
        }
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_UPDATEPIN, EventStatus.SUCCESS,
                ModuleTypes.CRYPTOTOKEN, ServiceTypes.CORE, authenticationToken.toString(),
                String.valueOf(cryptoTokenId), null, null,
                "Updated PIN of CryptoToken '" + cryptoToken.getTokenName() + "' with id " + cryptoTokenId);
        // Return the current auto-activation state
        return BaseCryptoToken.getAutoActivatePin(cryptoTokenProperties) != null;
    }

    @Override
    public List<KeyPairInfo> getKeyPairInfos(final AuthenticationToken authenticationToken, final int cryptoTokenId)
            throws CryptoTokenOfflineException, AuthorizationDeniedException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.VIEW.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        final List<KeyPairInfo> ret = new ArrayList<KeyPairInfo>();
        for (final String alias : getKeyPairAliasesInternal(cryptoToken)) {
            final PublicKey publicKey = cryptoToken.getPublicKey(alias);
            final String keyAlgorithm = AlgorithmTools.getKeyAlgorithm(publicKey);
            final String keySpecification = AlgorithmTools.getKeySpecification(publicKey);
            final String subjectKeyId = new String(
                    Hex.encode(KeyTools.createSubjectKeyId(publicKey).getKeyIdentifier()));
            ret.add(new KeyPairInfo(alias, keyAlgorithm, keySpecification, subjectKeyId));
        }
        return ret;
    }

    @Override
    public KeyPairInfo getKeyPairInfo(AuthenticationToken authenticationToken, int cryptoTokenId, String alias)
            throws CryptoTokenOfflineException, AuthorizationDeniedException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.VIEW.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        if (!getKeyPairAliasesInternal(cryptoToken).contains(alias)) {
            return null;
        }
        final PublicKey publicKey = cryptoToken.getPublicKey(alias);
        final String keyAlgorithm = AlgorithmTools.getKeyAlgorithm(publicKey);
        final String keySpecification = AlgorithmTools.getKeySpecification(publicKey);
        final String subjectKeyId = new String(
                Hex.encode(KeyTools.createSubjectKeyId(publicKey).getKeyIdentifier()));
        return new KeyPairInfo(alias, keyAlgorithm, keySpecification, subjectKeyId);
    }

    @Override
    public PublicKeyWrapper getPublicKey(AuthenticationToken authenticationToken, int cryptoTokenId, String alias)
            throws AuthorizationDeniedException, CryptoTokenOfflineException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.VIEW.resource() + "/" + cryptoTokenId);
        return new PublicKeyWrapper(getCryptoTokenAndAssertExistence(cryptoTokenId).getPublicKey(alias));
    }

    @Override
    public Integer getIdFromName(final String cryptoTokenName) {
        if (cryptoTokenName == null) {
            return null;
        }
        final Map<String, Integer> cachedNameToIdMap = cryptoTokenSession.getCachedNameToIdMap();
        Integer cryptoTokenId = cachedNameToIdMap.get(cryptoTokenName);
        if (cryptoTokenId == null) {
            // Ok.. so it's not in the cache.. look for it the hard way..
            for (final Integer currentCryptoTokenId : cryptoTokenSession.getCryptoTokenIds()) {
                // Don't lookup CryptoTokens we already have in the id to name cache
                if (!cachedNameToIdMap.keySet().contains(currentCryptoTokenId)) {
                    final CryptoToken currentCryptoToken = cryptoTokenSession
                            .getCryptoToken(currentCryptoTokenId.intValue());
                    final String currentCryptoTokenName = currentCryptoToken == null ? null
                            : currentCryptoToken.getTokenName();
                    if (cryptoTokenName.equals(currentCryptoTokenName)) {
                        cryptoTokenId = currentCryptoTokenId;
                        break;
                    }
                }
            }
        }
        return cryptoTokenId;
    }

    @Override
    public List<String> getKeyPairAliases(final AuthenticationToken authenticationToken, final int cryptoTokenId)
            throws AuthorizationDeniedException, CryptoTokenOfflineException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.VIEW.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        return getKeyPairAliasesInternal(cryptoToken);
    }

    private List<String> getKeyPairAliasesInternal(final CryptoToken cryptoToken)
            throws CryptoTokenOfflineException {
        try {
            final List<String> aliasEnumeration = cryptoToken.getAliases();
            final List<String> keyPairAliases = new ArrayList<String>();
            for (final String currentAlias : aliasEnumeration) {
                try {
                    if (cryptoToken.getPublicKey(currentAlias) != null
                            && cryptoToken.getPrivateKey(currentAlias) != null) {
                        // A key pair exists for this alias, so add it
                        keyPairAliases.add(currentAlias);
                    }
                } catch (CryptoTokenOfflineException ignored) {
                    if (log.isDebugEnabled()) {
                        log.debug("Ignord key alias '" + currentAlias + "' in crypto token '"
                                + cryptoToken.getTokenName()
                                + "' since it is missing a public and/or private key. Perhaps it is a symmetric key?");
                    }
                }
            }
            return keyPairAliases;
        } catch (KeyStoreException e) {
            throw new CryptoTokenOfflineException(e);
        }
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public void createKeyPair(final AuthenticationToken authenticationToken, final int cryptoTokenId,
            final String alias, final String keySpecificationParam) throws AuthorizationDeniedException,
            CryptoTokenOfflineException, InvalidKeyException, InvalidAlgorithmParameterException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.GENERATE_KEYS.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        // Check if alias is already in use
        assertAliasNotInUse(cryptoToken, alias);

        // Support "RSAnnnn" and convert it to the legacy format "nnnn"
        final String keySpecification;
        if (keySpecificationParam.startsWith(AlgorithmConstants.KEYALGORITHM_RSA)) {
            keySpecification = keySpecificationParam.substring(AlgorithmConstants.KEYALGORITHM_RSA.length());
        } else {
            keySpecification = keySpecificationParam;
        }
        // Check if keySpec is valid
        KeyTools.checkValidKeyLength(keySpecification);
        // Audit log before generation. If the token is an HSM the merge will not make a difference.
        final Map<String, Object> details = new LinkedHashMap<String, Object>();
        details.put("msg", "Generated new keypair in CryptoToken " + cryptoTokenId);
        details.put("keyAlias", alias);
        details.put("keySpecification", keySpecification);
        cryptoToken.generateKeyPair(keySpecification, alias);
        cryptoToken.testKeyPair(alias);
        // Merge is important for soft tokens where the data is persisted in the database, but will also update lastUpdate
        try {
            cryptoTokenSession.mergeCryptoToken(cryptoToken);
        } catch (CryptoTokenNameInUseException e) {
            throw new RuntimeException(e); // We have not changed the name of the CrytpoToken here, so this should never happen
        }
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_GEN_KEYPAIR, EventStatus.SUCCESS,
                ModuleTypes.CRYPTOTOKEN, ServiceTypes.CORE, authenticationToken.toString(),
                String.valueOf(cryptoTokenId), null, null, details);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public void createKeyPairWithSameKeySpec(final AuthenticationToken authenticationToken, final int cryptoTokenId,
            final String currentAlias, final String newAlias) throws AuthorizationDeniedException,
            CryptoTokenOfflineException, InvalidKeyException, InvalidAlgorithmParameterException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.GENERATE_KEYS.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        assertAliasNotInUse(cryptoToken, newAlias);
        final PublicKey publicKey = cryptoToken.getPublicKey(currentAlias);
        final String keyAlgorithm = AlgorithmTools.getKeyAlgorithm(publicKey);
        final String keySpecification;
        if (AlgorithmConstants.KEYALGORITHM_DSA.equals(keyAlgorithm)) {
            keySpecification = AlgorithmConstants.KEYALGORITHM_DSA + AlgorithmTools.getKeySpecification(publicKey);
        } else {
            keySpecification = AlgorithmTools.getKeySpecification(publicKey);
        }
        KeyTools.checkValidKeyLength(keySpecification);
        final Map<String, Object> details = new LinkedHashMap<String, Object>();
        details.put("msg", "Generated new keypair in CryptoToken " + cryptoTokenId);
        details.put("keyAlias", newAlias);
        details.put("keySpecification", keySpecification);
        cryptoToken.generateKeyPair(keySpecification, newAlias);
        cryptoToken.testKeyPair(newAlias);
        try {
            cryptoTokenSession.mergeCryptoToken(cryptoToken);
        } catch (CryptoTokenNameInUseException e) {
            throw new RuntimeException(e); // We have not changed the name of the CrytpoToken here, so this should never happen
        }
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_GEN_KEYPAIR, EventStatus.SUCCESS,
                ModuleTypes.CRYPTOTOKEN, ServiceTypes.CORE, authenticationToken.toString(),
                String.valueOf(cryptoTokenId), null, null, details);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public void createKeyPairFromTemplate(AuthenticationToken authenticationToken, int cryptoTokenId, String alias,
            String keySpecification) throws AuthorizationDeniedException, CryptoTokenOfflineException,
            InvalidKeyException, InvalidAlgorithmParameterException {
        createKeyPair(authenticationToken, cryptoTokenId, alias, keySpecification);
        removeKeyPairPlaceholder(authenticationToken, cryptoTokenId, alias);
    }

    @Override
    public boolean isAliasUsedInCryptoToken(final int cryptoTokenId, final String alias) {
        return getCryptoToken(cryptoTokenId).isAliasUsed(alias);
    }

    /** @throws InvalidKeyException if the alias is in use by a private, public or symmetric key */
    private void assertAliasNotInUse(final CryptoToken cryptoToken, final String alias) throws InvalidKeyException {
        if (cryptoToken.isAliasUsed(alias)) {
            throw new InvalidKeyException("alias " + alias + " is in use");
        }
    }

    @Override
    public void removeKeyPair(final AuthenticationToken authenticationToken, final int cryptoTokenId,
            final String alias)
            throws AuthorizationDeniedException, CryptoTokenOfflineException, InvalidKeyException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.REMOVE_KEYS.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        // Check if alias is in use
        if (!cryptoToken.isAliasUsed(alias)) {
            throw new InvalidKeyException("Alias " + alias + " is not in use");
        }
        final Map<String, Object> details = new LinkedHashMap<String, Object>();
        details.put("msg", "Deleted key pair from CryptoToken " + cryptoTokenId);
        details.put("keyAlias", alias);
        try {
            cryptoToken.deleteEntry(alias);
        } catch (KeyStoreException e) {
            throw new InvalidKeyException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new InvalidKeyException(e);
        } catch (CertificateException e) {
            throw new InvalidKeyException(e);
        } catch (IOException e) {
            throw new InvalidKeyException(e);
        }
        assertAliasNotInUse(cryptoToken, alias);
        log.debug("cryptoTokenSession.mergeCryptoToken");
        // Merge is important for soft tokens where the data is persisted in the database, but will also update lastUpdate
        try {
            cryptoTokenSession.mergeCryptoToken(cryptoToken);
        } catch (CryptoTokenNameInUseException e) {
            throw new IllegalStateException(e); // We have not changed the name of the CrytpoToken here, so this should never happen
        }
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_DELETE_ENTRY, EventStatus.SUCCESS,
                ModuleTypes.CRYPTOTOKEN, ServiceTypes.CORE, authenticationToken.toString(),
                String.valueOf(cryptoTokenId), null, null, details);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    @Override
    public void removeKeyPairPlaceholder(final AuthenticationToken authenticationToken, final int cryptoTokenId,
            final String alias) throws AuthorizationDeniedException, InvalidKeyException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.REMOVE_KEYS.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);

        boolean removed = false;
        final Properties props = new Properties();
        props.putAll(cryptoToken.getProperties());
        final String placeholdersString = props.getProperty(CryptoToken.KEYPLACEHOLDERS_PROPERTY, "");
        final List<String> entries = new ArrayList<String>(
                Arrays.asList(placeholdersString.split("[" + CryptoToken.KEYPLACEHOLDERS_OUTER_SEPARATOR + "]")));
        final Iterator<String> iter = entries.iterator();
        while (iter.hasNext()) {
            final String entry = iter.next();
            if (entry.startsWith(alias + CryptoToken.KEYPLACEHOLDERS_INNER_SEPARATOR)) {
                iter.remove();
                removed = true;
            }
        }

        if (removed) {
            final String newValue = StringUtils.join(entries, CryptoToken.KEYPLACEHOLDERS_OUTER_SEPARATOR);
            props.setProperty(CryptoToken.KEYPLACEHOLDERS_PROPERTY, newValue);
            cryptoToken.setProperties(props);
        }

        // Check if alias is in use
        if (!removed) {
            throw new InvalidKeyException("Alias " + alias + " is not in use");
        }

        try {
            cryptoTokenSession.mergeCryptoToken(cryptoToken);
        } catch (CryptoTokenNameInUseException e) {
            throw new IllegalStateException(e); // We have not changed the name of the CrytpoToken here, so this should never happen
        }

        final Map<String, Object> details = new LinkedHashMap<String, Object>();
        details.put("msg", "Deleted key pair placeholder from CryptoToken " + cryptoTokenId);
        details.put("keyAlias", alias);
        securityEventsLoggerSession.log(EventTypes.CRYPTOTOKEN_DELETE_ENTRY, EventStatus.SUCCESS,
                ModuleTypes.CRYPTOTOKEN, ServiceTypes.CORE, authenticationToken.toString(),
                String.valueOf(cryptoTokenId), null, null, details);
    }

    @Override
    public void testKeyPair(final AuthenticationToken authenticationToken, final int cryptoTokenId,
            final String alias)
            throws AuthorizationDeniedException, CryptoTokenOfflineException, InvalidKeyException {
        assertAuthorization(authenticationToken, cryptoTokenId,
                CryptoTokenRules.TEST_KEYS.resource() + "/" + cryptoTokenId);
        final CryptoToken cryptoToken = getCryptoTokenAndAssertExistence(cryptoTokenId);
        cryptoToken.testKeyPair(alias);
    }

    /** @return a CryptoToken for the requested Id of authorized and it exists. Never returns null. */
    private void assertAuthorization(final AuthenticationToken authenticationToken, final int cryptoTokenId,
            final String resource) throws AuthorizationDeniedException {
        if (!accessControlSessionSession.isAuthorized(authenticationToken, resource)) {
            final String msg = INTRES.getLocalizedMessage("authorization.notuathorizedtoresource", resource,
                    authenticationToken.toString());
            throw new AuthorizationDeniedException(msg);
        }

    }

    private CryptoToken getCryptoTokenAndAssertExistence(int cryptoTokenId) {
        final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(cryptoTokenId);
        if (cryptoToken == null) {
            throw new RuntimeException("No such CryptoToken for id " + cryptoTokenId);
        }
        return cryptoToken;
    }

    /** Helper method for audit logging changes */
    private void putDelta(Properties oldProperties, Properties newProperties, Map<String, Object> details) {
        // Find out what has happended to all the old properties
        for (final Object key : oldProperties.keySet()) {
            final String oldValue = oldProperties.getProperty(String.valueOf(key));
            final String newValue = newProperties.getProperty(String.valueOf(key));
            putDelta(String.valueOf(key), oldValue, newValue, details);
        }
        // Find out which new properties that did not exist in the old
        for (final Object key : newProperties.keySet()) {
            final String oldValue = oldProperties.getProperty(String.valueOf(key));
            if (oldValue == null) {
                final String newValue = newProperties.getProperty(String.valueOf(key));
                putDelta(String.valueOf(key), oldValue, newValue, details);
            }
        }
    }

    /** Helper method for audit logging changes */
    private void putDelta(String key, String oldValue, String newValue, Map<String, Object> details) {
        // Treat the auto-activation pin with care
        if (BaseCryptoToken.AUTOACTIVATE_PIN_PROPERTY.equals(key)) {
            if (oldValue == null && newValue == null) {
                // NOP
            } else if (oldValue == null && newValue != null) {
                details.put("autoActivation", "added");
            } else if (oldValue != null && newValue == null) {
                details.put("autoActivation", "removed");
            } else if (!oldValue.equals(newValue)) {
                details.put("autoActivation", "pin changed");
            }
        } else {
            if (oldValue == null && newValue == null) {
                // NOP
            } else if (oldValue == null && newValue != null) {
                details.put("added:" + key, newValue);
            } else if (oldValue != null && newValue == null) {
                details.put("removed:" + key, oldValue);
            } else if (!oldValue.equals(newValue)) {
                details.put("changed:" + key, newValue);
            } else {
                details.put(key, newValue);
            }
        }
    }
}