/************************************************************************* * * * CESeCore: CE Security Core * * * * This software is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or any later version. * * * * See terms of license at gnu.org. * * * *************************************************************************/ package org.cesecore.certificates.ca; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.cert.Certificate; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import java.util.TreeSet; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.ejb.EJB; 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 javax.persistence.Query; import javax.persistence.TypedQuery; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.bouncycastle.util.encoders.Base64; 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.AuthenticationToken; import org.cesecore.authorization.AuthorizationDeniedException; import org.cesecore.authorization.AuthorizationSessionLocal; import org.cesecore.authorization.control.CryptoTokenRules; import org.cesecore.authorization.control.StandardRules; import org.cesecore.certificates.ca.catoken.CAToken; import org.cesecore.certificates.ca.internal.CACacheHelper; import org.cesecore.certificates.ca.internal.CaCache; import org.cesecore.certificates.ca.internal.CaIDCacheBean; import org.cesecore.certificates.certificate.CertificateWrapper; import org.cesecore.certificates.certificate.certextensions.AvailableCustomCertificateExtensionsConfiguration; import org.cesecore.config.CesecoreConfiguration; import org.cesecore.configuration.GlobalConfigurationSessionLocal; import org.cesecore.internal.InternalResources; import org.cesecore.internal.UpgradeableDataHashMap; import org.cesecore.jndi.JndiConstants; import org.cesecore.keys.token.CryptoToken; import org.cesecore.keys.token.CryptoTokenFactory; import org.cesecore.keys.token.CryptoTokenManagementSessionLocal; import org.cesecore.keys.token.CryptoTokenNameInUseException; import org.cesecore.keys.token.CryptoTokenSessionLocal; import org.cesecore.keys.token.PKCS11CryptoToken; import org.cesecore.keys.token.p11.exception.NoSuchSlotException; import org.cesecore.util.CertTools; import org.cesecore.util.CryptoProviderTools; import org.cesecore.util.EJBTools; import org.cesecore.util.QueryResultWrapper; /** * Implementation of CaSession, i.e takes care of all CA related CRUD operations. * * @version $Id: CaSessionBean.java 29306 2018-06-21 14:07:02Z andresjakobs $ */ @Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "CaSessionRemote") @TransactionAttribute(TransactionAttributeType.REQUIRED) public class CaSessionBean implements CaSessionLocal, CaSessionRemote { private static final Logger log = Logger.getLogger(CaSessionBean.class); /* Internal localization of logs and errors */ private static final InternalResources intres = InternalResources.getInstance(); @PersistenceContext(unitName = CesecoreConfiguration.PERSISTENCE_UNIT) private EntityManager entityManager; @Resource private SessionContext sessionContext; @EJB private AuthorizationSessionLocal authorizationSession; @EJB private CryptoTokenManagementSessionLocal cryptoTokenManagementSession; @EJB private CryptoTokenSessionLocal cryptoTokenSession; @EJB private SecurityEventsLoggerSessionLocal logSession; @EJB private GlobalConfigurationSessionLocal globalConfigurationSession; @EJB private CaIDCacheBean caIDCache; private CaSessionLocal caSession; @PostConstruct public void postConstruct() { // Install BouncyCastle provider if not available CryptoProviderTools.installBCProviderIfNotAvailable(); // It is not possible to @EJB-inject our self on all application servers so we need to do a lookup caSession = sessionContext.getBusinessObject(CaSessionLocal.class); } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public List findAll() { final TypedQuery query = entityManager.createQuery("SELECT a FROM CAData a", CAData.class); return query.getResultList(); } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CAData findById(final Integer cAId) { return entityManager.find(CAData.class, cAId); } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CAData findByIdOrThrow(final Integer cAId) throws CADoesntExistsException { final CAData ret = findById(cAId); if (ret == null) { throw new CADoesntExistsException("CA id: " + cAId); } return ret; } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CAData findByName(final String name) { final Query query = entityManager.createQuery("SELECT a FROM CAData a WHERE a.name=:name"); query.setParameter("name", name); return (CAData) QueryResultWrapper.getSingleResult(query); } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CAData findByNameOrThrow(final String name) throws CADoesntExistsException { final CAData ret = findByName(name); if (ret == null) { throw new CADoesntExistsException("CA name: " + name); } return ret; } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public void flushCACache() { CaCache.INSTANCE.flush(); caIDCache.forceCacheExpiration(); if (log.isDebugEnabled()) { log.debug("Flushed CA cache."); } } @Override public void addCA(final AuthenticationToken admin, final CA ca) throws CAExistsException, AuthorizationDeniedException { if (ca != null) { final int cryptoTokenId = ca.getCAToken().getCryptoTokenId(); if (!authorizationSession.isAuthorized(admin, StandardRules.CAADD.resource(), CryptoTokenRules.USE.resource() + "/" + cryptoTokenId)) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoaddca", admin.toString(), Integer.valueOf(ca.getCAId())); throw new AuthorizationDeniedException(msg); } CAInfo cainfo = ca.getCAInfo(); // The CA needs a name and a subject DN in order to store it if ((ca.getName() == null) || (ca.getSubjectDN() == null)) { throw new CAExistsException("Null CA name or SubjectDN. Name: '"+ca.getName()+"', SubjectDN: '"+ca.getSubjectDN()+"'."); } if (findByName(cainfo.getName()) != null) { String msg = intres.getLocalizedMessage("caadmin.caexistsname", cainfo.getName()); throw new CAExistsException(msg); } if (findById(ca.getCAId()) != null) { String msg = intres.getLocalizedMessage("caadmin.caexistsid", Integer.valueOf(ca.getCAId())); throw new CAExistsException(msg); } final CAData caData = new CAData(cainfo.getSubjectDN(), cainfo.getName(), cainfo.getStatus(), ca); entityManager.persist(caData); caIDCache.forceCacheExpiration(); // Clear ID cache so this one will be reloaded as well. String msg = intres.getLocalizedMessage("caadmin.addedca", ca.getCAId(), cainfo.getName(), cainfo.getStatus()); final Map details = new LinkedHashMap(); details.put("msg", msg); details.put("tokenproperties", ca.getCAToken().getProperties()); details.put("tokensequence", ca.getCAToken().getKeySequence()); logSession.log(EventTypes.CA_CREATION, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE, admin.toString(), String.valueOf(ca.getCAId()), null, null, details); } else { log.debug("Trying to add null CA, nothing done."); } } @Override public void editCA(final AuthenticationToken admin, final CAInfo cainfo) throws CADoesntExistsException, AuthorizationDeniedException { if (cainfo != null) { if (log.isTraceEnabled()) { log.trace(">editCA (CAInfo): "+cainfo.getName()); } try { final CA ca = getCAInternal(cainfo.getCAId(), null, false); // Check if we can edit the CA (also checks authorization) int newCryptoTokenId = ca.getCAToken().getCryptoTokenId(); if (cainfo.getCAToken() != null) { newCryptoTokenId = cainfo.getCAToken().getCryptoTokenId(); } assertAuthorizationAndTarget(admin, cainfo.getName(), cainfo.getSubjectDN(), newCryptoTokenId, ca); @SuppressWarnings("unchecked") final Map orgmap = (Map)ca.saveData(); AvailableCustomCertificateExtensionsConfiguration cceConfig = (AvailableCustomCertificateExtensionsConfiguration) globalConfigurationSession.getCachedConfiguration(AvailableCustomCertificateExtensionsConfiguration.CONFIGURATION_ID); ca.updateCA(cryptoTokenManagementSession.getCryptoToken(ca.getCAToken().getCryptoTokenId()), cainfo, cceConfig); // Audit log @SuppressWarnings("unchecked") final Map newmap = (Map)ca.saveData(); // Get the diff of what changed final Map diff = UpgradeableDataHashMap.diffMaps(orgmap, newmap); final String msg = intres.getLocalizedMessage("caadmin.editedca", ca.getCAId(), ca.getName(), ca.getStatus()); // Use a LinkedHashMap because we want the details logged (in the final log string) in the order we insert them, and not randomly final Map details = new LinkedHashMap(); details.put("msg", msg); for (final Map.Entry entry : diff.entrySet()) { details.put(entry.getKey().toString(), entry.getValue().toString()); } details.put("tokenproperties", ca.getCAToken().getProperties()); details.put("tokensequence", ca.getCAToken().getKeySequence()); logSession.log(EventTypes.CA_EDITING, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,admin.toString(), String.valueOf(ca.getCAId()), null, null, details); // Store it mergeCa(ca); } catch (InvalidAlgorithmException e) { throw new CADoesntExistsException(e); } if (log.isTraceEnabled()) { log.trace("editCA (CA): "+ca.getName()); } final CA orgca = getCAInternal(ca.getCAId(), null, true); // Check if we can edit the CA (also checks authorization) assertAuthorizationAndTarget(admin, ca.getName(), ca.getSubjectDN(), ca.getCAToken().getCryptoTokenId(), orgca); if (auditlog) { // Get the diff of what changed final Map diff = orgca.diff(ca); String msg = intres.getLocalizedMessage("caadmin.editedca", ca.getCAId(), ca.getName(), ca.getStatus()); // Use a LinkedHashMap because we want the details logged (in the final log string) in the order we insert them, and not randomly final Map details = new LinkedHashMap(); details.put("msg", msg); for (Map.Entry entry : diff.entrySet()) { details.put(entry.getKey().toString(), entry.getValue().toString()); } details.put("tokenproperties", ca.getCAToken().getProperties()); details.put("tokensequence", ca.getCAToken().getKeySequence()); logSession.log(EventTypes.CA_EDITING, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,admin.toString(), String.valueOf(ca.getCAId()), null, null, details); } if (log.isTraceEnabled()) { log.trace(" ids = getCAInfoInternal(caId).getValidators(); if (ids != null) { for (final Integer id : ids) { if (id.intValue() == keyValidatorId) { // We have found a match. No point in looking for more. return true; } } } } return false; } /** Ensure that the caller is authorized to the CA we are about to edit and that the CA name and subjectDN matches. */ private void assertAuthorizationAndTarget(AuthenticationToken admin, final String name, final String subjectDN, final int cryptoTokenId, final CA ca) throws CADoesntExistsException, AuthorizationDeniedException { assertAuthorizationAndTargetWithNewSubjectDn(admin, name, subjectDN, cryptoTokenId, ca); if (!StringUtils.equals(subjectDN, ca.getSubjectDN()) && ca.getCAInfo().getStatus() != CAConstants.CA_UNINITIALIZED) { throw new CADoesntExistsException("Not same CA subject DN."); } } /** Ensure that the caller is authorized to the CA we are about to edit and that the CA name matches. */ private void assertAuthorizationAndTargetWithNewSubjectDn(AuthenticationToken admin, final String name, final String subjectDN, final int cryptoTokenId, final CA ca) throws CADoesntExistsException, AuthorizationDeniedException { // Check if we are authorized to edit CA and authorization to specific CA if (cryptoTokenId == ca.getCAToken().getCryptoTokenId() || cryptoTokenId==0) { if (!authorizationSession.isAuthorized(admin, StandardRules.CAEDIT.resource(), StandardRules.CAACCESS.resource() + ca.getCAId())) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoeditca", admin.toString(), Integer.valueOf(ca.getCAId())); throw new AuthorizationDeniedException(msg); } } else { // We only need to check usage authorization if we change CryptoToken reference (and not to 0 which means "removed"). if (!authorizationSession.isAuthorized(admin, StandardRules.CAEDIT.resource(), StandardRules.CAACCESS.resource() + ca.getCAId(), CryptoTokenRules.USE.resource() + "/" + cryptoTokenId)) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoeditca", admin.toString(), Integer.valueOf(ca.getCAId())); throw new AuthorizationDeniedException(msg); } } // The CA needs the same name and subject DN in order to store it if (name == null || subjectDN == null) { throw new CADoesntExistsException("Null CA name or SubjectDN"); } else if (!StringUtils.equals(name, ca.getName())) { throw new CADoesntExistsException("Not same CA name."); } } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CA getCA(final AuthenticationToken admin, final int caid) throws AuthorizationDeniedException { if (!authorizedToCA(admin, caid)) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), Integer.valueOf(caid)); throw new AuthorizationDeniedException(msg); } return getCAInternal(caid, null, true); } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CA getCA(final AuthenticationToken admin, final String name) throws AuthorizationDeniedException { CA ca = getCAInternal(-1, name, true); if(ca != null) { if (!authorizedToCA(admin, ca.getCAId())) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), name); throw new AuthorizationDeniedException(msg); } } return ca; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public CA getCANoLog(final AuthenticationToken admin, final int caid) throws AuthorizationDeniedException { if (!authorizedToCANoLogging(admin, caid)) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), Integer.valueOf(caid)); throw new AuthorizationDeniedException(msg); } return getCAInternal(caid, null, true); } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CA getCAForEdit(final AuthenticationToken admin, final int caid) throws AuthorizationDeniedException { CA ca = getCAInternal(caid, null, false); if (ca != null) { if (!authorizedToCA(admin, ca.getCAId())) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), Integer.valueOf(caid)); throw new AuthorizationDeniedException(msg); } } return ca; } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CA getCAForEdit(final AuthenticationToken admin, final String name) throws AuthorizationDeniedException { CA ca = getCAInternal(-1, name, false); if(ca == null) { return null; } if (!authorizedToCA(admin, ca.getCAId())) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), name); throw new AuthorizationDeniedException(msg); } return ca; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public CAInfo getCAInfo(final AuthenticationToken admin, final String name) throws AuthorizationDeniedException { // Authorization is handled by getCA CA ca = getCA(admin, name); if (ca == null) { return null; } else { return ca.getCAInfo(); } } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public CAInfo getCAInfo(final AuthenticationToken admin, final int caid) throws AuthorizationDeniedException { // Authorization is handled by getCA CA ca = getCA(admin, caid); if (ca == null) { return null; } else { return ca.getCAInfo(); } } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public CAInfo getCAInfoInternal(final int caid) { // Authorization is handled by getCA CA ca = getCAInternal(caid, null, true); if (ca == null) { return null; } else { return ca.getCAInfo(); } } @Override public Collection getCaChain(AuthenticationToken authenticationToken, String caName) throws AuthorizationDeniedException, CADoesntExistsException { final CAInfo info = getCAInfo(authenticationToken, caName); if(info == null) { throw new CADoesntExistsException("CA with name " + caName + " doesn't exist."); } final List result = new ArrayList<>(); if (info.getStatus() != CAConstants.CA_WAITING_CERTIFICATE_RESPONSE) { result.addAll(EJBTools.wrapCertCollection(info.getCertificateChain())); } if (log.isDebugEnabled()) { log.debug("CA chain request by admin " + authenticationToken.getUniqueId() + " " + result); } return result; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public CAInfo getCAInfoInternal(final int caid, final String name, boolean fromCache) { // Authorization is handled by getCA CA ca = getCAInternal(caid, name, fromCache); if (ca == null) { return null; } else { return ca.getCAInfo(); } } @Override public void removeCA(final AuthenticationToken admin, final int caid) throws AuthorizationDeniedException { // check authorization if (!authorizationSession.isAuthorized(admin, StandardRules.CAREMOVE.resource())) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoremoveca", admin.toString(), Integer.valueOf(caid)); throw new AuthorizationDeniedException(msg); } // Get CA from database if it does not exist, ignore CAData cadata = findById(Integer.valueOf(caid)); if (cadata != null) { // Remove CA entityManager.remove(cadata); // Invalidate CA cache to refresh information CaCache.INSTANCE.removeEntry(caid); caIDCache.forceCacheExpiration(); // Clear ID cache so this one will be reloaded as well. final String detailsMsg = intres.getLocalizedMessage("caadmin.removedca", Integer.valueOf(caid), cadata.getName()); logSession.log(EventTypes.CA_DELETION, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,admin.toString(), String.valueOf(caid), null, null, detailsMsg); } } @Override public void renameCA(final AuthenticationToken admin, final String oldname, final String newname) throws CAExistsException, CADoesntExistsException, AuthorizationDeniedException { // Get CA from database CAData cadata = findByNameOrThrow(oldname); // Check authorization, to rename we need remove (for the old name) and add for the new name) if (!authorizationSession.isAuthorized(admin, StandardRules.CAREMOVE.resource(), StandardRules.CAADD.resource())) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtorenameca", admin.toString(), cadata.getCaId()); throw new AuthorizationDeniedException(msg); } if (findByName(newname) == null) { // The new CA doesn't exist, it's okay to rename old one. cadata.setName(newname); // Invalidate CA cache to refresh information int caid = cadata.getCaId().intValue(); CaCache.INSTANCE.removeEntry(caid); caIDCache.forceCacheExpiration(); // Clear ID cache so this one will be reloaded as well. final String detailsMsg = intres.getLocalizedMessage("caadmin.renamedca", oldname, cadata.getCaId(), newname); logSession.log(EventTypes.CA_RENAMING, EventStatus.SUCCESS, ModuleTypes.CA, ServiceTypes.CORE,admin.toString(), String.valueOf(caid), null, null, detailsMsg); } else { throw new CAExistsException("CA " + newname + " already exists."); } } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List getAllCaIds() { // We need a cache of these, to not list from the database all the time return caIDCache.getCacheContent(); } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List getActiveCANames(final AuthenticationToken admin) { return new ArrayList<>(getActiveCAIdToNameMap(admin).values()); } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public Map getActiveCAIdToNameMap(final AuthenticationToken authenticationToken) { final HashMap returnval = new HashMap<>(); for (int caId : getAllCaIds()) { if (authorizedToCANoLogging(authenticationToken, caId)) { CAInfo caInfo = getCAInfoInternal(caId); if (caInfo != null && (caInfo.getStatus() == CAConstants.CA_ACTIVE || caInfo.getStatus() == CAConstants.CA_UNINITIALIZED)) { returnval.put(caInfo.getCAId(), caInfo.getName()); } } } return returnval; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List getAuthorizedCaIds(final AuthenticationToken admin) { final Collection availableCaIds = getAllCaIds(); final ArrayList returnval = new ArrayList<>(); for (Integer caid : availableCaIds) { if (authorizedToCANoLogging(admin, caid)) { returnval.add(caid); } } return returnval; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public Collection getAuthorizedCaNames(final AuthenticationToken admin) { final Collection availableCaIds = getAllCaIds(); final TreeSet names = new TreeSet<>(); for (Integer caid : availableCaIds) { if (authorizedToCANoLogging(admin, caid)) { names.add(getCAInfoInternal(caid).getName()); } } return names; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public TreeMap getAuthorizedCaNamesToIds(final AuthenticationToken admin) { final Collection availableCaIds = getAllCaIds(); final TreeMap names = new TreeMap<>(); for (Integer caid : availableCaIds) { if (authorizedToCANoLogging(admin, caid)) { final CAInfo caInfo = getCAInfoInternal(caid); if (caInfo != null) { names.put(caInfo.getName(), caInfo.getCAId()); } } } return names; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List getAuthorizedAndEnabledCaInfos(AuthenticationToken authenticationToken) { List result = new ArrayList(); for (int caId : getAuthorizedCaIds(authenticationToken)) { CAInfo caInfo = getCAInfoInternal(caId); if ( caInfo != null && caInfo.getStatus() != CAConstants.CA_EXTERNAL && caInfo.getStatus() != CAConstants.CA_UNINITIALIZED && caInfo.getStatus() != CAConstants.CA_WAITING_CERTIFICATE_RESPONSE ) { result.add(caInfo); } } return result; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List getAuthorizedAndNonExternalCaInfos(AuthenticationToken authenticationToken) { List result = new ArrayList(); for (Integer caId : getAuthorizedCaIds(authenticationToken)) { CAInfo caInfo = getCAInfoInternal(caId); if ( caInfo != null && caInfo.getStatus() != CAConstants.CA_EXTERNAL ) { result.add(caInfo); } } return result; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List getAuthorizedCaInfos(AuthenticationToken authenticationToken) { List result = new ArrayList(); for (Integer caId : getAuthorizedCaIds(authenticationToken)) { CAInfo caInfo = getCAInfoInternal(caId); if (caInfo != null) { result.add(caInfo); } } return result; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public void verifyExistenceOfCA(int caid) throws CADoesntExistsException { if( getCAInternal(caid, null, true) == null) { throw new CADoesntExistsException("CA with id " + caid + " does not exist."); } } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public HashMap getCAIdToNameMap() { final HashMap returnval = new HashMap(); for (final CAData cadata : findAll()) { returnval.put(cadata.getCaId(), cadata.getName()); } return returnval; } /** * Internal method for getting CA, to avoid code duplication. Tries to find the CA even if the CAId is wrong due to CA certificate DN not being * the same as CA DN. Uses CACache directly if configured to do so in ejbca.properties. * * Note! No authorization checks performed in this internal method * * @param caid * numerical id of CA (subjectDN.hashCode()) that we search for, or -1 if a name is to be used instead * @param name * human readable name of CA, used instead of caid if caid == -1, can be null if caid != -1 * @param fromCache if we should use the CA cache or return a new, decoupled, instance from the database, to be used when you need * a completely distinct object, for edit, and not a shared cached instance. * @return CA value object, or null if it doesn't exist. */ private CA getCAInternal(int caid, final String name, boolean fromCache) { if (log.isTraceEnabled()) { log.trace(">getCAInternal: " + caid + ", " + name); } Integer caIdValue = Integer.valueOf(caid); if (caid == -1) { caIdValue = CaCache.INSTANCE.getNameToIdMap().get(name); } CA ca = null; if (fromCache && caIdValue!=null) { ca = getCa(caIdValue.intValue()); if (ca != null && hasCAExpiredNow(ca)) { // CA has expired, re-read from database with the side affect that the status will be updated ca = getCAData(caid, name).getCA(); } } else { CAData caData = getCAData(caid, name); if (caData != null) { ca = caData.getCA(); } } if (log.isTraceEnabled()) { log.trace(" caDataMap = cadata.getDataMap(); // If CA-data is upgraded we want to save the new data, so we must get the old version before loading the data // and perhaps upgrading final float oldversion = ((Float) caDataMap.get(UpgradeableDataHashMap.VERSION)).floatValue(); // Perform "live" upgrade from 5.0.x and earlier boolean adhocUpgrade = adhocUpgradeFrom50(cadata.getCaId().intValue(), caDataMap, cadata.getName()); if (adhocUpgrade) { // Convert map into storage friendly format now since we changed it cadata.setDataMap(caDataMap); } // Fetching the CA object will trigger UpgradableHashMap upgrades CA ca = cadata.getCA(); if (ca != null) { final boolean expired = hasCAExpiredNow(ca); if (expired) { ca.setStatus(CAConstants.CA_EXPIRED); } final boolean upgradedExtendedService = ca.upgradeExtendedCAServices(); // Compare old version with current version and save the data if there has been a change final boolean upgradeCA = (Float.compare(oldversion, ca.getVersion()) != 0); if (adhocUpgrade || upgradedExtendedService || upgradeCA || expired) { if (log.isDebugEnabled()) { log.debug("Merging CA to database. Name: " + cadata.getName() + ", id: " + cadata.getCaId() + ", adhocUpgrade: " + adhocUpgrade+", upgradedExtendedService: " + upgradedExtendedService + ", upgradeCA: " + upgradeCA + ", expired: " + expired); } ca.getCAToken(); final int caId = caSession.mergeCa(ca); caDataReturn = entityManager.find(CAData.class, caId); } } return caDataReturn; } /** * Extract keystore or keystore reference and store it as a CryptoToken. Add a reference to the keystore. * @return true if any changes where made */ @SuppressWarnings("unchecked") @Deprecated // Remove when we no longer need to support upgrades from 5.0.x private boolean adhocUpgradeFrom50(int caid, LinkedHashMap data, String caName) { HashMap tokendata = (HashMap) data.get(CA.CATOKENDATA); if (tokendata.get(CAToken.CRYPTOTOKENID) != null) { // Already upgraded if (!CesecoreConfiguration.isKeepInternalCAKeystores()) { // All nodes in the cluster has been upgraded so we can remove any internal CA keystore now if (tokendata.get(CAToken.KEYSTORE)!=null) { tokendata.remove(CAToken.KEYSTORE); tokendata.remove(CAToken.CLASSPATH); log.info("Removed duplicate of upgraded CA's internal keystore for CA '" + caName + "' with id: " + caid); return true; } } else { if (log.isDebugEnabled()) { log.debug("CA '" + caName + "' already has cryptoTokenId and will not have it's token split of to a different db table because db.keepinternalcakeystores=true: " + caid); } } return false; } // Perform pre-upgrade of CATokenData to correct classpath changes (org.ejbca.core.model.ca.catoken.SoftCAToken) tokendata = (LinkedHashMap) new CAToken(tokendata).saveData(); data.put(CA.CATOKENDATA, tokendata); log.info("Pulling CryptoToken out of CA '" + caName + "' with id " + caid + " into a separate database table."); final String str = tokendata.get(CAToken.KEYSTORE); byte[] keyStoreData = null; if (StringUtils.isNotEmpty(str)) { keyStoreData = Base64.decode(str.getBytes()); } String propertyStr = tokendata.get(CAToken.PROPERTYDATA); final Properties prop = new Properties(); if (StringUtils.isNotEmpty(propertyStr)) { try { // If the input string contains \ (backslash on windows) we must convert it to \\ // Otherwise properties.load will parse it as an escaped character, and that is not good propertyStr = StringUtils.replace(propertyStr, "\\", "\\\\"); prop.load(new ByteArrayInputStream(propertyStr.getBytes())); } catch (IOException e) { log.error("Error getting CA token properties: ", e); } } final String classpath = tokendata.get(CAToken.CLASSPATH); if (log.isDebugEnabled()) { log.debug("CA token classpath: " + classpath); } // Upgrade the properties value final Properties upgradedProperties = PKCS11CryptoToken.upgradePropertiesFileFrom5_0_x(prop); // If it is an P11 we are using and the library and slot are the same as an existing CryptoToken we use that CryptoToken's id. int cryptoTokenId = 0; if (PKCS11CryptoToken.class.getName().equals(classpath)) { if (upgradedProperties.getProperty(PKCS11CryptoToken.SLOT_LABEL_TYPE)==null) { log.error("Upgrade of CA '" + caName + "' failed due to failed upgrade of PKCS#11 CA token properties."); return false; } for (final Integer currentCryptoTokenId : cryptoTokenSession.getCryptoTokenIds()) { final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(currentCryptoTokenId.intValue()); final Properties cryptoTokenProperties = cryptoToken.getProperties(); if (StringUtils.equals(upgradedProperties.getProperty(PKCS11CryptoToken.SHLIB_LABEL_KEY), cryptoTokenProperties.getProperty(PKCS11CryptoToken.SHLIB_LABEL_KEY)) && StringUtils.equals(upgradedProperties.getProperty(PKCS11CryptoToken.ATTRIB_LABEL_KEY), cryptoTokenProperties.getProperty(PKCS11CryptoToken.ATTRIB_LABEL_KEY)) && StringUtils.equals(upgradedProperties.getProperty(PKCS11CryptoToken.SLOT_LABEL_VALUE), cryptoTokenProperties.getProperty(PKCS11CryptoToken.SLOT_LABEL_VALUE)) && StringUtils.equals(upgradedProperties.getProperty(PKCS11CryptoToken.SLOT_LABEL_TYPE), cryptoTokenProperties.getProperty(PKCS11CryptoToken.SLOT_LABEL_TYPE))) { // The current CryptoToken point to the same HSM slot in the same way.. re-use this id! cryptoTokenId = currentCryptoTokenId.intValue(); break; } } } if (cryptoTokenId == 0) { final String cryptoTokenName = "Upgraded CA CryptoToken for " + caName; try { cryptoTokenId = cryptoTokenSession.mergeCryptoToken(CryptoTokenFactory.createCryptoToken(classpath, upgradedProperties, keyStoreData, caid, cryptoTokenName, true)); } catch (CryptoTokenNameInUseException e) { final String msg = "Crypto token name already in use upgrading (adhocUpgradeFrom50) crypto token for CA '"+caName+"', cryptoTokenName '"+cryptoTokenName+"'."; log.info(msg, e); throw new RuntimeException(msg, e); // Since we have a constraint on CA names to be unique, this should never happen } catch (NoSuchSlotException e) { final String msg = "Slot as defined by " + upgradedProperties.getProperty(PKCS11CryptoToken.SLOT_LABEL_VALUE) + " for CA '" + caName + "' could not be found."; log.error(msg, e); throw new RuntimeException(msg, e); } } // Mark this CA as upgraded by setting a reference to the CryptoToken if the merge was successful tokendata.put(CAToken.CRYPTOTOKENID, String.valueOf(cryptoTokenId)); // Note: We did not remove the keystore in the CA properties here, so old versions running in parallel will still work log.info("CA '" + caName + "' with id " + caid + " is now using CryptoToken with cryptoTokenId " + cryptoTokenId); return true; } @Override public Certificate getFutureRolloverCertificate(int caid) throws CADoesntExistsException { final CA ca = getCa(caid); if (ca == null) { throw new CADoesntExistsException("Method called on non-existent CA"); } final List chain = ca.getRolloverCertificateChain(); if (chain == null) { return null; } return chain.get(0); } }