org.ejbca.core.ejb.ca.caadmin.CAAdminSessionBean.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.core.ejb.ca.caadmin.CAAdminSessionBean.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA Community: 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.ejb.ca.caadmin;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
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 java.util.Set;
import java.util.TimeZone;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.EJBException;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.util.encoders.Hex;
import org.cesecore.CesecoreException;
import org.cesecore.ErrorCode;
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.SecurityEventsLoggerSessionLocal;
import org.cesecore.authentication.tokens.AlwaysAllowLocalAuthenticationToken;
import org.cesecore.authentication.tokens.AuthenticationToken;
import org.cesecore.authentication.tokens.UsernamePrincipal;
import org.cesecore.authorization.AuthorizationDeniedException;
import org.cesecore.authorization.control.AccessControlSessionLocal;
import org.cesecore.authorization.control.StandardRules;
import org.cesecore.authorization.rules.AccessRuleData;
import org.cesecore.authorization.user.AccessUserAspectData;
import org.cesecore.certificates.ca.CA;
import org.cesecore.certificates.ca.CAConstants;
import org.cesecore.certificates.ca.CAData;
import org.cesecore.certificates.ca.CADoesntExistsException;
import org.cesecore.certificates.ca.CAExistsException;
import org.cesecore.certificates.ca.CAInfo;
import org.cesecore.certificates.ca.CAOfflineException;
import org.cesecore.certificates.ca.CVCCAInfo;
import org.cesecore.certificates.ca.CaSessionLocal;
import org.cesecore.certificates.ca.CvcCA;
import org.cesecore.certificates.ca.InvalidAlgorithmException;
import org.cesecore.certificates.ca.X509CA;
import org.cesecore.certificates.ca.X509CAInfo;
import org.cesecore.certificates.ca.catoken.CAToken;
import org.cesecore.certificates.ca.catoken.CATokenConstants;
import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceInfo;
import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceNotActiveException;
import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceRequest;
import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceRequestException;
import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceResponse;
import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceTypes;
import org.cesecore.certificates.ca.extendedservices.IllegalExtendedCAServiceRequestException;
import org.cesecore.certificates.certificate.CertificateConstants;
import org.cesecore.certificates.certificate.CertificateInfo;
import org.cesecore.certificates.certificate.CertificateRevokeException;
import org.cesecore.certificates.certificate.CertificateStoreSessionLocal;
import org.cesecore.certificates.certificate.request.CertificateResponseMessage;
import org.cesecore.certificates.certificate.request.PKCS10RequestMessage;
import org.cesecore.certificates.certificate.request.RequestMessage;
import org.cesecore.certificates.certificate.request.ResponseMessage;
import org.cesecore.certificates.certificate.request.X509ResponseMessage;
import org.cesecore.certificates.certificateprofile.CertificatePolicy;
import org.cesecore.certificates.certificateprofile.CertificateProfile;
import org.cesecore.certificates.certificateprofile.CertificateProfileConstants;
import org.cesecore.certificates.certificateprofile.CertificateProfileSessionLocal;
import org.cesecore.certificates.crl.CrlStoreSessionLocal;
import org.cesecore.certificates.crl.RevokedCertInfo;
import org.cesecore.certificates.endentity.EndEntityInformation;
import org.cesecore.certificates.endentity.EndEntityType;
import org.cesecore.certificates.endentity.EndEntityTypes;
import org.cesecore.certificates.endentity.ExtendedInformation;
import org.cesecore.certificates.ocsp.exception.NotSupportedException;
import org.cesecore.certificates.util.AlgorithmConstants;
import org.cesecore.certificates.util.AlgorithmTools;
import org.cesecore.configuration.GlobalConfigurationSessionLocal;
import org.cesecore.jndi.JndiConstants;
import org.cesecore.keybind.CertificateImportException;
import org.cesecore.keybind.InternalKeyBinding;
import org.cesecore.keybind.InternalKeyBindingMgmtSessionLocal;
import org.cesecore.keybind.InternalKeyBindingNameInUseException;
import org.cesecore.keybind.InternalKeyBindingProperty;
import org.cesecore.keybind.InternalKeyBindingTrustEntry;
import org.cesecore.keys.token.CryptoToken;
import org.cesecore.keys.token.CryptoTokenAuthenticationFailedException;
import org.cesecore.keys.token.CryptoTokenManagementSessionLocal;
import org.cesecore.keys.token.CryptoTokenNameInUseException;
import org.cesecore.keys.token.CryptoTokenOfflineException;
import org.cesecore.keys.token.CryptoTokenSessionLocal;
import org.cesecore.keys.token.IllegalCryptoTokenException;
import org.cesecore.keys.token.NullCryptoToken;
import org.cesecore.keys.token.PKCS11CryptoToken;
import org.cesecore.keys.token.SoftCryptoToken;
import org.cesecore.keys.token.p11.exception.NoSuchSlotException;
import org.cesecore.keys.util.KeyTools;
import org.cesecore.roles.RoleData;
import org.cesecore.roles.RoleExistsException;
import org.cesecore.roles.RoleNotFoundException;
import org.cesecore.roles.management.RoleManagementSessionLocal;
import org.cesecore.util.Base64;
import org.cesecore.util.CertTools;
import org.cesecore.util.CryptoProviderTools;
import org.cesecore.util.StringTools;
import org.cesecore.util.ValidityDate;
import org.ejbca.config.CmpConfiguration;
import org.ejbca.config.EjbcaConfiguration;
import org.ejbca.config.GlobalConfiguration;
import org.ejbca.core.EjbcaException;
import org.ejbca.core.ejb.approval.ApprovalSessionLocal;
import org.ejbca.core.ejb.audit.enums.EjbcaEventTypes;
import org.ejbca.core.ejb.audit.enums.EjbcaServiceTypes;
import org.ejbca.core.ejb.ca.publisher.PublisherSessionLocal;
import org.ejbca.core.ejb.ca.revoke.RevocationSessionLocal;
import org.ejbca.core.ejb.crl.PublishingCrlSessionLocal;
import org.ejbca.core.ejb.ra.EndEntityManagementSessionLocal;
import org.ejbca.core.ejb.ra.NoSuchEndEntityException;
import org.ejbca.core.ejb.ra.raadmin.EndEntityProfileSessionLocal;
import org.ejbca.core.ejb.ra.userdatasource.UserDataSourceSessionLocal;
import org.ejbca.core.ejb.services.ServiceSessionLocal;
import org.ejbca.core.model.InternalEjbcaResources;
import org.ejbca.core.model.approval.ApprovalDataVO;
import org.ejbca.core.model.approval.ApprovalException;
import org.ejbca.core.model.approval.ApprovalExecutorUtil;
import org.ejbca.core.model.approval.ApprovalOveradableClassName;
import org.ejbca.core.model.approval.WaitingForApprovalException;
import org.ejbca.core.model.approval.approvalrequests.ActivateCATokenApprovalRequest;
import org.ejbca.core.model.authorization.AccessRulesConstants;
import org.ejbca.core.model.ca.caadmin.extendedcaservices.BaseSigningCAServiceInfo;
import org.ejbca.core.model.ca.caadmin.extendedcaservices.CmsCAServiceInfo;
import org.ejbca.core.model.ca.caadmin.extendedcaservices.XKMSCAServiceInfo;
import org.ejbca.core.model.ca.publisher.BasePublisher;
import org.ejbca.core.model.ca.publisher.CustomPublisherContainer;
import org.ejbca.core.model.ra.ExtendedInformationFields;
import org.ejbca.core.model.ra.raadmin.EndEntityProfile;
import org.ejbca.core.model.ra.raadmin.EndEntityProfileNotFoundException;
import org.ejbca.core.model.ra.userdatasource.BaseUserDataSource;
import org.ejbca.core.model.services.BaseWorker;
import org.ejbca.core.model.services.ServiceConfiguration;
import org.ejbca.cvc.CardVerifiableCertificate;

/**
 * Administrates and manages CAs in EJBCA system.
 * 
 * @version $Id: CAAdminSessionBean.java 21141 2015-04-27 11:27:26Z mikekushner $
 */
@Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "CAAdminSessionRemote")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CAAdminSessionBean implements CAAdminSessionLocal, CAAdminSessionRemote {

    private static final Logger log = Logger.getLogger(CAAdminSessionBean.class);

    @PersistenceContext(unitName = "ejbca")
    private EntityManager entityManager;

    @EJB
    private AccessControlSessionLocal accessSession;
    @EJB
    private ApprovalSessionLocal approvalSession;
    @EJB
    private CaSessionLocal caSession;
    @EJB
    private CertificateProfileSessionLocal certificateProfileSession;
    @EJB
    private CertificateStoreSessionLocal certificateStoreSession;
    @EJB
    private CrlStoreSessionLocal crlStoreSession;
    @EJB
    private CryptoTokenManagementSessionLocal cryptoTokenManagementSession;
    @EJB
    private CryptoTokenSessionLocal cryptoTokenSession;
    @EJB
    private EndEntityProfileSessionLocal endEntityProfileSession;
    @EJB
    private GlobalConfigurationSessionLocal globalConfigurationSession;
    @EJB
    private InternalKeyBindingMgmtSessionLocal keyBindMgmtSession;
    @EJB
    private PublisherSessionLocal publisherSession;
    @EJB
    private PublishingCrlSessionLocal publishingCrlSession;
    @EJB
    private RevocationSessionLocal revocationSession;
    @EJB
    private RoleManagementSessionLocal roleManagementSession;
    @EJB
    private SecurityEventsLoggerSessionLocal auditSession;
    @EJB
    private UserDataSourceSessionLocal userDataSourceSession;

    @Resource
    private SessionContext sessionContext;
    // Myself needs to be looked up in postConstruct
    private CAAdminSessionLocal caAdminSession;

    /** Internal localization of logs and errors */
    private static final InternalEjbcaResources intres = InternalEjbcaResources.getInstance();

    @PostConstruct
    public void postConstruct() {
        CryptoProviderTools.installBCProviderIfNotAvailable();
        // We lookup the reference to our-self in PostConstruct, since we cannot inject this.
        // We can not inject ourself, JBoss will not start then therefore we use this to get a reference to this session bean
        // to call initializeCa we want to do it on the real bean in order to get the transaction setting (REQUIRES_NEW).
        caAdminSession = sessionContext.getBusinessObject(CAAdminSessionLocal.class);
    }

    @Override
    public void initializeAndUpgradeCAs() {
        for (final CAData cadata : CAData.findAll(entityManager)) {
            final String caname = cadata.getName();
            try {
                caAdminSession.initializeAndUpgradeCA(cadata.getCaId());
                log.info("Initialized CA: " + caname + ", with expire time: " + new Date(cadata.getExpireTime()));
            } catch (CADoesntExistsException e) {
                log.error("CADoesntExistsException trying to load CA with name: " + caname, e);
            } catch (Throwable e) {
                log.error("Exception trying to load CA, possible upgrade not performed: " + caname, e);
            }
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void initializeAndUpgradeCA(Integer caid) throws CADoesntExistsException {
        caSession.getCAInfoInternal(caid);
    }

    @Override
    public void initializeCa(final AuthenticationToken authenticationToken, final CAInfo caInfo)
            throws AuthorizationDeniedException, CryptoTokenOfflineException, InvalidAlgorithmException {

        if (caInfo.getStatus() != CAConstants.CA_UNINITIALIZED) {
            throw new IllegalArgumentException(
                    "CA Status was not CA_UNINITIALIZED (" + CAConstants.CA_UNINITIALIZED + ")");
        }
        //Find the intended status
        caInfo.setStatus(getCaStatus(caInfo));

        // Since it's acceptable that SubjectDN (and CAId) changes, in initializing we'll simply kill the old uninitialized CA and then recreate it if anything has changed.
        int calculatedCAId = CertTools.stringToBCDNString(caInfo.getSubjectDN()).hashCode();
        int currentCAId = caInfo.getCAId();
        if (calculatedCAId != currentCAId) {
            caSession.removeCA(authenticationToken, currentCAId);
            caInfo.setCAId(calculatedCAId);
            updateCAIds(authenticationToken, currentCAId, calculatedCAId, caInfo.getSubjectDN());
            rebuildExtendedServices(authenticationToken, caInfo);
            try {
                createCA(authenticationToken, caInfo);
            } catch (CAExistsException e) {
                throw new IllegalStateException(e);
            }
        } else {
            // No Subject DN change
            CAToken caToken = caInfo.getCAToken();
            CertificateProfile certprofile = certificateProfileSession
                    .getCertificateProfile(caInfo.getCertificateProfileId());
            CryptoToken cryptoToken = cryptoTokenManagementSession.getCryptoToken(caToken.getCryptoTokenId());
            // See if CA token is OK before generating keys
            try {
                cryptoToken.testKeyPair(caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_KEYTEST));
            } catch (InvalidKeyException e1) {
                throw new IllegalStateException("The CA's test key alias points to an invalid key.", e1);
            }

            try {
                mergeCertificatePoliciesFromCAAndProfile(caInfo, certprofile);
                caSession.editCA(authenticationToken, caInfo);
                CA ca = caSession.getCA(authenticationToken, caInfo.getCAId());
                ca.updateUninitializedCA(caInfo);
                ca.setCAToken(caToken);
                ca.setCAInfo(caInfo);
                //Store the chain and new status.
                caSession.editCA(authenticationToken, ca, false);

                // Finish up and create certificate chain, CRL, etc.
                finalizeInitializedCA(authenticationToken, ca, caInfo, cryptoToken, certprofile);
            } catch (CADoesntExistsException e) {
                // getCAInfo should have thrown this exception already
                throw new IllegalStateException(e);
            }
        }

        if (caInfo.getSignedBy() != CAInfo.SIGNEDBYEXTERNALCA) {
            try {
                renewAndRevokeXKMSCertificate(authenticationToken, caInfo.getCAId());
                renewAndRevokeCmsCertificate(authenticationToken, caInfo.getCAId());
            } catch (CADoesntExistsException e) {
                // getCAInfo should have thrown this exception already
                throw new IllegalStateException(e);
            } catch (CAOfflineException e) {
                // This should not happen.
                // The user can ignore these errors if he/she does not use CMS or XKMS 
                log.error("Failed to renew extended service (CMS and XKMS) certificates for ca '" + caInfo.getName()
                        + "'.", e);
            } catch (CertificateRevokeException e) {
                // ditto
                log.error("Failed to renew extended service (CMS and XKMS) certificates for ca '" + caInfo.getName()
                        + "'.", e);
            }
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void updateCAIds(AuthenticationToken authenticationToken, int fromId, int toId, String toDN)
            throws AuthorizationDeniedException {
        log.info("Updating CAIds in relations from " + fromId + " to " + toId + "\n");

        // Update Certificate Profiles
        final Map<Integer, String> certProfiles = certificateProfileSession.getCertificateProfileIdToNameMap();
        for (Integer certProfId : certProfiles.keySet()) {
            boolean changed = false;
            final CertificateProfile certProfile = certificateProfileSession.getCertificateProfile(certProfId);
            final List<Integer> availableCAs = new ArrayList<Integer>(certProfile.getAvailableCAs());
            // The list is modified so we can't use an iterator
            for (int i = 0; i < availableCAs.size(); i++) {
                int value = availableCAs.get(i);
                if (value == fromId) {
                    availableCAs.set(i, toId);
                    changed = true;
                }
            }

            if (changed) {
                certProfile.setAvailableCAs(availableCAs);
                String name = certProfiles.get(certProfId);
                certificateProfileSession.changeCertificateProfile(authenticationToken, name, certProfile);
            }
        }

        // Update End-Entity Profiles
        final Map<Integer, String> endEntityProfiles = endEntityProfileSession.getEndEntityProfileIdToNameMap();
        for (Integer endEntityProfId : endEntityProfiles.keySet()) {
            boolean changed = false;
            final EndEntityProfile endEntityProfile = endEntityProfileSession.getEndEntityProfile(endEntityProfId);

            if (endEntityProfile.getDefaultCA() == fromId) {
                endEntityProfile.setValue(EndEntityProfile.DEFAULTCA, 0, String.valueOf(toId));
                changed = true;
            }

            final Collection<String> original = endEntityProfile.getAvailableCAs();
            final List<Integer> updated = new ArrayList<Integer>();
            for (String oldvalueStr : original) {
                int oldvalue = Integer.valueOf(oldvalueStr);
                int newvalue;
                if (oldvalue == fromId) {
                    newvalue = toId;
                    changed = true;
                } else {
                    newvalue = oldvalue;
                }
                updated.add(newvalue);
            }

            if (changed) {
                endEntityProfile.setAvailableCAs(updated);
                String name = endEntityProfiles.get(endEntityProfId);
                try {
                    endEntityProfileSession.changeEndEntityProfile(authenticationToken, name, endEntityProfile);
                } catch (EndEntityProfileNotFoundException e) {
                    log.error("End-entity profile " + name + " could no longer be found", e);
                }
            }
        }

        // Update End-Entities (only if it's possible to get the session bean)
        EndEntityManagementSessionLocal endEntityManagementSession = getEndEntityManagementSession();
        if (endEntityManagementSession != null) {
            final Collection<EndEntityInformation> endEntities = endEntityManagementSession
                    .findAllUsersByCaId(authenticationToken, fromId);
            for (EndEntityInformation endEntityInfo : endEntities) {
                endEntityInfo.setCAId(toId);
                try {
                    endEntityManagementSession.updateCAId(authenticationToken, endEntityInfo.getUsername(), toId);
                } catch (NoSuchEndEntityException e) {
                    log.error("End entity " + endEntityInfo.getUsername() + " could no longer be found", e);
                }
            }
        } else {
            log.info("Can not update CAIds of end-entities (this requires EJB 3.1 support in the appserver)");
        }

        // Update Data Sources
        final Map<Integer, String> dataSources = userDataSourceSession
                .getUserDataSourceIdToNameMap(authenticationToken);
        for (Integer dataSourceId : dataSources.keySet()) {
            boolean changed = false;
            final BaseUserDataSource dataSource = userDataSourceSession.getUserDataSource(authenticationToken,
                    dataSourceId);

            dataSource.getApplicableCAs();

            final List<Integer> applicableCAs = new ArrayList<Integer>(dataSource.getApplicableCAs());
            // The list is modified so we can't use an iterator
            for (int i = 0; i < applicableCAs.size(); i++) {
                int value = applicableCAs.get(i);
                if (value == fromId) {
                    applicableCAs.set(i, toId);
                    changed = true;
                }
            }

            if (changed) {
                dataSource.setApplicableCAs(applicableCAs);
                String name = dataSources.get(dataSourceId);
                userDataSourceSession.changeUserDataSource(authenticationToken, name, dataSource);
            }
        }

        // Update Services
        ServiceSessionLocal serviceSession = getServiceSession();
        if (serviceSession != null) {
            final Map<Integer, String> services = serviceSession.getServiceIdToNameMap();
            for (String serviceName : services.values()) {
                final ServiceConfiguration serviceConf = serviceSession.getService(serviceName);
                final Properties workerProps = serviceConf.getWorkerProperties();
                final String idsToCheckStr = workerProps.getProperty(BaseWorker.PROP_CAIDSTOCHECK);
                if (!StringUtils.isEmpty(idsToCheckStr)) {
                    boolean changed = false;
                    final String[] caIds = idsToCheckStr.split(";");
                    for (int i = 0; i < caIds.length; i++) {
                        if (Integer.parseInt(caIds[i]) == fromId) {
                            caIds[i] = String.valueOf(toId);
                            changed = true;
                        }
                    }

                    if (changed) {
                        workerProps.setProperty(BaseWorker.PROP_CAIDSTOCHECK, StringUtils.join(caIds, ';'));
                        serviceConf.setWorkerProperties(workerProps);
                        serviceSession.changeService(authenticationToken, serviceName, serviceConf, false);
                    }
                }
            }
        }

        // Update Internal Key Bindings
        Map<String, Map<String, InternalKeyBindingProperty<?>>> keyBindTypes = keyBindMgmtSession
                .getAvailableTypesAndProperties();
        Map<String, List<Integer>> typesKeybindings = new HashMap<String, List<Integer>>();
        for (String type : keyBindTypes.keySet()) {
            typesKeybindings.put(type, keyBindMgmtSession.getInternalKeyBindingIds(authenticationToken, type));
        }
        for (Map.Entry<String, List<Integer>> entry : typesKeybindings.entrySet()) {
            final List<Integer> keybindIds = entry.getValue();
            for (int keybindId : keybindIds) {
                final InternalKeyBinding keybind = keyBindMgmtSession.getInternalKeyBinding(authenticationToken,
                        keybindId);
                boolean changed = false;
                List<InternalKeyBindingTrustEntry> trustentries = new ArrayList<InternalKeyBindingTrustEntry>();
                for (InternalKeyBindingTrustEntry trustentry : keybind.getTrustedCertificateReferences()) {
                    int trustCaId = trustentry.getCaId();
                    if (trustCaId == fromId) {
                        trustCaId = toId;
                        changed = true;
                    }
                    trustentries.add(
                            new InternalKeyBindingTrustEntry(trustCaId, trustentry.fetchCertificateSerialNumber()));
                }

                if (changed) {
                    keybind.setTrustedCertificateReferences(trustentries);
                    try {
                        keyBindMgmtSession.persistInternalKeyBinding(authenticationToken, keybind);
                    } catch (InternalKeyBindingNameInUseException e) {
                        // Should never happen
                        log.error("Name existed when trying to update keybinding", e);
                    }
                }
            }
        }

        // Update System Configuration
        GlobalConfiguration globalConfig = (GlobalConfiguration) globalConfigurationSession
                .getCachedConfiguration(GlobalConfiguration.GLOBAL_CONFIGURATION_ID);
        if (globalConfig != null) {
            boolean changed = false;
            if (globalConfig.getAutoEnrollCA() == fromId) {
                globalConfig.setAutoEnrollCA(toId);
                changed = true;
            }
            if (changed) {
                globalConfigurationSession.saveConfiguration(authenticationToken, globalConfig);
            }
        }

        // Update CMP Configuration
        // Only "Default CA" contains a reference to the Subject DN. All other fields reference the CAs by CA name.
        CmpConfiguration cmpConfig = (CmpConfiguration) globalConfigurationSession
                .getCachedConfiguration(CmpConfiguration.CMP_CONFIGURATION_ID);
        if (cmpConfig != null) {
            boolean changed = false;
            for (String alias : cmpConfig.getAliasList()) {
                final String defaultCaDN = cmpConfig.getCMPDefaultCA(alias);
                if (defaultCaDN != null && defaultCaDN.hashCode() == fromId) {
                    cmpConfig.setCMPDefaultCA(alias, toDN);
                    changed = true;
                }
            }
            if (changed) {
                globalConfigurationSession.saveConfiguration(authenticationToken, cmpConfig);
            }
        }

        // Update Roles
        final Random random = new Random(System.nanoTime());
        for (RoleData role : roleManagementSession.getAllRolesAuthorizedToEdit(authenticationToken)) {
            final String roleName = role.getRoleName();
            final Map<Integer, AccessUserAspectData> users = new HashMap<Integer, AccessUserAspectData>(
                    role.getAccessUsers());
            boolean changed = false;
            for (int id : new ArrayList<Integer>(users.keySet())) {
                AccessUserAspectData user = users.get(id);
                if (user.getCaId() == fromId) {
                    user = new AccessUserAspectData(roleName, toId, user.getMatchWith(), user.getTokenType(),
                            user.getMatchTypeAsType(), user.getMatchValue());
                    users.put(id, user);
                    changed = true;
                }
            }
            if (changed) {
                final Map<Integer, AccessRuleData> rules = role.getAccessRules(); // Contains no CAIds. Used as-is

                try {
                    // Rename old role so we can replace it without getting locked out
                    final String oldTempName = roleName + "_CAIdUpdateOld" + random.nextLong();
                    roleManagementSession.renameRole(authenticationToken, roleName, oldTempName);

                    RoleData newRole = roleManagementSession.create(authenticationToken, roleName);
                    // Rights are unchanged because they don't reference CAs
                    newRole = roleManagementSession.addAccessRulesToRole(authenticationToken, newRole,
                            rules.values());
                    newRole = roleManagementSession.addSubjectsToRole(authenticationToken, newRole, users.values());

                    roleManagementSession.remove(authenticationToken, oldTempName);
                } catch (RoleNotFoundException e) {
                    throw new IllegalStateException("Newly created temporary role was not found", e);
                } catch (RoleExistsException e) {
                    throw new IllegalStateException("Temporary role name already exists", e);
                }
            }
        }

        final String detailsMsg = intres.getLocalizedMessage("caadmin.updatedcaid", fromId, toId, toDN);
        auditSession.log(EventTypes.CA_EDITING, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                authenticationToken.toString(), String.valueOf(toId), null, null, detailsMsg);
    }

    /**
     * Rebuilds extended services so the Subject DN gets updated.
     */
    private void rebuildExtendedServices(final AuthenticationToken admin, CAInfo cainfo) {
        final List<ExtendedCAServiceInfo> extsvcs = new ArrayList<ExtendedCAServiceInfo>();
        final String casubjdn = cainfo.getSubjectDN();
        for (ExtendedCAServiceInfo extsvc : cainfo.getExtendedCAServiceInfos()) {
            if (extsvc instanceof XKMSCAServiceInfo) {
                final XKMSCAServiceInfo xkmssvc = (XKMSCAServiceInfo) extsvc;
                extsvc = new XKMSCAServiceInfo(extsvc.getStatus(), "CN=XKMSCertificate, " + casubjdn,
                        xkmssvc.getSubjectAltName(), xkmssvc.getKeySpec(), xkmssvc.getKeyAlgorithm());
            } else if (extsvc instanceof CmsCAServiceInfo) {
                final CmsCAServiceInfo cmssvc = (CmsCAServiceInfo) extsvc;
                extsvc = new CmsCAServiceInfo(extsvc.getStatus(), "CN=CMSCertificate, " + casubjdn,
                        cmssvc.getSubjectAltName(), cmssvc.getKeySpec(), cmssvc.getKeyAlgorithm());
            }
            extsvcs.add(extsvc);
        }
        cainfo.setExtendedCAServiceInfos(extsvcs);
    }

    @Override
    public void renewAndRevokeXKMSCertificate(final AuthenticationToken admin, int caid)
            throws AuthorizationDeniedException, CADoesntExistsException, CAOfflineException,
            CertificateRevokeException {
        CAInfo cainfo = caSession.getCAInfo(admin, caid);
        Iterator<ExtendedCAServiceInfo> iter = cainfo.getExtendedCAServiceInfos().iterator();
        while (iter.hasNext()) {
            ExtendedCAServiceInfo next = (ExtendedCAServiceInfo) iter.next();
            if (next instanceof XKMSCAServiceInfo) {
                List<Certificate> xkmscerts = ((XKMSCAServiceInfo) next).getCertificatePath();
                if (xkmscerts != null) {
                    X509Certificate xkmscert = (X509Certificate) xkmscerts.get(0);
                    revocationSession.revokeCertificate(admin, xkmscert, cainfo.getCRLPublishers(),
                            RevokedCertInfo.REVOCATION_REASON_UNSPECIFIED, cainfo.getSubjectDN());
                }
                initExternalCAService(admin, caid, next);
            }
        }
    }

    @Override
    public void renewAndRevokeCmsCertificate(final AuthenticationToken admin, int caid)
            throws AuthorizationDeniedException, CADoesntExistsException, CAOfflineException,
            CertificateRevokeException {
        CAInfo cainfo = caSession.getCAInfo(admin, caid);
        Iterator<ExtendedCAServiceInfo> iter = cainfo.getExtendedCAServiceInfos().iterator();
        while (iter.hasNext()) {
            ExtendedCAServiceInfo next = (ExtendedCAServiceInfo) iter.next();
            if (next instanceof CmsCAServiceInfo) {
                List<Certificate> cmscerts = ((CmsCAServiceInfo) next).getCertificatePath();
                if (cmscerts != null) {
                    X509Certificate cmscert = (X509Certificate) cmscerts.get(0);
                    revocationSession.revokeCertificate(admin, cmscert, cainfo.getCRLPublishers(),
                            RevokedCertInfo.REVOCATION_REASON_UNSPECIFIED, cainfo.getSubjectDN());
                }
                initExternalCAService(admin, caid, next);
            }
        }
    }

    /**
     * Tries to get an EndEntityManagementSession, if this is possible on the appserver.
     * We can't use @EJB since that fails on JBoss 5.1 which doesn't support circular dependencies.
     * We also can't use EjbLocalHelper here since it will "remember" failures, and propagate failures to other parts of EJBCA.
     * 
     * This method can be removed whenever JBoss 5.1 support is dropped, and replaced with a normal @EJB injection
     *
     * @return Session bean or null.
     */
    private EndEntityManagementSessionLocal getEndEntityManagementSession() {
        try {
            return (EndEntityManagementSessionLocal) sessionContext.lookup(
                    "java:global/ejbca/ejbca-ejb/EndEntityManagementSessionBean!org.ejbca.core.ejb.ra.EndEntityManagementSessionLocal");
        } catch (Exception e) {
            // Non EJB 3.1 app servers
            if (log.isDebugEnabled()) {
                log.debug("Could not look up end-entity management session", e);
            }
            return null;
        }
    }

    /**
     * Tries to get an ServiceSession.
     * @see getEndEntityManagementSession
     */
    private ServiceSessionLocal getServiceSession() {
        try {
            return (ServiceSessionLocal) sessionContext.lookup(
                    "java:global/ejbca/ejbca-ejb/ServiceSessionBean!org.ejbca.core.ejb.services.ServiceSessionLocal");
        } catch (Exception e) {
            // Non EJB 3.1 app servers
            if (log.isDebugEnabled()) {
                log.debug("Could not look up ServiceSession", e);
            }
            return null;
        }
    }

    private CA createCAObject(CAInfo cainfo, CAToken catoken, CertificateProfile certprofile)
            throws InvalidAlgorithmException {
        CA ca = null;
        // X509 CA is the most normal type of CA
        if (cainfo instanceof X509CAInfo) {
            log.info("Creating an X509 CA: " + cainfo.getName());
            X509CAInfo x509cainfo = (X509CAInfo) cainfo;
            // Create X509CA
            ca = new X509CA(x509cainfo);
            ca.setCAToken(catoken);
            // Set certificate policies in profile object
            mergeCertificatePoliciesFromCAAndProfile(x509cainfo, certprofile);
        } else {
            // CVC CA is a special type of CA for EAC electronic passports
            log.info("Creating a CVC CA: " + cainfo.getName());
            CVCCAInfo cvccainfo = (CVCCAInfo) cainfo;
            // Create CVCCA
            ca = CvcCA.getInstance(cvccainfo);
            ca.setCAToken(catoken);
        }
        return ca;
    }

    /** When creating, or renewing a CA we will merge the certificate policies from the CAInfo and the CertificateProfile.
     * Since  Certificate generation uses the CertificateProfile, we merge them into the CertificateProfile object.
     * 
     * @param cainfo cainfo that may contain certificate policies, or not
     * @param certprofile CertificateProfile that may contain certificate policies or not, this object is modified
     */
    private void mergeCertificatePoliciesFromCAAndProfile(CAInfo cainfo, CertificateProfile certprofile) {
        if (cainfo instanceof X509CAInfo) {
            X509CAInfo x509cainfo = (X509CAInfo) cainfo;
            // getCertificateProfile
            if ((x509cainfo.getPolicies() != null) && (x509cainfo.getPolicies().size() > 0)) {
                List<CertificatePolicy> policies = certprofile.getCertificatePolicies();
                policies.addAll(x509cainfo.getPolicies());
                // If the profile did not say to use the extensions before, add it.
                certprofile.setUseCertificatePolicies(true);
            }
        }
        // If not an X509CA, we will not do anything, because there are only certificate policies for X509CAs
    }

    @Override
    public void createCA(final AuthenticationToken admin, final CAInfo cainfo) throws AuthorizationDeniedException,
            CAExistsException, CryptoTokenOfflineException, InvalidAlgorithmException {
        if (log.isTraceEnabled()) {
            log.trace(">createCA: " + cainfo.getName());
        }
        final int caid = cainfo.getCAId();
        // Check that administrator has superadminstrator rights.
        if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtocreateca",
                    cainfo.getName());
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new AuthorizationDeniedException(detailsMsg);
        }
        // Check that CA doesn't already exists
        if (caid >= 0 && caid <= CAInfo.SPECIALCAIDBORDER) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.wrongcaid", Integer.valueOf(caid));
            auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new CAExistsException(detailsMsg);
        }
        if (CAData.findById(entityManager, Integer.valueOf(caid)) != null) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.caexistsid", Integer.valueOf(caid));
            auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new CAExistsException(detailsMsg);
        }
        if (CAData.findByName(entityManager, cainfo.getName()) != null) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.caexistsname", cainfo.getName());
            auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new CAExistsException(detailsMsg);
        }
        // Check if we are creating a CVC CA, and in case we have a unique (issuerDN,serialNumber) index in the database, then fail fast.
        if ((cainfo.getCAType() == CAInfo.CATYPE_CVC)
                && certificateStoreSession.isUniqueCertificateSerialNumberIndex()) {
            throw new IllegalArgumentException(
                    "Not possible to create CVC CA when there is a unique (issuerDN, serialNumber) index in the database.");
        }
        // Create CAToken
        final CAToken caToken = cainfo.getCAToken();
        int cryptoTokenId = caToken.getCryptoTokenId();
        final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(cryptoTokenId);
        // The certificate profile used for the CAs certificate
        CertificateProfile certprofile = certificateProfileSession
                .getCertificateProfile(cainfo.getCertificateProfileId());
        // Create CA
        CA ca = createCAObject(cainfo, caToken, certprofile);

        if (cainfo.getStatus() != CAConstants.CA_UNINITIALIZED) {
            // See if CA token is OK before storing CA, but skip if no keys can be guaranteed to exist.
            try {
                cryptoToken.testKeyPair(caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_KEYTEST));
            } catch (InvalidKeyException e1) {
                throw new RuntimeException("The CA's test key alias points to an invalid key.", e1);
            }
        }
        // Store CA in database, so we can generate keys using the ca token session.
        try {
            caSession.addCA(admin, ca);
        } catch (CAExistsException e) {
            String msg = intres.getLocalizedMessage("caadmin.caexistsid", Integer.valueOf(caid));
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, details);
            sessionContext.setRollbackOnly(); // This is an application exception so it wont trigger a roll-back automatically
            throw e;
        }

        // Finish up and create certifiate chain etc.
        // Both code paths will audit log.
        if (cainfo.getStatus() != CAConstants.CA_UNINITIALIZED) {
            finalizeInitializedCA(admin, ca, cainfo, cryptoToken, certprofile);
        } else {
            // Special handling for uninitialized CAs
            ca.setCertificateChain(new ArrayList<Certificate>());
            ca.setStatus(CAConstants.CA_UNINITIALIZED);
            if (log.isDebugEnabled()) {
                log.debug("Setting CA status to: " + CAConstants.CA_UNINITIALIZED);
            }
            try {
                caSession.editCA(admin, ca, true);
            } catch (CADoesntExistsException e) {
                final String detailsMsg = intres.getLocalizedMessage("caadmin.canotexistsid",
                        Integer.valueOf(caid));
                auditSession.log(EventTypes.CA_EDITING, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        admin.toString(), String.valueOf(caid), null, null, detailsMsg);
                throw new EJBException(e);
            }
        }

        if (log.isTraceEnabled()) {
            log.trace("<createCA: " + cainfo.getName());
        }
    }

    /**
     * The final steps of creating a CA, which are not performed for uninitialized CAs until
     * they are initialized.
     * 
     * It creates a certificate chain and publishes certificate, services, CRLs, etc.
     * This method also performs audit logging.
     */
    private void finalizeInitializedCA(final AuthenticationToken admin, final CA ca, final CAInfo cainfo,
            final CryptoToken cryptoToken, final CertificateProfile certprofile)
            throws CryptoTokenOfflineException, AuthorizationDeniedException {

        if (cainfo.getStatus() == CAConstants.CA_UNINITIALIZED) {
            throw new IllegalStateException("This method should never be called on uninitialized CAs");
        }

        final int caid = cainfo.getCAId();
        Collection<Certificate> certificatechain = createCertificateChain(admin, ca, cryptoToken, certprofile);
        int castatus = getCaStatus(cainfo);
        ca.setCertificateChain(certificatechain);
        if (log.isDebugEnabled()) {
            log.debug("Setting CA status to: " + castatus);
        }
        ca.setStatus(castatus);
        try {
            caSession.editCA(admin, ca, true);
        } catch (CADoesntExistsException e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.canotexistsid", Integer.valueOf(caid));
            auditSession.log(EventTypes.CA_EDITING, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new EJBException(e);
        }
        // Publish CA certificates if CA is initialized
        publishCACertificate(admin, ca.getCertificateChain(), ca.getCRLPublishers(), ca.getSubjectDN());
        switch (castatus) {
        case CAConstants.CA_ACTIVE:
            // activate External CA Services
            activateAndPublishExternalCAServices(admin, cainfo.getExtendedCAServiceInfos(), ca);
            try {
                caSession.editCA(admin, ca, false); // store any activates CA services
                // create initial CRLs
                publishingCrlSession.forceCRL(admin, ca.getCAId());
                publishingCrlSession.forceDeltaCRL(admin, ca.getCAId());
            } catch (CADoesntExistsException e) {
                String msg = intres.getLocalizedMessage("caadmin.errorcreateca", cainfo.getName());
                Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                details.put("error", e.getMessage());
                auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        admin.toString(), String.valueOf(caid), null, null, details);
                throw new EJBException(e);
            } catch (CAOfflineException e) {
                String msg = intres.getLocalizedMessage("caadmin.errorcreateca", cainfo.getName());
                Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                details.put("error", e.getMessage());
                auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        admin.toString(), String.valueOf(caid), null, null, details);
                throw new EJBException(e);
            }
            break;
        default:
            log.error(
                    "CA status not active when creating CA, extended services not created. CA status: " + castatus);
            break;
        }

        // Update local OCSP's CA certificate cache
        certificateStoreSession.reloadCaCertificateCache();
    }

    private int getCaStatus(CAInfo cainfo) {
        int castatus = CAConstants.CA_OFFLINE;
        if (cainfo.getSignedBy() == CAInfo.SELFSIGNED) {
            castatus = CAConstants.CA_ACTIVE;
        } else if (cainfo.getSignedBy() == CAInfo.SIGNEDBYEXTERNALCA) {
            // set status to waiting certificate response.
            castatus = CAConstants.CA_WAITING_CERTIFICATE_RESPONSE;
        } else if (cainfo.getSignedBy() > CAInfo.SPECIALCAIDBORDER || cainfo.getSignedBy() < 0) {
            castatus = CAConstants.CA_ACTIVE;
        }
        return castatus;
    }

    private Collection<Certificate> createCertificateChain(AuthenticationToken authenticationToken, CA ca,
            CryptoToken cryptoToken, CertificateProfile certprofile) throws CryptoTokenOfflineException {
        final CAInfo cainfo = ca.getCAInfo();
        final CAToken caToken = cainfo.getCAToken();
        Collection<Certificate> certificatechain = null;
        final String sequence = caToken.getKeySequence(); // get from CAtoken to make sure it is fresh
        final String aliasCertSign = caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN);
        int caid = cainfo.getCAId();
        if (cainfo.getSignedBy() == CAInfo.SELFSIGNED) {
            try {
                // create selfsigned certificate
                Certificate cacertificate = null;
                if (log.isDebugEnabled()) {
                    log.debug("CAAdminSessionBean : " + cainfo.getSubjectDN());
                }
                EndEntityInformation cadata = makeEndEntityInformation(cainfo);
                cacertificate = ca.generateCertificate(cryptoToken, cadata, cryptoToken.getPublicKey(aliasCertSign),
                        -1, null, cainfo.getValidity(), certprofile, sequence);
                if (log.isDebugEnabled()) {
                    log.debug("CAAdminSessionBean : " + CertTools.getSubjectDN(cacertificate));
                }
                // Build Certificate Chain
                certificatechain = new ArrayList<Certificate>();
                certificatechain.add(cacertificate);
                // set status to active

            } catch (CryptoTokenOfflineException e) {
                final String detailsMsg = intres.getLocalizedMessage("error.catokenoffline", cainfo.getName());
                auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
                sessionContext.setRollbackOnly(); // This is an application exception so it wont trigger a roll-back automatically
                throw e;
            } catch (Exception fe) {
                String msg = intres.getLocalizedMessage("caadmin.errorcreateca", cainfo.getName());
                Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                details.put("error", fe.getMessage());
                auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        authenticationToken.toString(), String.valueOf(caid), null, null, details);
                throw new EJBException(fe);
            }
        } else if (cainfo.getSignedBy() == CAInfo.SIGNEDBYEXTERNALCA) {
            certificatechain = new ArrayList<Certificate>();

        } else if (cainfo.getSignedBy() > CAInfo.SPECIALCAIDBORDER || cainfo.getSignedBy() < 0) {
            // Create CA signed by other internal CA.
            try {
                final CA signca = caSession.getCAForEdit(authenticationToken,
                        Integer.valueOf(cainfo.getSignedBy()));
                // Check that the signer is valid
                assertSignerValidity(authenticationToken, signca);
                // Create CA certificate
                EndEntityInformation cadata = makeEndEntityInformation(cainfo);
                CryptoToken signCryptoToken = cryptoTokenSession
                        .getCryptoToken(signca.getCAToken().getCryptoTokenId());
                Certificate cacertificate = signca.generateCertificate(signCryptoToken, cadata,
                        cryptoToken.getPublicKey(aliasCertSign), -1, null, cainfo.getValidity(), certprofile,
                        sequence);
                // Build Certificate Chain
                Collection<Certificate> rootcachain = signca.getCertificateChain();
                certificatechain = new ArrayList<Certificate>();
                certificatechain.add(cacertificate);
                certificatechain.addAll(rootcachain);
                // set status to active

            } catch (CryptoTokenOfflineException e) {
                final String detailsMsg = intres.getLocalizedMessage("error.catokenoffline", cainfo.getName());
                auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
                sessionContext.setRollbackOnly(); // This is an application exception so it wont trigger a roll-back automatically
                throw e;
            } catch (Exception fe) {
                String msg = intres.getLocalizedMessage("caadmin.errorcreateca", cainfo.getName());
                Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                details.put("error", fe.getMessage());
                auditSession.log(EventTypes.CA_CREATION, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        authenticationToken.toString(), String.valueOf(caid), null, null, details);
                throw new EJBException(fe);
            }
        }
        return certificatechain;

    }

    private EndEntityInformation makeEndEntityInformation(final CAInfo cainfo) {
        String caAltName = null;
        ExtendedInformation extendedinfo = null;
        if (cainfo instanceof X509CAInfo) {
            final X509CAInfo x509cainfo = (X509CAInfo) cainfo;
            caAltName = x509cainfo.getSubjectAltName();
            extendedinfo = new ExtendedInformation();
            extendedinfo.setNameConstraintsPermitted(x509cainfo.getNameConstraintsPermitted());
            extendedinfo.setNameConstraintsExcluded(x509cainfo.getNameConstraintsExcluded());
        }

        return new EndEntityInformation("nobody", cainfo.getSubjectDN(), cainfo.getSubjectDN().hashCode(),
                caAltName, null, 0, new EndEntityType(EndEntityTypes.INVALID), 0, cainfo.getCertificateProfileId(),
                null, null, 0, 0, extendedinfo);
    }

    @Override
    public void editCA(AuthenticationToken admin, CAInfo cainfo) throws AuthorizationDeniedException {
        boolean xkmsrenewcert = false;
        boolean cmsrenewcert = false;
        final int caid = cainfo.getCAId();
        // Check authorization
        if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) {
            String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoeditca", cainfo.getName());
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, details);
            throw new AuthorizationDeniedException(msg);
        }

        // In uninitialized CAs, the Subject DN might change, and then
        // we need to update the CA ID as well.
        if (cainfo.getStatus() == CAConstants.CA_UNINITIALIZED) {
            int calculatedCAId = CertTools.stringToBCDNString(cainfo.getSubjectDN()).hashCode();
            int currentCAId = cainfo.getCAId();
            if (calculatedCAId != currentCAId) {
                caSession.removeCA(admin, currentCAId);
                cainfo.setCAId(calculatedCAId);
                updateCAIds(admin, currentCAId, calculatedCAId, cainfo.getSubjectDN());
                rebuildExtendedServices(admin, cainfo);
                try {
                    createCA(admin, cainfo);
                } catch (CAExistsException e) {
                    throw new IllegalStateException(e);
                } catch (CryptoTokenOfflineException e) {
                    throw new IllegalStateException(e);
                } catch (InvalidAlgorithmException e) {
                    throw new IllegalStateException(e);
                }
            }
        }

        // Check if extended service certificates are about to be renewed.
        if (cainfo.getStatus() != CAConstants.CA_UNINITIALIZED) {
            final Collection<ExtendedCAServiceInfo> extendedCAServiceInfos = cainfo.getExtendedCAServiceInfos();
            if (extendedCAServiceInfos != null) {
                for (final ExtendedCAServiceInfo extendedCAServiceInfo : extendedCAServiceInfos) {
                    if (extendedCAServiceInfo instanceof XKMSCAServiceInfo) {
                        final BaseSigningCAServiceInfo signingInfo = (BaseSigningCAServiceInfo) extendedCAServiceInfo;
                        xkmsrenewcert = signingInfo.getRenewFlag() || (signingInfo.getCertificatePath() == null
                                && signingInfo.getStatus() == ExtendedCAServiceInfo.STATUS_ACTIVE);
                    } else if (extendedCAServiceInfo instanceof CmsCAServiceInfo) {
                        final BaseSigningCAServiceInfo signingInfo = (BaseSigningCAServiceInfo) extendedCAServiceInfo;
                        cmsrenewcert = signingInfo.getRenewFlag() || (signingInfo.getCertificatePath() == null
                                && signingInfo.getStatus() == ExtendedCAServiceInfo.STATUS_ACTIVE);
                    }
                }
            }
        }

        // Get CA from database
        try {
            caSession.editCA(admin, cainfo);
            CA ca = caSession.getCA(admin, cainfo.getCAId());
            if (cainfo.getStatus() != CAConstants.CA_UNINITIALIZED) {
                // No OCSP Certificate exists that can be renewed.
                if (xkmsrenewcert) {
                    XKMSCAServiceInfo info = (XKMSCAServiceInfo) ca
                            .getExtendedCAServiceInfo(ExtendedCAServiceTypes.TYPE_XKMSEXTENDEDSERVICE);
                    // Publish the extended service certificate, but only for active services
                    if (info.getStatus() == ExtendedCAServiceInfo.STATUS_ACTIVE) {
                        final ArrayList<Certificate> xkmscertificate = new ArrayList<Certificate>();
                        xkmscertificate.add(info.getCertificatePath().get(0));
                        publishCACertificate(admin, xkmscertificate, ca.getCRLPublishers(), ca.getSubjectDN());
                    }
                }
                if (cmsrenewcert) {
                    CmsCAServiceInfo info = (CmsCAServiceInfo) ca
                            .getExtendedCAServiceInfo(ExtendedCAServiceTypes.TYPE_CMSEXTENDEDSERVICE);
                    if (info.getStatus() == ExtendedCAServiceInfo.STATUS_ACTIVE) {
                        final ArrayList<Certificate> cmscertificate = new ArrayList<Certificate>();
                        cmscertificate.add(info.getCertificatePath().get(0));
                        // Publish the extended service certificate, but only for active services
                        publishCACertificate(admin, cmscertificate, ca.getCRLPublishers(), ca.getSubjectDN());
                    }
                }
            }
            // Log Action was done by caSession
        } catch (Exception fe) {
            String msg = intres.getLocalizedMessage("caadmin.erroreditca", cainfo.getName());
            log.error(msg, fe);
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EventTypes.CA_EDITING, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, details);
            throw new EJBException(fe);
        }
    }

    @Override
    public byte[] makeRequest(AuthenticationToken authenticationToken, int caid, Collection<?> certChain,
            String nextSignKeyAlias)
            throws AuthorizationDeniedException, CertPathValidatorException, CryptoTokenOfflineException {
        if (log.isTraceEnabled()) {
            log.trace(">makeRequest: " + caid + ", certChain=" + certChain + ", nextSignKeyAlias="
                    + nextSignKeyAlias);
        }
        byte[] returnval = null;
        if (!accessSession.isAuthorizedNoLogging(authenticationToken, AccessRulesConstants.REGULAR_RENEWCA)) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtocertreq",
                    Integer.valueOf(caid));
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new AuthorizationDeniedException(detailsMsg);
        }
        try {
            final CA ca = caSession.getCAForEdit(authenticationToken, caid);
            final List<Certificate> chain = new ArrayList<Certificate>();
            if (certChain != null && certChain.size() > 0) {
                chain.addAll(CertTools.createCertChain(certChain));
                log.debug("Setting request certificate chain of size: " + chain.size());
                ca.setRequestCertificateChain(chain);
            }
            // AR+ patch to make SPOC independent of external CVCA certificates for automatic renewals
            // i.e. if we don't pass a CA certificate as parameter we try to find a suitable CA certificate in the database, among existing CAs
            // (can be a simple imported CA-certificate of external CA)
            if (chain.isEmpty() && ca.getCAType() == CAInfo.CATYPE_CVC
                    && ca.getSignedBy() == CAInfo.SIGNEDBYEXTERNALCA && ca.getStatus() == CAConstants.CA_ACTIVE) {
                final CardVerifiableCertificate dvcert = (CardVerifiableCertificate) ca.getCACertificate();
                final String ca_ref = dvcert.getCVCertificate().getCertificateBody().getAuthorityReference()
                        .getConcatenated();
                log.debug("DV renewal missing CVCA cert, try finding CA for:" + ca_ref);
                for (final Integer availableCaId : caSession.getAuthorizedCaIds(authenticationToken)) {
                    final CA cvca = caSession.getCA(authenticationToken, availableCaId);
                    if (cvca.getCAType() == CAInfo.CATYPE_CVC && cvca.getSignedBy() == CAInfo.SELFSIGNED) {
                        final CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cvca
                                .getCACertificate();
                        if (ca_ref.equals(cvccert.getCVCertificate().getCertificateBody().getHolderReference()
                                .getConcatenated())) {
                            log.debug("Added missing CVCA to rewnewal request: " + cvca.getName());
                            chain.add(cvccert);
                            break;
                        }
                    }
                }
                if (chain.isEmpty()) {
                    log.info("Failed finding suitable CVCA, forgot to import it?");
                }
            }
            // AR-

            // Generate new certificate signing request.
            final CAToken caToken = ca.getCAToken();
            final String signatureAlgorithm = caToken.getSignatureAlgorithm();
            if (log.isDebugEnabled()) {
                log.debug("Using signing algorithm: " + signatureAlgorithm + " for the CSR.");
            }
            final Properties oldprop = caToken.getProperties();
            final String oldsequence = caToken.getKeySequence();
            // If no alias is supplied we use the CAs current signature key and the KeySequence to generate a new one
            if (nextSignKeyAlias == null || nextSignKeyAlias.length() == 0) {
                nextSignKeyAlias = caToken.generateNextSignKeyAlias();
            }
            caToken.setNextCertSignKey(nextSignKeyAlias);
            final int cryptoTokenId = caToken.getCryptoTokenId();
            try {
                // Test if key already exists
                cryptoTokenManagementSession.testKeyPair(authenticationToken, cryptoTokenId, nextSignKeyAlias);
            } catch (Exception e) {
                try {
                    final String currentSignKeyAlias = caToken
                            .getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN);
                    cryptoTokenManagementSession.createKeyPairWithSameKeySpec(authenticationToken, cryptoTokenId,
                            currentSignKeyAlias, nextSignKeyAlias);
                    // Audit log CA key generation
                    final Map<String, Object> details = new LinkedHashMap<String, Object>();
                    details.put("msg", intres.getLocalizedMessage("catoken.generatedkeys", caid, true, false));
                    details.put("oldproperties", oldprop);
                    details.put("oldsequence", oldsequence);
                    details.put("properties", caToken.getProperties());
                    details.put("sequence", caToken.getKeySequence());
                    auditSession.log(EventTypes.CA_KEYGEN, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                            authenticationToken.toString(), String.valueOf(caid), null, null, details);

                } catch (AuthorizationDeniedException e2) {
                    throw e2;
                } catch (CryptoTokenOfflineException e2) {
                    throw e2;
                } catch (Exception e2) {
                    throw new RuntimeException(e2);
                }
            }
            ca.setCAToken(caToken);
            // The CA certificate signing this request is the first in the certificate chain
            final Certificate caCert = chain.size() == 0 ? null : chain.get(0);
            final CryptoToken cryptoToken = cryptoTokenManagementSession.getCryptoToken(cryptoTokenId);
            byte[] request = ca.createRequest(cryptoToken, null, signatureAlgorithm, caCert,
                    CATokenConstants.CAKEYPURPOSE_CERTSIGN_NEXT);
            if (ca.getCAType() == CAInfo.CATYPE_CVC) {
                /*
                 * If this is a CVC CA renewal request, we need to sign it to make an authenticated
                 * request. The CVC CAs current signing certificate will always be the right one,
                 * because it is the "previous" signing certificate until we have imported a new
                 * one as response to the request we create here.
                 */
                // Sign the request with the current sign key making it an CVCAuthenticatedRequest
                final byte[] authCertSignRequest = ca.createAuthCertSignRequest(cryptoToken, request);
                if (authCertSignRequest != null) {
                    returnval = authCertSignRequest;
                } else {
                    // This is expected if we try to generate another CSR from a CA which has not yet recieved a response.
                    log.debug("Unable to create authorization signature on CSR. Returning a regular request.");
                    returnval = request;
                }
            } else {
                returnval = request;
            }
            caSession.editCA(authenticationToken, ca, true);
            // Log information about the event
            final String detailsMsg = intres.getLocalizedMessage("caadmin.certreqcreated", ca.getName(),
                    Integer.valueOf(caid));
            auditSession.log(EventTypes.CA_EDITING, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
        } catch (CertPathValidatorException e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errorcertreq", Integer.valueOf(caid));
            auditSession.log(EventTypes.CA_EDITING, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw e;
        } catch (CryptoTokenOfflineException e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errorcertreq", Integer.valueOf(caid));
            auditSession.log(EventTypes.CA_EDITING, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw e;
        } catch (Exception e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errorcertreq", Integer.valueOf(caid));
            auditSession.log(EventTypes.CA_EDITING, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new EJBException(e);
        }
        if (log.isTraceEnabled()) {
            log.trace("<makeRequest: " + caid);
        }
        return returnval;
    }

    @Override
    public byte[] createAuthCertSignRequest(AuthenticationToken authenticationToken, int caid,
            byte[] certSignRequest)
            throws AuthorizationDeniedException, CADoesntExistsException, CryptoTokenOfflineException {
        if (!accessSession.isAuthorizedNoLogging(authenticationToken, StandardRules.ROLE_ROOT.resource())) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtocertreq",
                    Integer.valueOf(caid));
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new AuthorizationDeniedException(detailsMsg);
        }
        final CA signedbyCA = caSession.getCA(authenticationToken, caid);
        final String caname = signedbyCA.getName();
        final CryptoToken cryptoToken = cryptoTokenSession
                .getCryptoToken(signedbyCA.getCAToken().getCryptoTokenId());
        final byte[] returnval = signedbyCA.createAuthCertSignRequest(cryptoToken, certSignRequest);
        final String detailsMsg = intres.getLocalizedMessage("caadmin.certreqsigned", caname);
        auditSession.log(EjbcaEventTypes.CA_SIGNREQUEST, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
        return returnval;
    }

    @Override
    public void receiveResponse(AuthenticationToken authenticationToken, int caid, ResponseMessage responsemessage,
            Collection<?> cachain, String nextKeyAlias)
            throws AuthorizationDeniedException, CertPathValidatorException, EjbcaException, CesecoreException {
        if (log.isTraceEnabled()) {
            log.trace(">receiveResponse: " + caid);
        }
        if (!accessSession.isAuthorizedNoLogging(authenticationToken, AccessRulesConstants.REGULAR_RENEWCA)) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtocertresp",
                    Integer.valueOf(caid));
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
        }
        try {
            final CA ca = caSession.getCAForEdit(authenticationToken, caid);
            if (!(responsemessage instanceof X509ResponseMessage)) {
                String msg = intres.getLocalizedMessage("caadmin.errorcertrespillegalmsg",
                        responsemessage != null ? responsemessage.getClass().getName() : "null");
                log.info(msg);
                throw new EjbcaException(msg);
            }
            final Certificate cacert = ((X509ResponseMessage) responsemessage).getCertificate();
            // Receiving a certificate for an internal CA will transform it into an externally signed CA
            if (ca.getSignedBy() != CAInfo.SIGNEDBYEXTERNALCA) {
                ca.setSignedBy(CAInfo.SIGNEDBYEXTERNALCA);
            }
            // Check that CA DN is equal to the certificate response.
            if (!CertTools.getSubjectDN(cacert).equals(CertTools.stringToBCDNString(ca.getSubjectDN()))) {
                String msg = intres.getLocalizedMessage("caadmin.errorcertrespwrongdn",
                        CertTools.getSubjectDN(cacert), ca.getSubjectDN());
                log.info(msg);
                throw new EjbcaException(msg);
            }
            List<Certificate> tmpchain = new ArrayList<Certificate>();
            tmpchain.add(cacert);

            Collection<Certificate> reqchain = null;
            if (cachain != null && cachain.size() > 0) {
                //  1. If we have a chain given as parameter, we will use that.
                reqchain = CertTools.createCertChain(cachain);
                log.debug("Using CA certificate chain from parameter of size: " + reqchain.size());
            } else {
                // 2. If no parameter is given we assume that the request chain was stored when the request was created.
                reqchain = ca.getRequestCertificateChain();
                if (reqchain == null) {
                    // 3. Lastly, if that failed we'll check if the certificate chain in it's entirety already exists in the database. 
                    reqchain = new ArrayList<Certificate>();
                    Certificate issuer = certificateStoreSession
                            .findLatestX509CertificateBySubject(CertTools.getIssuerDN(cacert));
                    if (issuer != null) {
                        reqchain.add(issuer);
                        while (!CertTools.isSelfSigned(issuer)) {
                            issuer = certificateStoreSession
                                    .findLatestX509CertificateBySubject(CertTools.getIssuerDN(issuer));
                            if (issuer != null) {
                                reqchain.add(issuer);
                            } else {
                                String msg = intres.getLocalizedMessage("caadmin.errorincompleterequestchain", caid,
                                        ca.getSubjectDN());
                                log.info(msg);
                                throw new CertPathValidatorException(msg);
                            }
                        }
                    }
                    if (reqchain.size() == 0) {
                        String msg = intres.getLocalizedMessage("caadmin.errornorequestchain", caid,
                                ca.getSubjectDN());
                        log.info(msg);
                        throw new CertPathValidatorException(msg);
                    }

                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Using pre-stored CA certificate chain.");
                    }
                }
            }
            log.debug("Picked up request certificate chain of size: " + reqchain.size());
            tmpchain.addAll(reqchain);
            final List<Certificate> chain = CertTools.createCertChain(tmpchain);
            log.debug("Storing certificate chain of size: " + chain.size());
            // Before importing the certificate we want to make sure that the public key matches the CAs private key
            PublicKey caCertPublicKey = cacert.getPublicKey();
            // If it is a DV certificate signed by a CVCA, enrich the public key for EC parameters from the CVCA's certificate
            if (StringUtils.equals(cacert.getType(), "CVC")) {
                if (caCertPublicKey.getAlgorithm().equals("ECDSA")) {
                    CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cacert;
                    try {
                        if (cvccert.getCVCertificate().getCertificateBody().getAuthorizationTemplate()
                                .getAuthorizationField().getAuthRole().isDV()) {
                            log.debug("Enriching DV public key with EC parameters from CVCA");
                            Certificate cvcacert = (Certificate) reqchain.iterator().next();
                            caCertPublicKey = KeyTools.getECPublicKeyWithParams(caCertPublicKey,
                                    cvcacert.getPublicKey());
                        }
                    } catch (InvalidKeySpecException e) {
                        log.debug("Strange CVCA certificate that we can't get the key from, continuing anyway...",
                                e);
                    } catch (NoSuchFieldException e) {
                        log.debug("Strange DV certificate with no AutheorizationRole, continuing anyway...", e);
                    }
                } else {
                    log.debug("Key is not ECDSA, don't try to enrich with EC parameters.");
                }
            } else {
                log.debug("Cert is not CVC, no need to enrich with EC parameters.");
            }

            final CAToken catoken = ca.getCAToken();
            final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(catoken.getCryptoTokenId());
            boolean activatedNextSignKey = false;
            if (nextKeyAlias != null) {
                try {
                    if (log.isDebugEnabled()) {
                        log.debug("SubjectKeyId for CA cert public key: " + new String(
                                Hex.encode(KeyTools.createSubjectKeyId(caCertPublicKey).getKeyIdentifier())));
                        log.debug("SubjectKeyId for CA next public key: " + new String(Hex.encode(KeyTools
                                .createSubjectKeyId(cryptoToken.getPublicKey(nextKeyAlias)).getKeyIdentifier())));
                    }
                    KeyTools.testKey(cryptoToken.getPrivateKey(nextKeyAlias), caCertPublicKey,
                            cryptoToken.getSignProviderName());
                } catch (InvalidKeyException e) {
                    throw new EjbcaException(ErrorCode.INVALID_KEY, e);
                }
                catoken.setNextCertSignKey(nextKeyAlias);
                catoken.activateNextSignKey();
                activatedNextSignKey = true;
            } else {
                // Since we don't specified the nextSignKey, we will just try the current or next CA sign key
                try {
                    KeyTools.testKey(
                            cryptoToken.getPrivateKey(
                                    catoken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN)),
                            caCertPublicKey, cryptoToken.getSignProviderName());
                } catch (Exception e1) {
                    log.debug(
                            "The received certificate response does not match the CAs private signing key for purpose CAKEYPURPOSE_CERTSIGN, trying CAKEYPURPOSE_CERTSIGN_NEXT...");
                    if (e1 instanceof InvalidKeyException) {
                        log.trace(e1);
                    } else {
                        // If it's not invalid key, we want to see more of the error
                        log.debug("Error: ", e1);
                    }
                    try {
                        KeyTools.testKey(
                                cryptoToken.getPrivateKey(
                                        catoken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN_NEXT)),
                                caCertPublicKey, cryptoToken.getSignProviderName());
                        // This was OK, so we must also activate the next signing key when importing this certificate
                        catoken.activateNextSignKey();
                        activatedNextSignKey = true;
                    } catch (Exception e2) {
                        log.debug(
                                "The received certificate response does not match the CAs private signing key for purpose CAKEYPURPOSE_CERTSIGN_NEXT either, giving up.");
                        if ((e2 instanceof InvalidKeyException) || (e2 instanceof IllegalArgumentException)) {
                            log.trace(e2);
                        } else {
                            // If it's not invalid key or missing authentication code, we want to see more of the error
                            log.debug("Error: ", e2);
                        }
                        throw new EjbcaException(ErrorCode.INVALID_KEY, e2);
                    }
                }
            }
            if (activatedNextSignKey) {
                // Activated the next signing key(s) so generate audit log
                final Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", intres.getLocalizedMessage("catoken.activatednextkey", caid));
                details.put("certSignKey", catoken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN));
                details.put("crlSignKey", catoken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CRLSIGN));
                details.put("sequence", catoken.getKeySequence());
                auditSession.log(EventTypes.CA_KEYACTIVATE, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                        authenticationToken.toString(), String.valueOf(caid), null, null, details);
            }
            ca.setCAToken(catoken);
            ca.setCertificateChain(chain);

            // Set status to active, so we can sign certificates for the external services below.
            ca.setStatus(CAConstants.CA_ACTIVE);

            // activate External CA Services
            for (int type : ca.getExternalCAServiceTypes()) {
                try {
                    ca.initExtendedService(cryptoToken, type, ca);
                    final ExtendedCAServiceInfo info = ca.getExtendedCAServiceInfo(type);
                    if (info instanceof BaseSigningCAServiceInfo) {
                        // Publish the extended service certificate, but only for active services
                        if (info.getStatus() == ExtendedCAServiceInfo.STATUS_ACTIVE) {
                            final List<Certificate> extcacertificate = new ArrayList<Certificate>();
                            extcacertificate.add(((BaseSigningCAServiceInfo) info).getCertificatePath().get(0));
                            publishCACertificate(authenticationToken, extcacertificate, ca.getCRLPublishers(),
                                    ca.getSubjectDN());
                        }
                    }
                } catch (Exception fe) {
                    final String detailsMsg = intres.getLocalizedMessage("caadmin.errorcreatecaservice",
                            Integer.valueOf(caid));
                    auditSession.log(EventTypes.CA_EDITING, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                            authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
                    throw new EJBException(fe);
                }
            }
            // Set expire time
            ca.setExpireTime(CertTools.getNotAfter(cacert));
            // Save CA
            caSession.editCA(authenticationToken, ca, true);
            // Publish CA Certificate
            publishCACertificate(authenticationToken, chain, ca.getCRLPublishers(), ca.getSubjectDN());
            // Create initial CRL
            publishingCrlSession.forceCRL(authenticationToken, caid);
            publishingCrlSession.forceDeltaCRL(authenticationToken, caid);
            // All OK
            String detailsMsg = intres.getLocalizedMessage("caadmin.certrespreceived", Integer.valueOf(caid));
            auditSession.log(EventTypes.CA_EDITING, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
        } catch (CryptoTokenOfflineException e) {
            String msg = intres.getLocalizedMessage("caadmin.errorcertresp", Integer.valueOf(caid));
            log.info(msg);
            sessionContext.setRollbackOnly(); // This is an application exception so it wont trigger a roll-back automatically
            throw e;
        } catch (CADoesntExistsException e) {
            String msg = intres.getLocalizedMessage("caadmin.errorcertresp", Integer.valueOf(caid));
            log.info(msg);
            sessionContext.setRollbackOnly(); // This is an application exception so it wont trigger a roll-back automatically
            throw e;
        } catch (CertificateEncodingException e) {
            String msg = intres.getLocalizedMessage("caadmin.errorcertresp", Integer.valueOf(caid));
            log.info(msg);
            throw new EjbcaException(e.getMessage());
        } catch (CertificateException e) {
            String msg = intres.getLocalizedMessage("caadmin.errorcertresp", Integer.valueOf(caid));
            log.info(msg);
            throw new EjbcaException(e.getMessage());
        } catch (InvalidAlgorithmParameterException e) {
            String msg = intres.getLocalizedMessage("caadmin.errorcertresp", Integer.valueOf(caid));
            log.info(msg);
            throw new EjbcaException(e.getMessage());
        } catch (NoSuchAlgorithmException e) {
            String msg = intres.getLocalizedMessage("caadmin.errorcertresp", Integer.valueOf(caid));
            log.info(msg);
            throw new EjbcaException(e.getMessage());
        } catch (NoSuchProviderException e) {
            String msg = intres.getLocalizedMessage("caadmin.errorcertresp", Integer.valueOf(caid));
            log.info(msg);
            throw new EjbcaException(e.getMessage());
        }
        if (log.isTraceEnabled()) {
            log.trace("<receiveResponse: " + caid);
        }
    }

    @Override
    public ResponseMessage processRequest(AuthenticationToken admin, CAInfo cainfo, RequestMessage requestmessage)
            throws CAExistsException, CADoesntExistsException, AuthorizationDeniedException,
            CryptoTokenOfflineException {
        final CA ca;
        Collection<Certificate> certchain = null;
        CertificateResponseMessage returnval = null;
        int caid = cainfo.getCAId();
        // check authorization
        if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) {
            String msg = intres.getLocalizedMessage("caadmin.notauthorizedtocertresp", cainfo.getName());
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, details);
            throw new AuthorizationDeniedException(msg);
        }

        // Check that CA doesn't already exists
        CAData oldcadata = null;
        if (caid >= 0 && caid <= CAInfo.SPECIALCAIDBORDER) {
            String msg = intres.getLocalizedMessage("caadmin.errorcaexists", cainfo.getName());
            log.info(msg);
            throw new CAExistsException(msg);
        }
        oldcadata = CAData.findById(entityManager, Integer.valueOf(caid));
        // If it did not exist with a certain DN (caid) perhaps a CA with the
        // same CA name exists?
        if (oldcadata == null) {
            oldcadata = CAData.findByName(entityManager, cainfo.getName());
        }
        boolean processinternalca = false;
        if (oldcadata != null) {
            // If we find an already existing CA, there is a good chance that we
            // should throw an exception
            // Saying that the CA already exists.
            // However, if we have the same DN, and give the same name, we
            // simply assume that the admin actually wants
            // to treat an internal CA as an external CA, perhaps there is
            // different HSMs connected for root CA and sub CA?
            if (log.isDebugEnabled()) {
                log.debug("Old castatus=" + oldcadata.getStatus() + ", oldcaid=" + oldcadata.getCaId().intValue()
                        + ", caid=" + cainfo.getCAId() + ", oldcaname=" + oldcadata.getName() + ", name="
                        + cainfo.getName());
            }
            if (((oldcadata.getStatus() == CAConstants.CA_WAITING_CERTIFICATE_RESPONSE)
                    || (oldcadata.getStatus() == CAConstants.CA_ACTIVE)
                    || (oldcadata.getStatus() == CAConstants.CA_EXTERNAL))
                    && (oldcadata.getCaId().intValue() == cainfo.getCAId())
                    && (oldcadata.getName().equals(cainfo.getName()))) {
                // Yes, we have all the same DN, CAName and the old CA is either
                // waiting for a certificate response or is active
                // (new CA or active CA that we want to renew)
                // or it is an external CA that we want to issue a new
                // certificate to
                processinternalca = true;
                if (oldcadata.getStatus() == CAConstants.CA_EXTERNAL) {
                    log.debug("Renewing an external CA.");
                } else {
                    log.debug("Processing an internal CA, as an external.");
                }
            } else {
                String msg = intres.getLocalizedMessage("caadmin.errorcaexists", cainfo.getName());
                log.info(msg);
                throw new CAExistsException(msg);
            }
        }

        // get signing CA
        if (cainfo.getSignedBy() > CAInfo.SPECIALCAIDBORDER || cainfo.getSignedBy() < 0) {
            try {
                final CA signca = caSession.getCAForEdit(admin, Integer.valueOf(cainfo.getSignedBy()));
                try {
                    // Check that the signer is valid
                    assertSignerValidity(admin, signca);

                    // Get public key from request
                    PublicKey publickey = requestmessage.getRequestPublicKey();

                    // Create cacertificate
                    Certificate cacertificate = null;
                    EndEntityInformation cadata = makeEndEntityInformation(cainfo);
                    // We can pass the PKCS10 request message as extra
                    // parameters
                    if (requestmessage instanceof PKCS10RequestMessage) {
                        ExtendedInformation extInfo = new ExtendedInformation();
                        PKCS10CertificationRequest pkcs10 = ((PKCS10RequestMessage) requestmessage)
                                .getCertificationRequest();
                        extInfo.setCustomData(ExtendedInformationFields.CUSTOM_PKCS10,
                                new String(Base64.encode(pkcs10.getEncoded())));
                        cadata.setExtendedinformation(extInfo);
                    }
                    CertificateProfile certprofile = certificateProfileSession
                            .getCertificateProfile(cainfo.getCertificateProfileId());
                    String sequence = null;
                    byte[] ki = requestmessage.getRequestKeyInfo();
                    if ((ki != null) && (ki.length > 0)) {
                        sequence = new String(ki);
                    }
                    final CryptoToken signCryptoToken = cryptoTokenSession
                            .getCryptoToken(signca.getCAToken().getCryptoTokenId());
                    cacertificate = signca.generateCertificate(signCryptoToken, cadata, publickey, -1, null,
                            cainfo.getValidity(), certprofile, sequence);
                    // X509ResponseMessage works for both X509 CAs and CVC CAs, should really be called CertificateResponsMessage
                    returnval = new X509ResponseMessage();
                    returnval.setCertificate(cacertificate);

                    // Build Certificate Chain
                    Collection<Certificate> rootcachain = signca.getCertificateChain();
                    certchain = new ArrayList<Certificate>();
                    certchain.add(cacertificate);
                    certchain.addAll(rootcachain);

                    if (!processinternalca) {
                        // If this is an internal CA, we don't create it and set
                        // a NULL token, since the CA is already created
                        if (cainfo instanceof X509CAInfo) {
                            log.info("Creating a X509 CA (process request)");
                            ca = new X509CA((X509CAInfo) cainfo);
                        } else if (cainfo instanceof CVCCAInfo) {
                            // CVC CA is a special type of CA for EAC electronic
                            // passports
                            log.info("Creating a CVC CA (process request)");
                            CVCCAInfo cvccainfo = (CVCCAInfo) cainfo;
                            // Create CVCCA
                            ca = CvcCA.getInstance(cvccainfo);
                        } else {
                            ca = null;
                        }
                        ca.setCertificateChain(certchain);
                        CAToken token = new CAToken(ca.getCAId(), new NullCryptoToken().getProperties());
                        ca.setCAToken(token);

                        // set status to active
                        entityManager.persist(
                                new CAData(cainfo.getSubjectDN(), cainfo.getName(), CAConstants.CA_EXTERNAL, ca));
                        // cadatahome.create(cainfo.getSubjectDN(), cainfo.getName(), SecConst.CA_EXTERNAL, ca);
                    } else {
                        if (oldcadata.getStatus() == CAConstants.CA_EXTERNAL) {
                            // If it is an external CA we will not import the
                            // certificate later on here, so we want to
                            // update the CA in this instance with the new
                            // certificate so it is visible
                            ca = caSession.getCAForEdit(admin, oldcadata.getCaId());//getCAFromDatabase(oldcadata.getCaId());
                            ca.setCertificateChain(certchain);
                            if (log.isDebugEnabled()) {
                                log.debug("Storing new certificate chain for external CA " + cainfo.getName()
                                        + ", CA token type: " + ca.getCAToken().getClass().getName());
                            }
                            caSession.editCA(admin, ca, true);
                        } else {
                            // If it is an internal CA so we are "simulating"
                            // signing a real external CA we don't do anything
                            // because that CA is waiting to import a
                            // certificate
                            if (log.isDebugEnabled()) {
                                log.debug(
                                        "Not storing new certificate chain or updating CA for internal CA, simulating external: "
                                                + cainfo.getName());
                            }
                            ca = null;
                        }
                    }
                    // Publish CA certificates.
                    publishCACertificate(admin, certchain, signca.getCRLPublishers(),
                            ca != null ? ca.getSubjectDN() : null);
                    // External CAs will not have any CRLs in this system, so we don't have to try to publish any CRLs
                } catch (CryptoTokenOfflineException e) {
                    String msg = intres.getLocalizedMessage("caadmin.errorprocess", cainfo.getName());
                    log.error(msg, e);
                    throw e;
                }
            } catch (Exception e) {
                String msg = intres.getLocalizedMessage("caadmin.errorprocess", cainfo.getName());
                log.error(msg, e);
                throw new EJBException(e);
            }

        }

        if (certchain != null) {
            String msg = intres.getLocalizedMessage("caadmin.processedca", cainfo.getName());
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EventTypes.CA_EDITING, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, details);
        } else {
            String msg = intres.getLocalizedMessage("caadmin.errorprocess", cainfo.getName());
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EventTypes.CA_EDITING, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, details);
        }
        return returnval;
    }

    @Override
    public void importCACertificate(AuthenticationToken admin, String caname, Collection<Certificate> certificates)
            throws AuthorizationDeniedException, CAExistsException, IllegalCryptoTokenException,
            CertificateImportException {
        // Re-order if needed and validate chain
        if (certificates.size() != 1) {
            // In the case there is a chain, we require a full chain leading up to a root
            try {
                certificates = CertTools.createCertChain(certificates);
            } catch (CertPathValidatorException e) {
                throw new CertificateImportException(
                        "The provided certificates does not form a full certificate chain.");
            } catch (InvalidAlgorithmParameterException e) {
                throw new CertificateImportException(e);
            } catch (NoSuchAlgorithmException e) {
                throw new CertificateImportException(e);
            } catch (NoSuchProviderException e) {
                throw new CertificateImportException(e);
            } catch (CertificateException e) {
                throw new CertificateImportException(e);
            }
        }
        final Certificate caCertificate = certificates.iterator().next();
        if (!CertTools.isCA(caCertificate)) {
            throw new CertificateImportException("Only CA certificates can be imported using this function.");
        }
        CA ca = null;
        CAInfo cainfo = null;

        // Parameters common for both X509 and CVC CAs
        int certprofileid = CertTools.isSelfSigned(caCertificate)
                ? CertificateProfileConstants.CERTPROFILE_FIXED_ROOTCA
                : CertificateProfileConstants.CERTPROFILE_FIXED_SUBCA;
        String subjectdn = CertTools.getSubjectDN(caCertificate);
        long validity = 0;
        int signedby = CertTools.isSelfSigned(caCertificate) ? CAInfo.SELFSIGNED : CAInfo.SIGNEDBYEXTERNALCA;
        log.info("Preparing to import of CA with Subject DN " + subjectdn);
        if (caCertificate instanceof X509Certificate) {
            X509Certificate x509CaCertificate = (X509Certificate) caCertificate;
            String subjectaltname = CertTools.getSubjectAlternativeName(x509CaCertificate);

            // Process certificate policies.
            ArrayList<CertificatePolicy> policies = new ArrayList<CertificatePolicy>();
            CertificateProfile certprof = certificateProfileSession.getCertificateProfile(certprofileid);
            if (certprof.getCertificatePolicies() != null && certprof.getCertificatePolicies().size() > 0) {
                policies.addAll(certprof.getCertificatePolicies());
            }

            X509CAInfo x509cainfo = new X509CAInfo(subjectdn, caname, CAConstants.CA_EXTERNAL, certprofileid,
                    validity, signedby, null, null);
            x509cainfo.setSubjectAltName(subjectaltname);
            x509cainfo.setPolicies(policies);
            x509cainfo.setExpireTime(CertTools.getNotAfter(x509CaCertificate));
            cainfo = x509cainfo;
        } else if (StringUtils.equals(caCertificate.getType(), "CVC")) {
            cainfo = new CVCCAInfo(subjectdn, caname, CAConstants.CA_EXTERNAL, certprofileid, validity, signedby,
                    null, null);
        }

        cainfo.setDescription("CA created by certificate import.");

        if (cainfo instanceof X509CAInfo) {
            log.info("Creating a X509 CA (process request)");
            ca = new X509CA((X509CAInfo) cainfo);
        } else if (cainfo instanceof CVCCAInfo) {
            // CVC CA is a special type of CA for EAC electronic passports
            log.info("Creating a CVC CA (process request)");
            CVCCAInfo cvccainfo = (CVCCAInfo) cainfo;
            ca = CvcCA.getInstance(cvccainfo);
        }
        ca.setCertificateChain(certificates);
        CAToken token = new CAToken(ca.getCAId(), new NullCryptoToken().getProperties());
        try {
            ca.setCAToken(token);
        } catch (InvalidAlgorithmException e) {
            throw new IllegalCryptoTokenException(e);
        }
        // Add CA
        caSession.addCA(admin, ca);
        // Publish CA certificates.
        publishCACertificate(admin, certificates, null, ca.getSubjectDN());
    }

    @Override
    public void importCACertificateUpdate(final AuthenticationToken authenticationToken, final int caId,
            Collection<Certificate> certificates)
            throws CADoesntExistsException, AuthorizationDeniedException, CertificateImportException {
        // Re-order if needed and validate chain
        if (certificates.size() != 1) {
            // In the case there is a chain, we require a full chain leading up to a root
            try {
                certificates = CertTools.createCertChain(certificates);
            } catch (CertPathValidatorException e) {
                throw new CertificateImportException(
                        "The provided certificates does not form a full certificate chain.");
            } catch (InvalidAlgorithmParameterException e) {
                throw new CertificateImportException(e);
            } catch (NoSuchAlgorithmException e) {
                throw new CertificateImportException(e);
            } catch (NoSuchProviderException e) {
                throw new CertificateImportException(e);
            } catch (CertificateException e) {
                throw new CertificateImportException(e);
            }
        }
        final Certificate newCaCertificate = certificates.iterator().next();
        if (!CertTools.isCA(newCaCertificate)) {
            throw new CertificateImportException("Only CA certificates can be imported using this function.");
        }
        final String newSubjectDn = CertTools.getSubjectDN(newCaCertificate);
        log.info("Preparing to import of update for CA with Subject DN " + newSubjectDn);
        final CA ca = caSession.getCAForEdit(authenticationToken, caId);
        final CAInfo caInfo = ca.getCAInfo();
        final Certificate oldCaCertificate = ca.getCACertificate();
        if (ca.getStatus() != CAConstants.CA_EXTERNAL) {
            throw new CertificateImportException("Only able to update imported CA certificate of external CAs.");
        }
        if (CertTools.getFingerprintAsString(oldCaCertificate)
                .equals(CertTools.getFingerprintAsString(newCaCertificate))) {
            // The admin might want to update the chain even if the leaf CA cert is the same
            boolean sameAsExisting = true;
            if (caInfo.getCertificateChain().size() == certificates.size()) {
                for (int i = 1; i < certificates.size(); i++) {
                    if (!CertTools.getFingerprintAsString(oldCaCertificate)
                            .equals(CertTools.getFingerprintAsString(newCaCertificate))) {
                        sameAsExisting = false;
                    }
                }
            } else {
                sameAsExisting = false;
            }
            if (sameAsExisting) {
                throw new CertificateImportException("The CA certificate chain is already imported.");
            }
        }
        if (!CertTools.getSubjectDN(oldCaCertificate).equals(newSubjectDn)) {
            throw new CertificateImportException(
                    "Only able to update imported CA certificate if Subject DN of the leaf CA certificate is the same.");
        }
        // Check that update is newer if information is present
        final Date newValidFrom = CertTools.getNotBefore(newCaCertificate);
        final Date oldValidFrom = CertTools.getNotBefore(oldCaCertificate);
        if (log.isDebugEnabled()) {
            log.debug("Current valid from: " + ValidityDate.formatAsISO8601(oldValidFrom, TimeZone.getDefault())
                    + " Import valid from: " + ValidityDate.formatAsISO8601(newValidFrom, TimeZone.getDefault()));
        }
        if (newValidFrom != null && oldValidFrom != null && newValidFrom.before(oldValidFrom)) {
            throw new CertificateImportException(
                    "Only able to update imported CA certificate if new certificate is issued after the currently used.");
        }
        ca.setExpireTime(CertTools.getNotAfter(newCaCertificate));
        // Could be signed by an external CA now or vice versa
        if (CertTools.isSelfSigned(newCaCertificate)) {
            caInfo.setCertificateProfileId(CertificateProfileConstants.CERTPROFILE_FIXED_ROOTCA);
            caInfo.setSignedBy(CAInfo.SELFSIGNED);
        } else {
            caInfo.setCertificateProfileId(CertificateProfileConstants.CERTPROFILE_FIXED_SUBCA);
            caInfo.setSignedBy(CAInfo.SIGNEDBYEXTERNALCA);
        }
        ca.setCertificateChain(certificates);
        // Update CA in database
        caSession.editCA(authenticationToken, ca, true);
    }

    @Override
    public void initExternalCAService(AuthenticationToken admin, int caid, ExtendedCAServiceInfo info)
            throws CADoesntExistsException, AuthorizationDeniedException, CAOfflineException {
        // check authorization
        if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) {
            String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoeditca", Integer.valueOf(caid));
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, details);
            throw new AuthorizationDeniedException(msg);
        }

        // Get CA info.
        CA ca = caSession.getCAForEdit(admin, caid);
        if (ca.getStatus() == CAConstants.CA_OFFLINE) {
            String msg = intres.getLocalizedMessage("error.catokenoffline", ca.getName());
            throw new CAOfflineException(msg);
        }
        ArrayList<ExtendedCAServiceInfo> infos = new ArrayList<ExtendedCAServiceInfo>();
        infos.add(info);
        activateAndPublishExternalCAServices(admin, infos, ca);
        // Update CA in database
        caSession.editCA(admin, ca, true);
    }

    @Override
    public void renewCA(AuthenticationToken authenticationToken, int caid, boolean regenerateKeys,
            Date customNotBefore, final boolean createLinkCertificate)
            throws CADoesntExistsException, AuthorizationDeniedException, CryptoTokenOfflineException {
        final CA ca = caSession.getCAForEdit(authenticationToken, caid);
        final CAToken caToken = ca.getCAToken();
        final Properties oldProperties = caToken.getProperties();
        final String oldSequence = caToken.getKeySequence();
        final String currentSignKeyAlias = caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN);
        String nextSignKeyAlias = currentSignKeyAlias;
        final int cryptoTokenId = caToken.getCryptoTokenId();
        if (regenerateKeys) {
            nextSignKeyAlias = caToken.generateNextSignKeyAlias();
            if (cryptoTokenManagementSession.getKeyPairInfo(authenticationToken, cryptoTokenId,
                    nextSignKeyAlias) == null) {
                // Ok.. No such key..
                try {
                    cryptoTokenManagementSession.createKeyPairWithSameKeySpec(authenticationToken, cryptoTokenId,
                            currentSignKeyAlias, nextSignKeyAlias);
                    // Audit log CA key generation
                    final Map<String, Object> details = new LinkedHashMap<String, Object>();
                    details.put("msg", intres.getLocalizedMessage("catoken.generatedkeys", caid, true, false));
                    details.put("oldproperties", oldProperties);
                    details.put("oldsequence", oldSequence);
                    details.put("properties", caToken.getProperties());
                    details.put("sequence", caToken.getKeySequence());
                    auditSession.log(EventTypes.CA_KEYGEN, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                            authenticationToken.toString(), String.valueOf(caid), null, null, details);
                    ca.setCAToken(caToken);
                    caSession.editCA(authenticationToken, ca, true);
                } catch (AuthorizationDeniedException e2) {
                    throw e2;
                } catch (CryptoTokenOfflineException e2) {
                    throw e2;
                } catch (Exception e2) {
                    throw new RuntimeException(e2);
                }
            } else {
                log.warn("Key generation request for existing key alias ignored for CA=" + ca.getCAId()
                        + ", CryptoToken=" + cryptoTokenId + " and alias=" + nextSignKeyAlias);
            }
        }
        renewCA(authenticationToken, caid, nextSignKeyAlias, customNotBefore, createLinkCertificate);
    }

    @Override
    public void renewCA(final AuthenticationToken authenticationToken, final int caid,
            final String nextSignKeyAlias, Date customNotBefore, final boolean createLinkCertificate)
            throws AuthorizationDeniedException, CryptoTokenOfflineException {
        if (log.isTraceEnabled()) {
            log.trace(">CAAdminSession, renewCA(), caid=" + caid);
        }
        Collection<Certificate> cachain = null;
        Certificate cacertificate = null;
        // check authorization
        if (!accessSession.isAuthorizedNoLogging(authenticationToken, AccessRulesConstants.REGULAR_RENEWCA)) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtorenew",
                    Integer.valueOf(caid));
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new AuthorizationDeniedException(detailsMsg);
        }
        // Get CA info.
        try {
            CA ca = caSession.getCAForEdit(authenticationToken, caid);
            if (ca.getStatus() == CAConstants.CA_OFFLINE || ca.getCAToken().getTokenStatus(true, cryptoTokenSession
                    .getCryptoToken(ca.getCAToken().getCryptoTokenId())) == CryptoToken.STATUS_OFFLINE) {
                String msg = intres.getLocalizedMessage("error.catokenoffline", ca.getName());
                throw new CryptoTokenOfflineException(msg);
            }
            if (ca.getSignedBy() == CAInfo.SIGNEDBYEXTERNALCA) {
                // We should never get here
                log.error("Directly renewing a CA signed by external can not be done");
                throw new NotSupportedException("Directly renewing a CA signed by external can not be done");
            }
            final CAToken caToken = ca.getCAToken();
            final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(caToken.getCryptoTokenId());
            cryptoToken.testKeyPair(nextSignKeyAlias);
            caToken.setNextCertSignKey(nextSignKeyAlias);
            // Activate the next signing key(s) and generate audit log
            caToken.activateNextSignKey();
            final Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", intres.getLocalizedMessage("catoken.activatednextkey", caid));
            details.put("certSignKey", caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN));
            details.put("crlSignKey", caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CRLSIGN));
            details.put("sequence", caToken.getKeySequence());
            auditSession.log(EventTypes.CA_KEYACTIVATE, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, details);
            // if issuer is insystem CA or selfsigned, then generate new certificate.
            log.info("Renewing CA using " + caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN));
            final PublicKey caPublicKey = cryptoToken
                    .getPublicKey(caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN));
            ca.setCAToken(caToken);
            final CertificateProfile certprofile = certificateProfileSession
                    .getCertificateProfile(ca.getCertificateProfileId());
            mergeCertificatePoliciesFromCAAndProfile(ca.getCAInfo(), certprofile);

            if (ca.getSignedBy() == CAInfo.SELFSIGNED) {
                // create selfsigned certificate
                EndEntityInformation cainfodata = makeEndEntityInformation(ca.getCAInfo());
                // get from CAtoken to make sure it is fresh
                String sequence = caToken.getKeySequence();
                cacertificate = ca.generateCertificate(cryptoToken, cainfodata, caPublicKey, -1, customNotBefore,
                        ca.getValidity(), certprofile, sequence);
                // Build Certificate Chain
                cachain = new ArrayList<Certificate>();
                cachain.add(cacertificate);

            } else {
                // Resign with CA above.
                if (ca.getSignedBy() > CAInfo.SPECIALCAIDBORDER || ca.getSignedBy() < 0) {
                    // Create CA signed by other internal CA.
                    final CA signca = caSession.getCAForEdit(authenticationToken,
                            Integer.valueOf(ca.getSignedBy()));
                    // Check that the signer is valid
                    assertSignerValidity(authenticationToken, signca);
                    // Create cacertificate
                    EndEntityInformation cainfodata = makeEndEntityInformation(ca.getCAInfo());
                    String sequence = caToken.getKeySequence(); // get from CAtoken to make sure it is fresh
                    CryptoToken signCryptoToken = cryptoTokenSession
                            .getCryptoToken(signca.getCAToken().getCryptoTokenId());
                    cacertificate = signca.generateCertificate(signCryptoToken, cainfodata, caPublicKey, -1,
                            customNotBefore, ca.getValidity(), certprofile, sequence);
                    // Build Certificate Chain
                    Collection<Certificate> rootcachain = signca.getCertificateChain();
                    cachain = new ArrayList<Certificate>();
                    cachain.add(cacertificate);
                    cachain.addAll(rootcachain);
                }
            }
            // Set statuses and expire time
            ca.setExpireTime(CertTools.getNotAfter(cacertificate));
            ca.setStatus(CAConstants.CA_ACTIVE);
            // Set the new certificate chain that we have created above
            ca.setCertificateChain(cachain);
            ca.createOrRemoveLinkCertificate(cryptoToken, createLinkCertificate, certprofile);
            // We need to save all this, audit logging that the CA is changed
            caSession.editCA(authenticationToken, ca, true);

            // Publish the new CA certificate
            publishCACertificate(authenticationToken, cachain, ca.getCRLPublishers(), ca.getSubjectDN());
            publishingCrlSession.forceCRL(authenticationToken, caid);
            publishingCrlSession.forceDeltaCRL(authenticationToken, caid);
            // Audit log
            final String detailsMsg = intres.getLocalizedMessage("caadmin.renewdca", Integer.valueOf(caid));
            auditSession.log(EjbcaEventTypes.CA_RENEWED, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
        } catch (CryptoTokenOfflineException e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errorrenewca", Integer.valueOf(caid));
            auditSession.log(EjbcaEventTypes.CA_RENEWED, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw e;
        } catch (Exception e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errorrenewca", Integer.valueOf(caid));
            auditSession.log(EjbcaEventTypes.CA_RENEWED, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    authenticationToken.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new EJBException(e);
        }
        if (log.isTraceEnabled()) {
            log.trace("<CAAdminSession, renewCA(), caid=" + caid);
        }
    }

    @Override
    public byte[] getLatestLinkCertificate(final int caId) throws CADoesntExistsException {
        try {
            CA ca = caSession.getCANoLog(new AlwaysAllowLocalAuthenticationToken(
                    new UsernamePrincipal("Fetching link certificate user.")), caId);
            return ca.getLatestLinkCertificate();
        } catch (AuthorizationDeniedException e) {
            throw new RuntimeException(e); // Should always be allowed
        }
    }

    @Override
    public void revokeCA(AuthenticationToken admin, int caid, int reason)
            throws CADoesntExistsException, AuthorizationDeniedException {
        // check authorization
        if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtorevoke",
                    Integer.valueOf(caid));
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new AuthorizationDeniedException(detailsMsg);
        }
        // Get CA info.
        CA ca = caSession.getCAForEdit(admin, caid);
        try {
            // Revoke all issued CA certificates for this CA
            Collection<Certificate> cacerts = certificateStoreSession.findCertificatesBySubject(ca.getSubjectDN());
            for (Certificate certificate : cacerts) {
                revocationSession.revokeCertificate(admin, certificate, ca.getCRLPublishers(), reason,
                        ca.getSubjectDN());
            }
            // Revoke all certificates issued by this CA. If this is a root CA the CA certificates will be included in this batch as well
            // but if this is a subCA these are only the "entity" certificates issued by this CA
            if (ca.getStatus() != CAConstants.CA_EXTERNAL) {
                certificateStoreSession.revokeAllCertByCA(admin, ca.getSubjectDN(), reason);
                Collection<Integer> caids = new ArrayList<Integer>();
                caids.add(Integer.valueOf(ca.getCAId()));
                publishingCrlSession.createCRLs(admin, caids, 0);
            }
            ca.setRevocationReason(reason);
            ca.setRevocationDate(new Date());
            if (ca.getStatus() != CAConstants.CA_EXTERNAL) {
                ca.setStatus(CAConstants.CA_REVOKED);
            }
            // Store new status, audit logging
            caSession.editCA(admin, ca, true);
            final String detailsMsg = intres.getLocalizedMessage("caadmin.revokedca", ca.getName(),
                    Integer.valueOf(reason));
            auditSession.log(EjbcaEventTypes.CA_REVOKED, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
        } catch (Exception e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errorrevoke", ca.getName());
            auditSession.log(EjbcaEventTypes.CA_REVOKED, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new EJBException(e);
        }
    }

    @Override
    public void importCAFromKeyStore(AuthenticationToken admin, String caname, byte[] p12file, String keystorepass,
            String privkeypass, String privateSignatureKeyAlias, String privateEncryptionKeyAlias) {
        try {
            // check authorization
            if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) {
                String msg = intres.getLocalizedMessage("caadmin.notauthorizedtocreateca", caname);
                Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        admin.toString(), null, null, null, details);
            }
            // load keystore
            java.security.KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
            keystore.load(new java.io.ByteArrayInputStream(p12file), keystorepass.toCharArray());
            // Extract signature keys
            if (privateSignatureKeyAlias == null || !keystore.isKeyEntry(privateSignatureKeyAlias)) {
                throw new Exception("Alias \"" + privateSignatureKeyAlias + "\" not found.");
            }
            Certificate[] signatureCertChain = KeyTools.getCertChain(keystore, privateSignatureKeyAlias);
            if (signatureCertChain.length < 1) {
                String msg = "Cannot load certificate chain with alias " + privateSignatureKeyAlias;
                log.error(msg);
                throw new Exception(msg);
            }
            Certificate caSignatureCertificate = (Certificate) signatureCertChain[0];
            PublicKey p12PublicSignatureKey = caSignatureCertificate.getPublicKey();
            PrivateKey p12PrivateSignatureKey = null;
            p12PrivateSignatureKey = (PrivateKey) keystore.getKey(privateSignatureKeyAlias,
                    privkeypass.toCharArray());
            log.debug("ImportSignatureKeyAlgorithm=" + p12PrivateSignatureKey.getAlgorithm());

            // Extract encryption keys
            PrivateKey p12PrivateEncryptionKey = null;
            PublicKey p12PublicEncryptionKey = null;
            Certificate caEncryptionCertificate = null;
            if (privateEncryptionKeyAlias != null) {
                if (!keystore.isKeyEntry(privateEncryptionKeyAlias)) {
                    throw new Exception("Alias \"" + privateEncryptionKeyAlias + "\" not found.");
                }
                Certificate[] encryptionCertChain = KeyTools.getCertChain(keystore, privateEncryptionKeyAlias);
                if (encryptionCertChain.length < 1) {
                    String msg = "Cannot load certificate chain with alias " + privateEncryptionKeyAlias;
                    log.error(msg);
                    throw new Exception(msg);
                }
                caEncryptionCertificate = (Certificate) encryptionCertChain[0];
                p12PrivateEncryptionKey = (PrivateKey) keystore.getKey(privateEncryptionKeyAlias,
                        privkeypass.toCharArray());
                p12PublicEncryptionKey = caEncryptionCertificate.getPublicKey();
            }
            importCAFromKeys(admin, caname, keystorepass, signatureCertChain, p12PublicSignatureKey,
                    p12PrivateSignatureKey, p12PrivateEncryptionKey, p12PublicEncryptionKey);
        } catch (Exception e) {
            String detailsMsg = intres.getLocalizedMessage("caadmin.errorimportca", caname, "PKCS12",
                    e.getMessage());
            auditSession.log(EjbcaEventTypes.CA_IMPORT, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), null, null, null, detailsMsg);
            throw new EJBException(e);
        }
    }

    @Override
    public void removeCAKeyStore(AuthenticationToken admin, String caname) throws EJBException {
        if (log.isTraceEnabled()) {
            log.trace(">removeCAKeyStore");
        }
        try {
            // check authorization
            if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) {
                String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoremovecatoken", caname);
                Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        admin.toString(), null, null, null, details);
            }
            CA ca = caSession.getCAForEdit(admin, caname);
            final CAToken currentCaToken = ca.getCAToken();
            final int cryptoTokenId = currentCaToken.getCryptoTokenId();
            CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(cryptoTokenId);
            if (!(cryptoToken instanceof SoftCryptoToken)) {
                throw new Exception("Cannot export anything but a soft token.");
            }
            cryptoTokenManagementSession.deactivate(admin, cryptoTokenId);
            // Create a new CAToken with the same properties but without the reference to the removed CryptoToken
            cryptoTokenSession.removeCryptoToken(cryptoTokenId);
            final CAToken newCaToken = new CAToken(0, currentCaToken.getProperties());
            newCaToken.setKeySequence(newCaToken.getKeySequence());
            newCaToken.setKeySequenceFormat(newCaToken.getKeySequenceFormat());
            newCaToken.setSignatureAlgorithm(newCaToken.getSignatureAlgorithm());
            newCaToken.setEncryptionAlgorithm(newCaToken.getEncryptionAlgorithm());
            ca.setCAToken(newCaToken);
            // Set this CA to offline, since it cannot be used without a CryptoToken this is probably intended.
            ca.setStatus(CAConstants.CA_OFFLINE);
            // Save to database
            caSession.editCA(admin, ca, false);
            // Log
            final String detailsMsg = intres.getLocalizedMessage("caadmin.removedcakeystore",
                    Integer.valueOf(ca.getCAId()));
            auditSession.log(EjbcaEventTypes.CA_REMOVETOKEN, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(ca.getCAId()), null, null, detailsMsg);
        } catch (Exception e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errorremovecakeystore", caname, "PKCS12",
                    e.getMessage());
            auditSession.log(EjbcaEventTypes.CA_REMOVETOKEN, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), null, null, null, detailsMsg);
            throw new EJBException(detailsMsg, e);
        }
        if (log.isTraceEnabled()) {
            log.trace("<removeCAKeyStore");
        }
    }

    @Override
    public void restoreCAKeyStore(AuthenticationToken authenticationToken, String caname, byte[] p12file,
            String keystorepass, String privkeypass, String privateSignatureKeyAlias,
            String privateEncryptionKeyAlias) {
        if (log.isTraceEnabled()) {
            log.trace(">restoreCAKeyStore");
        }
        try {
            // check authorization
            if (!accessSession.isAuthorizedNoLogging(authenticationToken, StandardRules.ROLE_ROOT.resource())) {
                final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtorestorecatoken",
                        caname);
                auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        authenticationToken.toString(), null, null, null, detailsMsg);
            }
            CA thisCa = caSession.getCAForEdit(authenticationToken, caname);
            final CAToken thisCAToken = thisCa.getCAToken();
            CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(thisCAToken.getCryptoTokenId());
            if (cryptoToken != null) {
                throw new Exception("CA already has an existing CryptoToken reference: " + cryptoToken.getId());
            }
            // load keystore from input
            KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
            keystore.load(new ByteArrayInputStream(p12file), keystorepass.toCharArray());
            // Extract signature keys
            if (privateSignatureKeyAlias == null || !keystore.isKeyEntry(privateSignatureKeyAlias)) {
                throw new Exception("Alias \"" + privateSignatureKeyAlias + "\" not found.");
            }
            Certificate[] signatureCertChain = KeyTools.getCertChain(keystore, privateSignatureKeyAlias);
            if (signatureCertChain.length < 1) {
                String msg = "Cannot load certificate chain with alias " + privateSignatureKeyAlias;
                log.error(msg);
                throw new Exception(msg);
            }
            Certificate caSignatureCertificate = (Certificate) signatureCertChain[0];
            PublicKey p12PublicSignatureKey = caSignatureCertificate.getPublicKey();
            PrivateKey p12PrivateSignatureKey = null;
            p12PrivateSignatureKey = (PrivateKey) keystore.getKey(privateSignatureKeyAlias,
                    privkeypass.toCharArray());

            // Extract encryption keys
            PrivateKey p12PrivateEncryptionKey = null;
            PublicKey p12PublicEncryptionKey = null;
            Certificate caEncryptionCertificate = null;
            if (privateEncryptionKeyAlias != null) {
                if (!keystore.isKeyEntry(privateEncryptionKeyAlias)) {
                    throw new Exception("Alias \"" + privateEncryptionKeyAlias + "\" not found.");
                }
                Certificate[] encryptionCertChain = KeyTools.getCertChain(keystore, privateEncryptionKeyAlias);
                if (encryptionCertChain.length < 1) {
                    String msg = "Cannot load certificate chain with alias " + privateEncryptionKeyAlias;
                    log.error(msg);
                    throw new Exception(msg);
                }
                caEncryptionCertificate = (Certificate) encryptionCertChain[0];
                p12PrivateEncryptionKey = (PrivateKey) keystore.getKey(privateEncryptionKeyAlias,
                        privkeypass.toCharArray());
                p12PublicEncryptionKey = caEncryptionCertificate.getPublicKey();
            } else {
                throw new Exception("Missing encryption key");
            }

            // Sign something to see that we are restoring the right private signature key
            String testSigAlg = (String) AlgorithmTools
                    .getSignatureAlgorithms(thisCa.getCACertificate().getPublicKey()).iterator().next();
            if (testSigAlg == null) {
                testSigAlg = "SHA1WithRSA";
            }
            // Sign with imported private key
            byte[] input = "Test data...".getBytes();
            Signature signature = Signature.getInstance(testSigAlg, "BC");
            signature.initSign(p12PrivateSignatureKey);
            signature.update(input);
            byte[] signed = signature.sign();
            // Verify with public key from CA certificate
            signature = Signature.getInstance(testSigAlg, "BC");
            signature.initVerify(thisCa.getCACertificate().getPublicKey());
            signature.update(input);
            if (!signature.verify(signed)) {
                throw new Exception("Could not use private key for verification. Wrong p12-file for this CA?");
            }
            // Import the keys and save to database
            CAToken catoken = importKeysToCAToken(authenticationToken, keystorepass, thisCAToken.getProperties(),
                    p12PrivateSignatureKey, p12PublicSignatureKey, p12PrivateEncryptionKey, p12PublicEncryptionKey,
                    signatureCertChain, thisCa.getCAId());
            thisCa.setCAToken(catoken);
            // Finally save the CA
            caSession.editCA(authenticationToken, thisCa, true);
            // Log
            final String detailsMsg = intres.getLocalizedMessage("caadmin.restoredcakeystore",
                    Integer.valueOf(thisCa.getCAId()));
            auditSession.log(EjbcaEventTypes.CA_RESTORETOKEN, EventStatus.SUCCESS, ModuleTypes.CA,
                    ServiceTypes.CORE, authenticationToken.toString(), String.valueOf(thisCa.getCAId()), null, null,
                    detailsMsg);
        } catch (Exception e) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errorrestorecakeystore", caname, "PKCS12",
                    e.getMessage());
            auditSession.log(EjbcaEventTypes.CA_RESTORETOKEN, EventStatus.FAILURE, ModuleTypes.CA,
                    ServiceTypes.CORE, authenticationToken.toString(), null, null, null, detailsMsg);
            throw new EJBException(e);
        }
        if (log.isTraceEnabled()) {
            log.trace("<restoreCAKeyStore");
        }
    }

    @Override
    public void importCAFromKeys(AuthenticationToken authenticationToken, String caname, String keystorepass,
            Certificate[] signatureCertChain, PublicKey p12PublicSignatureKey, PrivateKey p12PrivateSignatureKey,
            PrivateKey p12PrivateEncryptionKey, PublicKey p12PublicEncryptionKey)
            throws CryptoTokenAuthenticationFailedException, CryptoTokenOfflineException,
            IllegalCryptoTokenException, AuthorizationDeniedException, CAExistsException, CAOfflineException {
        // Transform into token
        int caId = StringTools.strip(CertTools.getSubjectDN(signatureCertChain[0])).hashCode(); // caid
        CAToken catoken = null;
        try {
            catoken = importKeysToCAToken(authenticationToken, keystorepass, null, p12PrivateSignatureKey,
                    p12PublicSignatureKey, p12PrivateEncryptionKey, p12PublicEncryptionKey, signatureCertChain,
                    caId);
        } catch (OperatorCreationException e) {
            log.error(e.getLocalizedMessage(), e);
            throw new EJBException(e);
        }
        log.debug("CA-Info: " + catoken.getSignatureAlgorithm() + " " + catoken.getEncryptionAlgorithm());
        // Identify the key algorithms for extended CA services, OCSP, XKMS, CMS
        String keyAlgorithm = AlgorithmTools.getKeyAlgorithm(p12PublicSignatureKey);
        String keySpecification = AlgorithmTools.getKeySpecification(p12PublicSignatureKey);
        if (keyAlgorithm == null || keyAlgorithm == AlgorithmConstants.KEYALGORITHM_RSA) {
            keyAlgorithm = AlgorithmConstants.KEYALGORITHM_RSA;
            keySpecification = "2048";
        }
        // Do the general import
        CA ca = importCA(authenticationToken, caname, keystorepass, signatureCertChain, catoken, keyAlgorithm,
                keySpecification);
        // Finally audit log
        String msg = intres.getLocalizedMessage("caadmin.importedca", caname, "PKCS12", ca.getStatus());
        Map<String, Object> details = new LinkedHashMap<String, Object>();
        details.put("msg", msg);
        auditSession.log(EjbcaEventTypes.CA_IMPORT, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                authenticationToken.toString(), String.valueOf(ca.getCAId()), null, null, details);
    }

    /**
     * Method that import CA token keys from a P12 file. Was originally used when upgrading from old EJBCA versions. Only supports SHA1 and SHA256
     * with RSA or ECDSA and SHA1 with DSA.
     * @throws OperatorCreationException 
     * @throws AuthorizationDeniedException 
     */
    private CAToken importKeysToCAToken(AuthenticationToken authenticationToken, String authenticationCode,
            Properties caTokenProperties, PrivateKey privatekey, PublicKey publickey,
            PrivateKey privateEncryptionKey, PublicKey publicEncryptionKey, Certificate[] caSignatureCertChain,
            int caId) throws CryptoTokenAuthenticationFailedException, IllegalCryptoTokenException,
            OperatorCreationException, AuthorizationDeniedException {
        // If we don't give an authentication code, perhaps we have autoactivation enabled
        if (StringUtils.isEmpty(authenticationCode)) {
            String msg = intres.getLocalizedMessage("token.authcodemissing", Integer.valueOf(caId));
            log.info(msg);
            throw new CryptoTokenAuthenticationFailedException(msg);
        }
        if (caTokenProperties == null) {
            caTokenProperties = new Properties();
        }

        try {
            // Currently only RSA keys are supported
            KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
            keystore.load(null, null);

            // The CAs certificate is first in chain
            Certificate cacert = caSignatureCertChain[0];
            // Assume that the same hash algorithm is used for signing that was used to sign this CA cert
            String signatureAlgorithm = AlgorithmTools.getSignatureAlgorithm(cacert);
            String keyAlg = AlgorithmTools.getKeyAlgorithm(publickey);
            if (keyAlg == null) {
                throw new IllegalCryptoTokenException(
                        "Unknown public key type: " + publickey.getAlgorithm() + " (" + publickey.getClass() + ")");
            }

            // import sign keys.
            final Certificate[] certchain = new Certificate[1];
            certchain[0] = CertTools.genSelfCert("CN=dummy", 36500, null, privatekey, publickey, signatureAlgorithm,
                    true);

            keystore.setKeyEntry(CAToken.SOFTPRIVATESIGNKEYALIAS, privatekey, null, certchain);

            // generate enc keys.
            // Encryption keys must be RSA still
            final String encryptionAlgorithm = AlgorithmTools.getEncSigAlgFromSigAlg(signatureAlgorithm);
            keyAlg = AlgorithmTools.getKeyAlgorithmFromSigAlg(encryptionAlgorithm);
            final String enckeyspec = "2048";
            KeyPair enckeys = null;
            if (publicEncryptionKey == null || privateEncryptionKey == null) {
                enckeys = KeyTools.genKeys(enckeyspec, keyAlg);
            } else {
                enckeys = new KeyPair(publicEncryptionKey, privateEncryptionKey);
            }
            // generate dummy certificate
            certchain[0] = CertTools.genSelfCert("CN=dummy2", 36500, null, enckeys.getPrivate(),
                    enckeys.getPublic(), encryptionAlgorithm, true);
            keystore.setKeyEntry(CAToken.SOFTPRIVATEDECKEYALIAS, enckeys.getPrivate(), null, certchain);

            // Set the token properties
            caTokenProperties.setProperty(CATokenConstants.CAKEYPURPOSE_CERTSIGN_STRING,
                    CAToken.SOFTPRIVATESIGNKEYALIAS);
            caTokenProperties.setProperty(CATokenConstants.CAKEYPURPOSE_CRLSIGN_STRING,
                    CAToken.SOFTPRIVATESIGNKEYALIAS);
            caTokenProperties.setProperty(CATokenConstants.CAKEYPURPOSE_DEFAULT_STRING,
                    CAToken.SOFTPRIVATEDECKEYALIAS);

            // Write the keystore to byte[] that we can feed to crypto token factory
            final char[] authCode = authenticationCode.toCharArray();
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            keystore.store(baos, authCode);

            // Now we have the PKCS12 keystore, from this we can create the CAToken
            final Properties cryptoTokenProperties = new Properties();
            int cryptoTokenId;
            try {
                cryptoTokenId = createCryptoTokenWithUniqueName(authenticationToken, "ImportedCryptoToken" + caId,
                        SoftCryptoToken.class.getName(), cryptoTokenProperties, baos.toByteArray(), authCode);
            } catch (NoSuchSlotException e1) {
                throw new RuntimeException(
                        "Attempte to define a slot for a soft crypto token. This should not happen.");
            }
            final CAToken catoken = new CAToken(cryptoTokenId, caTokenProperties);
            // If this is a CVC CA we need to find out the sequence
            String sequence = CAToken.DEFAULT_KEYSEQUENCE;
            if (cacert instanceof CardVerifiableCertificate) {
                CardVerifiableCertificate cvccacert = (CardVerifiableCertificate) cacert;
                log.debug("Getting sequence from holderRef in CV certificate.");
                try {
                    sequence = cvccacert.getCVCertificate().getCertificateBody().getHolderReference().getSequence();
                } catch (NoSuchFieldException e) {
                    log.error("Can not get sequence from holderRef in CV certificate, using default sequence.");
                }
            }
            log.debug("Setting sequence " + sequence);
            catoken.setKeySequence(sequence);
            log.debug("Setting default sequence format " + StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
            catoken.setKeySequenceFormat(StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
            catoken.setSignatureAlgorithm(signatureAlgorithm);
            catoken.setEncryptionAlgorithm(encryptionAlgorithm);
            return catoken;
        } catch (KeyStoreException e) {
            throw new IllegalCryptoTokenException(e);
        } catch (NoSuchProviderException e) {
            throw new IllegalCryptoTokenException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalCryptoTokenException(e);
        } catch (CertificateException e) {
            throw new IllegalCryptoTokenException(e);
        } catch (IOException e) {
            throw new IllegalCryptoTokenException(e);
        } catch (IllegalStateException e) {
            throw new IllegalCryptoTokenException(e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new IllegalCryptoTokenException(e);
        } catch (CryptoTokenOfflineException e) {
            throw new IllegalCryptoTokenException(e);
        }
    } // importKeys

    @Override
    public void importCAFromHSM(AuthenticationToken authenticationToken, String caname,
            Certificate[] signatureCertChain, String catokenpassword, String catokenclasspath,
            String catokenproperties) throws CryptoTokenOfflineException, CryptoTokenAuthenticationFailedException,
            IllegalCryptoTokenException, AuthorizationDeniedException, CAExistsException, CAOfflineException,
            NoSuchSlotException {
        Certificate cacert = signatureCertChain[0];
        int caId = StringTools.strip(CertTools.getSubjectDN(cacert)).hashCode();
        Properties caTokenProperties = CAToken.getPropertiesFromString(catokenproperties);
        // Create the CryptoToken
        int cryptoTokenId = createCryptoTokenWithUniqueName(authenticationToken, "ImportedCryptoToken" + caId,
                PKCS11CryptoToken.class.getName(), caTokenProperties, null, catokenpassword.toCharArray());
        final CAToken catoken = new CAToken(cryptoTokenId, caTokenProperties);
        // Set a lot of properties on the crypto token

        // If this is a CVC CA we need to find out the sequence
        String signatureAlgorithm = AlgorithmTools.getSignatureAlgorithm(cacert);
        String sequence = CAToken.DEFAULT_KEYSEQUENCE;
        if (cacert instanceof CardVerifiableCertificate) {
            CardVerifiableCertificate cvccacert = (CardVerifiableCertificate) cacert;
            log.debug("Getting sequence from holderRef in CV certificate.");
            try {
                sequence = cvccacert.getCVCertificate().getCertificateBody().getHolderReference().getSequence();
            } catch (NoSuchFieldException e) {
                log.error("Can not get sequence from holderRef in CV certificate, using default sequence.");
            }
        }
        log.debug("Setting sequence " + sequence);
        catoken.setKeySequence(sequence);
        log.debug("Setting default sequence format " + StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
        catoken.setKeySequenceFormat(StringTools.KEY_SEQUENCE_FORMAT_NUMERIC);
        catoken.setSignatureAlgorithm(signatureAlgorithm);
        // Encryption keys must be RSA still
        String encryptionAlgorithm = AlgorithmTools.getEncSigAlgFromSigAlg(signatureAlgorithm);
        catoken.setEncryptionAlgorithm(encryptionAlgorithm);
        // Identify the key algorithms for extended CA services, OCSP, XKMS, CMS
        String keyAlgorithm = AlgorithmTools.getKeyAlgorithm(cacert.getPublicKey());
        String keySpecification = AlgorithmTools.getKeySpecification(cacert.getPublicKey());
        if (keyAlgorithm == null || keyAlgorithm == AlgorithmConstants.KEYALGORITHM_RSA) {
            keyAlgorithm = AlgorithmConstants.KEYALGORITHM_RSA;
            keySpecification = "2048";
        }
        // Do the general import
        importCA(authenticationToken, caname, catokenpassword, signatureCertChain, catoken, keyAlgorithm,
                keySpecification);
    }

    /** Wrapper for CryptoToken creation that tries to find a unique CryptoTokenName 
     * @throws NoSuchSlotException if no slot with the given label could be found
     */
    private int createCryptoTokenWithUniqueName(AuthenticationToken authenticationToken, String basename,
            String className, Properties cryptoTokenProperties, byte[] data, char[] authCode)
            throws CryptoTokenOfflineException, CryptoTokenAuthenticationFailedException,
            AuthorizationDeniedException, NoSuchSlotException {
        int cryptoTokenId = 0;
        final int maxTriesToFindUnusedCryptoTokenName = 25;
        String postFix = "";
        for (int i = 0; cryptoTokenId == 0 && i < maxTriesToFindUnusedCryptoTokenName; i++) {
            String cryptoTokenName = basename + postFix;
            try {
                cryptoTokenId = cryptoTokenManagementSession.createCryptoToken(authenticationToken, cryptoTokenName,
                        className, cryptoTokenProperties, data, authCode);
            } catch (CryptoTokenNameInUseException e) {
                log.info("CryptoToken with name '"
                        + "' could not be created since the name exists. Trying another name.");
                postFix = "_" + i;
            }
        }
        if (cryptoTokenId == 0) {
            final String msg = "Failed to create a CryptoToken with a unique name after "
                    + maxTriesToFindUnusedCryptoTokenName;
            log.error(msg);
            throw new RuntimeException(msg);
        }
        return cryptoTokenId;
    }

    /**
     * @param keyAlgorithm keyalgorithm for extended CA services, OCSP, XKMS, CMS. Example AlgorithmConstants.KEYALGORITHM_RSA
     * @param keySpecification keyspecification for extended CA services, OCSP, XKMS, CMS. Example 2048
     * @throws AuthorizationDeniedException if imported CA was signed by a CA user does not have authorization to.
     * @throws CADoesntExistsException if superCA does not exist
     * @throws CAExistsException if the CA already exists
     * @throws CAOfflineException if CRLs can not be generated because imported CA did not manage to get online
     * @throws CryptoTokenAuthenticationFailedException if authentication to crypto token failed
     * @throws IllegalCryptoTokenException if CA certificate was not self signed, and chain length > 1 
     * @throws CryptoTokenOfflineException if crypto token is unavailable. 
     * 
     */
    private CA importCA(AuthenticationToken admin, String caname, String keystorepass,
            Certificate[] signatureCertChain, CAToken catoken, String keyAlgorithm, String keySpecification)
            throws CryptoTokenAuthenticationFailedException, CryptoTokenOfflineException,
            IllegalCryptoTokenException, AuthorizationDeniedException, CAExistsException, CAOfflineException {
        // Create a new CA
        int signedby = CAInfo.SIGNEDBYEXTERNALCA;
        int certprof = CertificateProfileConstants.CERTPROFILE_FIXED_SUBCA;
        String description = "Imported external signed CA";
        Certificate caSignatureCertificate = signatureCertChain[0];
        ArrayList<Certificate> certificatechain = new ArrayList<Certificate>();
        for (int i = 0; i < signatureCertChain.length; i++) {
            certificatechain.add(signatureCertChain[i]);
        }
        if (signatureCertChain.length == 1) {
            if (verifyIssuer(caSignatureCertificate, caSignatureCertificate)) {
                signedby = CAInfo.SELFSIGNED;
                certprof = CertificateProfileConstants.CERTPROFILE_FIXED_ROOTCA;
                description = "Imported root CA";
            } else {
                // A less strict strategy can be to assume certificate signed
                // by an external CA. Useful if admin user forgot to create a
                // full certificate chain in PKCS#12 package.
                log.error("Cannot import CA " + CertTools.getSubjectDN(caSignatureCertificate) + ": certificate "
                        + CertTools.getSerialNumberAsString(caSignatureCertificate) + " is not self-signed.");
                throw new IllegalCryptoTokenException(
                        "Cannot import CA " + CertTools.getSubjectDN(caSignatureCertificate)
                                + ": certificate is not self-signed. Check " + "certificate chain in PKCS#12");
            }
        } else if (signatureCertChain.length > 1) {
            // Assuming certificate chain in forward direction (from target
            // to most-trusted CA). Multiple CA chains can contains the
            // issuer certificate; so only the chain where target certificate
            // is the issuer will be selected.
            for (int caid : caSession.getAllCaIds()) {
                CAInfo superCaInfo;
                try {
                    superCaInfo = caSession.getCAInfo(admin, caid);
                } catch (CADoesntExistsException e) {
                    throw new IllegalStateException(
                            "Newly retrieved CA " + caid + " does not exist in the system.");
                }
                Iterator<Certificate> i = superCaInfo.getCertificateChain().iterator();
                if (i.hasNext()) {
                    Certificate superCaCert = i.next();
                    if (verifyIssuer(caSignatureCertificate, superCaCert)) {
                        signedby = caid;
                        description = "Imported sub CA";
                        break;
                    }
                }
            }
        }

        CAInfo cainfo = null;
        CA ca = null;
        int validity = (int) ((CertTools.getNotAfter(caSignatureCertificate).getTime()
                - CertTools.getNotBefore(caSignatureCertificate).getTime()) / (24 * 3600 * 1000));
        ArrayList<ExtendedCAServiceInfo> extendedcaservices = new ArrayList<ExtendedCAServiceInfo>();
        ArrayList<Integer> approvalsettings = new ArrayList<Integer>();
        ArrayList<Integer> crlpublishers = new ArrayList<Integer>();
        if (caSignatureCertificate instanceof X509Certificate) {
            // Create an X509CA
            // Create and active extended CA Services (XKMS, CMS).
            // Create and active XKMS CA Service.
            extendedcaservices.add(new XKMSCAServiceInfo(ExtendedCAServiceInfo.STATUS_INACTIVE,
                    "CN=XKMSCertificate, " + CertTools.getSubjectDN(caSignatureCertificate), "", keySpecification,
                    keyAlgorithm));
            // Create and active CMS CA Service.
            extendedcaservices.add(new CmsCAServiceInfo(ExtendedCAServiceInfo.STATUS_INACTIVE,
                    "CN=CMSCertificate, " + CertTools.getSubjectDN(caSignatureCertificate), "", keySpecification,
                    keyAlgorithm));

            cainfo = new X509CAInfo(CertTools.getSubjectDN(caSignatureCertificate), caname, CAConstants.CA_ACTIVE,
                    certprof, validity, signedby, certificatechain, catoken);
            cainfo.setExpireTime(CertTools.getNotAfter(caSignatureCertificate));
            cainfo.setDescription(description);
            cainfo.setCRLPublishers(crlpublishers);
            cainfo.setExtendedCAServiceInfos(extendedcaservices);
            cainfo.setApprovalSettings(approvalsettings);
            ca = new X509CA((X509CAInfo) cainfo);
        } else if (caSignatureCertificate.getType().equals("CVC")) {
            // Create a CVC CA
            // Create the CAInfo to be used for either generating the whole CA
            // or making a request
            cainfo = new CVCCAInfo(CertTools.getSubjectDN(caSignatureCertificate), caname, CAConstants.CA_ACTIVE,
                    certprof, validity, signedby, certificatechain, catoken);
            cainfo.setExpireTime(CertTools.getNotAfter(caSignatureCertificate));
            cainfo.setDescription(description);
            cainfo.setCRLPublishers(crlpublishers);
            cainfo.setExtendedCAServiceInfos(extendedcaservices);
            cainfo.setApprovalSettings(approvalsettings);
            ca = CvcCA.getInstance((CVCCAInfo) cainfo);
        }
        // We must activate the token, in case it does not have the default password
        final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(catoken.getCryptoTokenId());
        cryptoToken.activate(keystorepass.toCharArray());
        try {
            ca.setCAToken(catoken);
        } catch (InvalidAlgorithmException e) {
            throw new IllegalCryptoTokenException(e);
        }
        ca.setCertificateChain(certificatechain);
        log.debug("CA-Info: " + catoken.getSignatureAlgorithm() + " " + ca.getCAToken().getEncryptionAlgorithm());
        // Publish CA certificates.
        publishCACertificate(admin, ca.getCertificateChain(), ca.getCRLPublishers(), ca.getSubjectDN());
        // activate External CA Services
        activateAndPublishExternalCAServices(admin, cainfo.getExtendedCAServiceInfos(), ca);
        // Store CA in database.
        caSession.addCA(admin, ca);

        // Create initial CRLs
        try {
            publishingCrlSession.forceCRL(admin, ca.getCAId());
            publishingCrlSession.forceDeltaCRL(admin, ca.getCAId());
        } catch (CADoesntExistsException e) {
            throw new IllegalStateException(
                    "Newly created CA with ID: " + ca.getCAId() + " was not found in database.");
        }

        return ca;
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public byte[] exportCAKeyStore(AuthenticationToken admin, String caname, String keystorepass,
            String privkeypass, String privateSignatureKeyAlias, String privateEncryptionKeyAlias) {
        log.trace(">exportCAKeyStore");
        try {
            final CA thisCa = caSession.getCAForEdit(admin, caname);
            // Make sure we are not trying to export a hard or invalid token
            CAToken thisCAToken = thisCa.getCAToken();
            final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(thisCAToken.getCryptoTokenId());
            if (!(cryptoToken instanceof SoftCryptoToken)) {
                throw new IllegalCryptoTokenException("Cannot export anything but a soft token.");
            }
            // Do not allow export without password protection
            if (StringUtils.isEmpty(keystorepass) || StringUtils.isEmpty(privkeypass)) {
                throw new IllegalArgumentException("Cannot export a token without password protection.");
            }
            // Check authorization
            if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) {
                String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoexportcatoken", caname);
                Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                        admin.toString(), String.valueOf(thisCa.getCAId()), null, null, details);
                throw new AuthorizationDeniedException(msg);
            }
            // Fetch keys
            final char[] password = keystorepass.toCharArray();
            ((SoftCryptoToken) cryptoToken).checkPasswordBeforeExport(password);
            cryptoToken.activate(password);

            PrivateKey p12PrivateEncryptionKey = cryptoToken
                    .getPrivateKey(thisCAToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_KEYENCRYPT));
            PublicKey p12PublicEncryptionKey = cryptoToken
                    .getPublicKey(thisCAToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_KEYENCRYPT));
            PrivateKey p12PrivateCertSignKey = cryptoToken
                    .getPrivateKey(thisCAToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN));
            PrivateKey p12PrivateCRLSignKey = cryptoToken
                    .getPrivateKey(thisCAToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CRLSIGN));
            if (!p12PrivateCertSignKey.equals(p12PrivateCRLSignKey)) {
                throw new Exception("Assertion of equal signature keys failed.");
            }
            // Proceed with the export
            byte[] ret = null;
            String format = null;
            if (thisCa.getCAType() == CAInfo.CATYPE_CVC) {
                log.debug("Exporting private key with algorithm: " + p12PrivateCertSignKey.getAlgorithm()
                        + " of format: " + p12PrivateCertSignKey.getFormat());
                format = p12PrivateCertSignKey.getFormat();
                ret = p12PrivateCertSignKey.getEncoded();
            } else {
                log.debug("Exporting PKCS12 keystore");
                format = "PKCS12";
                KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
                keystore.load(null, keystorepass.toCharArray());
                // Load keys into keystore
                Certificate[] certificateChainSignature = (Certificate[]) thisCa.getCertificateChain()
                        .toArray(new Certificate[0]);
                Certificate[] certificateChainEncryption = new Certificate[1];
                // certificateChainSignature[0].getSigAlgName(),
                // generate dummy certificate for encryption key.
                certificateChainEncryption[0] = CertTools.genSelfCertForPurpose("CN=dummy2", 36500, null,
                        p12PrivateEncryptionKey, p12PublicEncryptionKey, thisCAToken.getEncryptionAlgorithm(), true,
                        X509KeyUsage.keyEncipherment, true);
                log.debug("Exporting with sigAlgorithm "
                        + AlgorithmTools.getSignatureAlgorithm(certificateChainSignature[0]) + "encAlgorithm="
                        + thisCAToken.getEncryptionAlgorithm());
                if (keystore.isKeyEntry(privateSignatureKeyAlias)) {
                    throw new Exception("Key \"" + privateSignatureKeyAlias + "\"already exists in keystore.");
                }
                if (keystore.isKeyEntry(privateEncryptionKeyAlias)) {
                    throw new Exception("Key \"" + privateEncryptionKeyAlias + "\"already exists in keystore.");
                }

                keystore.setKeyEntry(privateSignatureKeyAlias, p12PrivateCertSignKey, privkeypass.toCharArray(),
                        certificateChainSignature);
                keystore.setKeyEntry(privateEncryptionKeyAlias, p12PrivateEncryptionKey, privkeypass.toCharArray(),
                        certificateChainEncryption);
                // Return KeyStore as byte array and clean up
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                keystore.store(baos, keystorepass.toCharArray());
                if (keystore.isKeyEntry(privateSignatureKeyAlias)) {
                    keystore.deleteEntry(privateSignatureKeyAlias);
                }
                if (keystore.isKeyEntry(privateEncryptionKeyAlias)) {
                    keystore.deleteEntry(privateEncryptionKeyAlias);
                }
                ret = baos.toByteArray();
            }
            String msg = intres.getLocalizedMessage("caadmin.exportedca", caname, format);
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EjbcaEventTypes.CA_EXPORTTOKEN, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(thisCa.getCAId()), null, null, details);
            log.trace("<exportCAKeyStore");
            return ret;
        } catch (Exception e) {
            String msg = intres.getLocalizedMessage("caadmin.errorexportca", caname, "PKCS12", e.getMessage());
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EjbcaEventTypes.CA_EXPORTTOKEN, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), null, null, null, details);
            throw new EJBException(e);
        }
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public Collection<Certificate> getAllCACertificates() {
        final ArrayList<Certificate> returnval = new ArrayList<Certificate>();
        for (final Integer caid : caSession.getAllCaIds()) {
            try {
                final CAInfo caInfo = caSession.getCAInfoInternal(caid.intValue(), null, true);
                if (log.isDebugEnabled()) {
                    log.debug("Getting certificate chain for CA: " + caInfo.getName() + ", " + caInfo.getCAId());
                }
                final Certificate caCertificate = caInfo.getCertificateChain().iterator().next();
                returnval.add(caCertificate);
            } catch (CADoesntExistsException e) {
                log.error("\"Available\" CA does not exist! caid=" + caid);
            }
        }
        return returnval;
    }

    @Override
    public void activateCAService(AuthenticationToken admin, int caid) throws AuthorizationDeniedException,
            ApprovalException, WaitingForApprovalException, CADoesntExistsException {
        // Authorize
        if (!accessSession.isAuthorizedNoLogging(admin, AccessRulesConstants.REGULAR_ACTIVATECA)) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtoactivatetoken",
                    Integer.valueOf(caid));
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new AuthorizationDeniedException(detailsMsg);
        }
        // Get CA also check authorization for this specific CA
        final CAInfo cainfo = caSession.getCAInfo(admin, caid);
        if (cainfo.getStatus() == CAConstants.CA_EXTERNAL) {
            log.info(intres.getLocalizedMessage("caadmin.catokenexternal", Integer.valueOf(caid)));
            return;
        }
        // Check if approvals is required.
        final int numOfApprovalsRequired = getNumOfApprovalRequired(CAInfo.REQ_APPROVAL_ACTIVATECA,
                cainfo.getCAId(), cainfo.getCertificateProfileId());
        final ActivateCATokenApprovalRequest ar = new ActivateCATokenApprovalRequest(cainfo.getName(), "", admin,
                numOfApprovalsRequired, caid, ApprovalDataVO.ANY_ENDENTITYPROFILE);
        if (ApprovalExecutorUtil.requireApproval(ar, NONAPPROVABLECLASSNAMES_ACTIVATECATOKEN)) {
            approvalSession.addApprovalRequest(admin, ar);
            throw new WaitingForApprovalException(intres.getLocalizedMessage("ra.approvalcaactivation"));
        }
        if (cainfo.getStatus() == CAConstants.CA_OFFLINE) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.activated", caid);
            auditSession.log(EventTypes.CA_SERVICEACTIVATE, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            CA ca = caSession.getCAForEdit(admin, caid);
            ca.setStatus(CAConstants.CA_ACTIVE);
            caSession.editCA(admin, ca, false);
        } else {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errornotoffline", cainfo.getName());
            auditSession.log(EventTypes.CA_SERVICEACTIVATE, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new RuntimeException(detailsMsg);
        }
    }

    private static final ApprovalOveradableClassName[] NONAPPROVABLECLASSNAMES_ACTIVATECATOKEN = {
            new ApprovalOveradableClassName(
                    org.ejbca.core.model.approval.approvalrequests.ActivateCATokenApprovalRequest.class.getName(),
                    null), };

    @Override
    public void deactivateCAService(AuthenticationToken admin, int caid)
            throws AuthorizationDeniedException, CADoesntExistsException {
        // Authorize
        if (!accessSession.isAuthorizedNoLogging(admin, AccessRulesConstants.REGULAR_ACTIVATECA)) {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.notauthorizedtodeactivatetoken",
                    Integer.valueOf(caid));
            auditSession.log(EventTypes.ACCESS_CONTROL, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new AuthorizationDeniedException(detailsMsg);
        }
        final CA ca = caSession.getCAForEdit(admin, caid);
        if (ca.getStatus() == CAConstants.CA_ACTIVE) {
            ca.setStatus(CAConstants.CA_OFFLINE);
            caSession.editCA(admin, ca, false);
            final String detailsMsg = intres.getLocalizedMessage("caadmin.deactivated", caid);
            auditSession.log(EventTypes.CA_SERVICEDEACTIVATE, EventStatus.SUCCESS, ModuleTypes.CA,
                    ServiceTypes.CORE, admin.toString(), String.valueOf(caid), null, null, detailsMsg);
        } else {
            final String detailsMsg = intres.getLocalizedMessage("caadmin.errornotonline", ca.getName());
            auditSession.log(EventTypes.CA_SERVICEDEACTIVATE, EventStatus.FAILURE, ModuleTypes.CA,
                    ServiceTypes.CORE, admin.toString(), String.valueOf(caid), null, null, detailsMsg);
            throw new RuntimeException(detailsMsg);

        }
    }

    /** Method used to check if certificate profile id exists in any CA. */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public List<String> getCAsUsingCertificateProfile(final int certificateprofileid) {
        List<String> result = new ArrayList<String>();
        for (final Integer caid : caSession.getAllCaIds()) {
            try {
                final CAInfo caInfo = caSession.getCAInfoInternal(caid.intValue(), null, true);
                if (caInfo.getCertificateProfileId() == certificateprofileid) {
                    result.add(caInfo.getName());
                }
            } catch (CADoesntExistsException e) {
                log.error("\"Available\" CA is no longer available. caid=" + caid.toString());
            }
        }
        return result;
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public boolean exitsPublisherInCAs(AuthenticationToken admin, int publisherid) {
        try {
            for (final Integer caid : caSession.getAuthorizedCaIds(admin)) {
                for (final Integer pubInt : caSession.getCA(admin, caid).getCRLPublishers()) {
                    if (pubInt.intValue() == publisherid) {
                        // We have found a match. No point in looking for more..
                        return true;
                    }
                }
            }
        } catch (CADoesntExistsException e) {
            throw new RuntimeException("Available CA is no longer available!");
        } catch (AuthorizationDeniedException e) {
            throw new RuntimeException("No longer authorized to authorized CA!");
        }
        return false;
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public int getNumOfApprovalRequired(final int action, final int caid, final int certProfileId) {
        int retval = 0;
        try {
            // No need to do access control here on the CA, we are just internally retrieving a value
            // to be used to see if approvals are needed.
            final CAInfo cainfo = caSession.getCAInfoInternal(caid, null, true);
            if (cainfo.isApprovalRequired(action)) {
                retval = cainfo.getNumOfReqApprovals();
            }
            final CertificateProfile certprofile = certificateProfileSession.getCertificateProfile(certProfileId);
            if (certprofile != null && certprofile.isApprovalRequired(action)) {
                retval = Math.max(retval, certprofile.getNumOfReqApprovals());
            }
        } catch (CADoesntExistsException e) {
            // NOPMD ignore cainfo is null
        }
        return retval;
    }

    @Override
    public void publishCACertificate(AuthenticationToken admin, Collection<Certificate> certificatechain,
            Collection<Integer> usedpublishers, String caDataDN) throws AuthorizationDeniedException {

        Object[] certs = certificatechain.toArray();
        for (int i = 0; i < certs.length; i++) {
            Certificate cert = (Certificate) certs[i];
            String fingerprint = CertTools.getFingerprintAsString(cert);
            // CA fingerprint, figure out the value if this is not a root CA
            String cafp = fingerprint;
            // Calculate the certificate type
            boolean isSelfSigned = CertTools.isSelfSigned(cert);
            int type = CertificateConstants.CERTTYPE_ENDENTITY;
            if (CertTools.isCA(cert)) {
                // this is a CA
                if (isSelfSigned) {
                    type = CertificateConstants.CERTTYPE_ROOTCA;
                } else {
                    type = CertificateConstants.CERTTYPE_SUBCA;
                    // If not a root CA, the next certificate in the chain
                    // should be the CA of this CA
                    if ((i + 1) < certs.length) {
                        Certificate cacert = (Certificate) certs[i + 1];
                        cafp = CertTools.getFingerprintAsString(cacert);
                    }
                }
            } else if (isSelfSigned) {
                // If we don't have basic constraints, but is self signed,
                // we are still a CA, just a stupid CA
                type = CertificateConstants.CERTTYPE_ROOTCA;
            } else {
                // If and end entity, the next certificate in the chain
                // should be the CA of this end entity
                if ((i + 1) < certs.length) {
                    Certificate cacert = (Certificate) certs[i + 1];
                    cafp = CertTools.getFingerprintAsString(cacert);
                }
            }

            String name = "SYSTEMCERT";
            if (type != CertificateConstants.CERTTYPE_ENDENTITY) {
                name = "SYSTEMCA";
            }
            // Store CA certificate in the database if it does not exist
            long updateTime = new Date().getTime();
            int profileId = 0;
            String tag = null;
            CertificateInfo ci = certificateStoreSession.getCertificateInfo(fingerprint);
            if (ci == null) {
                // If we don't have it in the database, store it setting
                // certificateProfileId = 0 and tag = null
                certificateStoreSession.storeCertificate(admin, cert, name, cafp, CertificateConstants.CERT_ACTIVE,
                        type, profileId, tag, updateTime);
                certificateStoreSession.reloadCaCertificateCache();
            } else {
                updateTime = ci.getUpdateTime().getTime();
                profileId = ci.getCertificateProfileId();
                tag = ci.getTag();
            }
            if (usedpublishers != null) {
                publisherSession.storeCertificate(admin, usedpublishers, cert, cafp, null, caDataDN, fingerprint,
                        CertificateConstants.CERT_ACTIVE, type, -1, RevokedCertInfo.NOT_REVOKED, tag, profileId,
                        updateTime, null);
            }
        }

    }

    @Override
    public void publishCRL(AuthenticationToken admin, Certificate caCert, Collection<Integer> usedpublishers,
            String caDataDN, boolean doPublishDeltaCRL) throws AuthorizationDeniedException {
        if (usedpublishers == null) {
            return;
        }
        // Store crl in ca CRL publishers.
        if (log.isDebugEnabled()) {
            log.debug("Storing CRL in publishers");
        }
        final String issuerDN = CertTools.getSubjectDN(caCert);
        final String caCertFingerprint = CertTools.getFingerprintAsString(caCert);
        final byte crl[] = crlStoreSession.getLastCRL(issuerDN, false);
        if (crl != null) {
            final int nr = crlStoreSession.getLastCRLInfo(issuerDN, false).getLastCRLNumber();
            publisherSession.storeCRL(admin, usedpublishers, crl, caCertFingerprint, nr, caDataDN);
        }
        if (!doPublishDeltaCRL) {
            return;
        }
        final byte deltaCrl[] = crlStoreSession.getLastCRL(issuerDN, true);
        if (deltaCrl != null) {
            final int nr = crlStoreSession.getLastCRLInfo(issuerDN, true).getLastCRLNumber();
            publisherSession.storeCRL(admin, usedpublishers, deltaCrl, caCertFingerprint, nr, caDataDN);
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public Set<Integer> getAuthorizedPublisherIds(final AuthenticationToken admin) {
        // Set to use to track all authorized publisher IDs
        final Set<Integer> result = new HashSet<Integer>();
        // Find all publishers, use this set to track unowned publishers
        final Map<Integer, BasePublisher> allPublishers = publisherSession.getAllPublishers();

        //Firstly, weed out all publishers which we lack authorization to 
        for (Integer key : new HashSet<Integer>(allPublishers.keySet())) {
            BasePublisher publisher = allPublishers.get(key);
            if (publisher instanceof CustomPublisherContainer) {
                final CustomPublisherContainer custompublisherdata = ((CustomPublisherContainer) publisher);
                if (custompublisherdata.isCustomAccessRulesSupported()) {
                    if (!custompublisherdata.isAuthorizedToPublisher(admin)) {
                        allPublishers.remove(key);
                    }
                }
            }
        }

        //Secondly, find all CAs
        for (final int caId : caSession.getAllCaIds()) {
            boolean authorizedToCa = caSession.authorizedToCA(admin, caId);
            try {
                Collection<Integer> crlPublishers = caSession.getCAInfoInternal(caId).getCRLPublishers();
                if (crlPublishers != null) {
                    // TODO: Logically getCRLPublishers() should return an empty list if empty, but that's a change for another day
                    for (Integer caPublisherId : crlPublishers) {
                        //This publisher is owned by a CA 
                        allPublishers.remove(caPublisherId);
                        if (authorizedToCa) {
                            //Admin has access to the CA, so return it as a result. 
                            result.add(caPublisherId);
                        }
                    }
                }
            } catch (CADoesntExistsException e) {
                // NOPMD: This can't happen
            }
        }
        //Any remaining publishers must be unowned, so add them in as well. 
        result.addAll(allPublishers.keySet());
        return result;
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public String healthCheck() {
        final StringBuilder sb = new StringBuilder();
        final boolean caTokenSignTest = EjbcaConfiguration.getHealthCheckCaTokenSignTest();
        if (log.isDebugEnabled()) {
            log.debug("CaTokenSignTest: " + caTokenSignTest);
        }
        final HashMap<Integer, CryptoToken> cryptoTokenMap = new HashMap<Integer, CryptoToken>();
        for (final Integer caid : caSession.getAllCaIds()) {
            try {
                final CAInfo cainfo = caSession.getCAInfoInternal(caid.intValue());
                if (cainfo.getStatus() == CAConstants.CA_ACTIVE && cainfo.getIncludeInHealthCheck()) {
                    // Verify that the CA's mapped keys exist and optionally that the test-key is usable
                    final int cryptoTokenId = cainfo.getCAToken().getCryptoTokenId();
                    CryptoToken cryptoToken = cryptoTokenMap.get(Integer.valueOf(cryptoTokenId));
                    if (cryptoToken == null) {
                        cryptoToken = cryptoTokenSession.getCryptoToken(cryptoTokenId);
                        if (cryptoToken != null) {
                            // Cache crypto token lookup locally since multiple CA might use the same and milliseconds count here
                            cryptoTokenMap.put(Integer.valueOf(cryptoTokenId), cryptoToken);
                        }
                    }
                    final int tokenstatus = cainfo.getCAToken().getTokenStatus(caTokenSignTest, cryptoToken);
                    if (tokenstatus == CryptoToken.STATUS_OFFLINE) {
                        sb.append("\nCA: Error CA Token is disconnected, CA Name : ").append(cainfo.getName());
                        log.error("Error CA Token is disconnected, CA Name : " + cainfo.getName());
                    }
                }
            } catch (CADoesntExistsException e) {
                if (log.isDebugEnabled()) {
                    log.debug("CA with id '" + caid.toString() + "' does not exist.");
                }
            }
        }
        return sb.toString();
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public ExtendedCAServiceResponse extendedService(AuthenticationToken admin, int caid,
            ExtendedCAServiceRequest request)
            throws ExtendedCAServiceRequestException, IllegalExtendedCAServiceRequestException,
            ExtendedCAServiceNotActiveException, CADoesntExistsException, AuthorizationDeniedException,
            CertificateEncodingException, CertificateException, OperatorCreationException {
        // Get CA that will process request
        final CA ca = caSession.getCA(admin, caid);
        if (log.isDebugEnabled()) {
            log.debug("Extended service with request class '" + request.getClass().getName() + "' called for CA '"
                    + ca.getName() + "'");
        }
        // We do not yet support using a separate crypto token for key recovery, although we have it stored in the key recovery entry
        // so everything is prepared for this possibility.
        final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(ca.getCAToken().getCryptoTokenId());
        final ExtendedCAServiceResponse resp = ca.extendedService(cryptoToken, request);
        final String msg = intres.getLocalizedMessage("caadmin.extendedserviceexecuted",
                request.getClass().getName(), ca.getName());
        final Map<String, Object> details = new LinkedHashMap<String, Object>();
        details.put("msg", msg);
        auditSession.log(EjbcaEventTypes.CA_EXTENDEDSERVICE, EventStatus.SUCCESS, ModuleTypes.CA,
                EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(caid), null, null, details);
        return resp;
    }

    //
    // Private methods
    //

    /**
     * Check if subject certificate is signed by issuer certificate. Used in
     * 
     * @see #upgradeFromOldCAKeyStore(Admin, String, byte[], char[], char[], String). This method does a lazy check: if signature verification failed
     *      for any reason that prevent verification, e.g. signature algorithm not supported, method returns false. Author: Marco Ferrante
     * 
     * @param subject Subject certificate
     * @param issuer Issuer certificate
     * @return true if subject certificate is signed by issuer certificate
     * @throws java.lang.Exception
     */
    private boolean verifyIssuer(Certificate subject, Certificate issuer) {
        try {
            PublicKey issuerKey = issuer.getPublicKey();
            subject.verify(issuerKey);
            return true;
        } catch (java.security.GeneralSecurityException e) {
            return false;
        }
    }

    /**
     * Checks the signer validity given a CA object and throws an EJBException to the caller.
     * This should only be called from create and edit CA methods.
     * 
     * @param admin administrator calling the method
     * @param signca a CA object of the signer to be checked
     * @throws EJBException embedding a CertificateExpiredException or a CertificateNotYetValidException if the certificate has expired or is not yet
     *             valid
     */
    private void assertSignerValidity(AuthenticationToken admin, CA signca) {
        // Check validity of signers certificate
        final Certificate signcert = (Certificate) signca.getCACertificate();
        try {
            CertTools.checkValidity(signcert, new Date());
        } catch (CertificateExpiredException ce) {
            // Signers Certificate has expired.
            String msg = intres.getLocalizedMessage("signsession.caexpired", signca.getSubjectDN());
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EjbcaEventTypes.CA_VALIDITY, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(signca.getCAId()), null, null, details);
            throw new EJBException(ce);
        } catch (CertificateNotYetValidException cve) {
            String msg = intres.getLocalizedMessage("signsession.canotyetvalid", signca.getSubjectDN());
            Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            auditSession.log(EjbcaEventTypes.CA_VALIDITY, EventStatus.FAILURE, ModuleTypes.CA, ServiceTypes.CORE,
                    admin.toString(), String.valueOf(signca.getCAId()), null, null, details);
            throw new EJBException(cve);
        }
    }

    /**
     * Helper method that activates CA services and publisher their certificates, if the services are marked as active
     * 
     * @throws AuthorizationDeniedException
     */
    private void activateAndPublishExternalCAServices(AuthenticationToken admin,
            Collection<ExtendedCAServiceInfo> extendedCAServiceInfos, CA ca) throws AuthorizationDeniedException {
        // activate External CA Services
        Iterator<ExtendedCAServiceInfo> iter = extendedCAServiceInfos.iterator();
        while (iter.hasNext()) {
            ExtendedCAServiceInfo info = (ExtendedCAServiceInfo) iter.next();
            ArrayList<Certificate> certificates = new ArrayList<Certificate>();
            if (info instanceof XKMSCAServiceInfo) {
                try {
                    final CryptoToken cryptoToken = cryptoTokenSession
                            .getCryptoToken(ca.getCAToken().getCryptoTokenId());
                    ca.initExtendedService(cryptoToken, ExtendedCAServiceTypes.TYPE_XKMSEXTENDEDSERVICE, ca);
                    final List<Certificate> certPath = ((XKMSCAServiceInfo) ca
                            .getExtendedCAServiceInfo(ExtendedCAServiceTypes.TYPE_XKMSEXTENDEDSERVICE))
                                    .getCertificatePath();
                    if (certPath != null) {
                        certificates.add(certPath.get(0));
                    }
                } catch (Exception fe) {
                    String msg = intres.getLocalizedMessage("caadmin.errorcreatecaservice", "XKMSCAService");
                    log.error(msg, fe);
                    throw new EJBException(fe);
                }
            }
            if (info instanceof CmsCAServiceInfo) {
                try {
                    final CryptoToken cryptoToken = cryptoTokenSession
                            .getCryptoToken(ca.getCAToken().getCryptoTokenId());
                    ca.initExtendedService(cryptoToken, ExtendedCAServiceTypes.TYPE_CMSEXTENDEDSERVICE, ca);
                    final List<Certificate> certPath = ((CmsCAServiceInfo) ca
                            .getExtendedCAServiceInfo(ExtendedCAServiceTypes.TYPE_CMSEXTENDEDSERVICE))
                                    .getCertificatePath();
                    if (certPath != null) {
                        certificates.add(certPath.get(0));
                    }
                } catch (Exception fe) {
                    String msg = intres.getLocalizedMessage("caadmin.errorcreatecaservice", "CMSCAService");
                    log.error(msg, fe);
                    throw new EJBException(fe);
                }
            }
            // Always store the certificate. Only publish the extended service
            // certificate for active services.
            Collection<Integer> publishers = null;
            if (info.getStatus() == ExtendedCAServiceInfo.STATUS_ACTIVE) {
                publishers = ca.getCRLPublishers();
            }
            if ((!certificates.isEmpty())) {
                publishCACertificate(admin, certificates, publishers, ca.getSubjectDN());
            }
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public void flushCACache() {
        // Just forward the call, because in CaSession it is only in the local interface and we
        // want to be able to use it from CLI
        caSession.flushCACache();
    }

}