Java tutorial
/************************************************************************* * * * 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.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 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.control.AccessControlSessionLocal; 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.config.CesecoreConfiguration; 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; /** * Implementation of CaSession, i.e takes care of all CA related CRUD operations. * * @version $Id: CaSessionBean.java 20746 2015-02-25 14:52:01Z mikekushner $ */ @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 AccessControlSessionLocal accessSession; @EJB private CryptoTokenManagementSessionLocal cryptoTokenManagementSession; @EJB private CryptoTokenSessionLocal cryptoTokenSession; @EJB private SecurityEventsLoggerSessionLocal logSession; 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 void flushCACache() { CaCache.INSTANCE.flush(); 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 (!accessSession.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 (CAData.findByName(entityManager, cainfo.getName()) != null) { String msg = intres.getLocalizedMessage("caadmin.caexistsname", cainfo.getName()); throw new CAExistsException(msg); } if (CAData.findById(entityManager, ca.getCAId()) != null) { String msg = intres.getLocalizedMessage("caadmin.caexistsid", Integer.valueOf(ca.getCAId())); throw new CAExistsException(msg); } entityManager.persist(new CAData(cainfo.getSubjectDN(), cainfo.getName(), cainfo.getStatus(), ca)); String msg = intres.getLocalizedMessage("caadmin.addedca", ca.getCAId(), cainfo.getName(), cainfo.getStatus()); final Map<String, Object> details = new LinkedHashMap<String, Object>(); 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<Object, Object> orgmap = (Map<Object, Object>) ca.saveData(); ca.updateCA(cryptoTokenManagementSession.getCryptoToken(ca.getCAToken().getCryptoTokenId()), cainfo); // Audit log @SuppressWarnings("unchecked") final Map<Object, Object> newmap = (Map<Object, Object>) ca.saveData(); // Get the diff of what changed final Map<Object, Object> 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<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); for (final Map.Entry<Object, Object> 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 (CAInfo): " + cainfo.getName()); } } else { log.debug("Trying to edit null CAInfo, nothing done."); } } @Override public void editCA(final AuthenticationToken admin, final CA ca, boolean auditlog) throws CADoesntExistsException, AuthorizationDeniedException { if (ca != null) { 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<Object, Object> 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<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); for (Map.Entry<Object, Object> 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("<editCA (CA): " + ca.getName()); } // Store it mergeCa(ca); } else { log.debug("Trying to edit null CA, nothing done."); } } @Override public boolean existsCa(final int caId) { return entityManager.find(CAData.class, caId) != null; } @Override public boolean existsCa(final String name) { return CAData.findByName(entityManager, name) != null; } /** 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 { // Check if we are authorized to edit CA and authorization to specific CA if (cryptoTokenId == ca.getCAToken().getCryptoTokenId() || cryptoTokenId == 0) { if (!accessSession.isAuthorized(admin, StandardRules.CAEDIT.resource(), StandardRules.CAACCESS.resource())) { 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 (!accessSession.isAuthorized(admin, StandardRules.CAEDIT.resource(), StandardRules.CAACCESS.resource(), 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."); } else if (!StringUtils.equals(subjectDN, ca.getSubjectDN()) && ca.getCAInfo().getStatus() != CAConstants.CA_UNINITIALIZED) { throw new CADoesntExistsException("Not same CA subject DN."); } } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public CA getCA(final AuthenticationToken admin, final int caid) throws CADoesntExistsException, 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 CADoesntExistsException, AuthorizationDeniedException { CA ca = getCAInternal(-1, name, true); 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 CADoesntExistsException, 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 CADoesntExistsException, AuthorizationDeniedException { CA ca = getCAInternal(caid, null, false); 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 CADoesntExistsException, AuthorizationDeniedException { CA ca = getCAInternal(-1, name, false); 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 CADoesntExistsException, AuthorizationDeniedException { // Authorization is handled by getCA return getCA(admin, name).getCAInfo(); } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public CAInfo getCAInfo(final AuthenticationToken admin, final int caid) throws CADoesntExistsException, AuthorizationDeniedException { // Authorization is handled by getCA return getCA(admin, caid).getCAInfo(); } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public CAInfo getCAInfoInternal(final int caid) throws CADoesntExistsException { return getCAInternal(caid, null, true).getCAInfo(); } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public CAInfo getCAInfoInternal(final int caid, final String name, boolean fromCache) throws CADoesntExistsException { return getCAInternal(caid, name, fromCache).getCAInfo(); } @Override public void removeCA(final AuthenticationToken admin, final int caid) throws AuthorizationDeniedException { // check authorization if (!accessSession.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 = CAData.findById(entityManager, Integer.valueOf(caid)); if (cadata != null) { // Remove CA entityManager.remove(cadata); // Invalidate CA cache to refresh information CaCache.INSTANCE.removeEntry(caid); 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 = CAData.findByNameOrThrow(entityManager, oldname); // Check authorization, to rename we need remove (for the old name) and add for the new name) if (!accessSession.isAuthorized(admin, StandardRules.CAREMOVE.resource(), StandardRules.CAADD.resource())) { String msg = intres.getLocalizedMessage("caadmin.notauthorizedtorenameca", admin.toString(), cadata.getCaId()); throw new AuthorizationDeniedException(msg); } if (CAData.findByName(entityManager, newname) == null) { // new CA doesn't exits, it's ok to rename old one. cadata.setName(newname); // Invalidate CA cache to refresh information int caid = cadata.getCaId().intValue(); CaCache.INSTANCE.removeEntry(caid); 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<Integer> getAllCaIds() { return CAData.findAllCaIds(entityManager); } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List<String> getActiveCANames(final AuthenticationToken admin) { return new ArrayList<String>(getActiveCAIdToNameMap(admin).values()); } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public Map<Integer, String> getActiveCAIdToNameMap(final AuthenticationToken authenticationToken) { final HashMap<Integer, String> returnval = new HashMap<Integer, String>(); for (int caId : getAllCaIds()) { if (authorizedToCA(authenticationToken, caId)) { CAInfo caInfo; try { caInfo = getCAInfoInternal(caId); if (caInfo.getStatus() == CAConstants.CA_ACTIVE) { returnval.put(caInfo.getCAId(), caInfo.getName()); } } catch (CADoesntExistsException e) { //NOPMD: This can never happen } } } return returnval; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List<Integer> getAuthorizedCaIds(final AuthenticationToken admin) { final Collection<Integer> availableCaIds = getAllCaIds(); final ArrayList<Integer> returnval = new ArrayList<Integer>(); for (Integer caid : availableCaIds) { if (authorizedToCANoLogging(admin, caid)) { returnval.add(caid); } } return returnval; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public Collection<String> getAuthorizedCaNames(final AuthenticationToken admin) { final Collection<Integer> availableCaIds = getAllCaIds(); final TreeSet<String> names = new TreeSet<String>(); for (Integer caid : availableCaIds) { if (authorizedToCANoLogging(admin, caid)) { try { names.add(getCAInfoInternal(caid).getName()); } catch (CADoesntExistsException e) { // NOPMD Should not happen since we just retrieved the ID } } } return names; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public List<CAInfo> getAuthorizedAndEnabledCaInfos(AuthenticationToken authenticationToken) { List<CAInfo> result = new ArrayList<CAInfo>(); for (int caId : getAuthorizedCaIds(authenticationToken)) { CAInfo caInfo; try { caInfo = getCAInfoInternal(caId); } catch (CADoesntExistsException e) { throw new IllegalStateException( "CA with ID " + caId + " was not found in spite if just being retrieved."); } if (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<CAInfo> getAuthorizedAndNonExternalCaInfos(AuthenticationToken authenticationToken) { List<CAInfo> result = new ArrayList<CAInfo>(); for (Integer caId : getAuthorizedCaIds(authenticationToken)) { CAInfo caInfo; try { caInfo = getCAInfoInternal(caId); } catch (CADoesntExistsException e) { throw new IllegalStateException( "CA with ID " + caId + " was not found in spite if just being retrieved."); } if (caInfo.getStatus() != CAConstants.CA_EXTERNAL) { result.add(caInfo); } } return result; } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public void verifyExistenceOfCA(int caid) throws CADoesntExistsException { getCAInternal(caid, null, true); } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public HashMap<Integer, String> getCAIdToNameMap() { final HashMap<Integer, String> returnval = new HashMap<Integer, String>(); for (final CAData cadata : CAData.findAll(entityManager)) { 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, never null * @throws CADoesntExistsException * if no CA was found */ private CA getCAInternal(int caid, final String name, boolean fromCache) throws CADoesntExistsException { if (log.isTraceEnabled()) { log.trace(">getCAInternal: " + caid + ", " + name); } Integer caIdValue = Integer.valueOf(caid); if (caid == -1) { caIdValue = CaCache.INSTANCE.getNameToIdMap().get(name); } CA ca; 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 { ca = getCAData(caid, name).getCA(); } if (log.isTraceEnabled()) { log.trace("<getCAInternal: " + caid + ", " + name); } if (ca == null) { throw new CADoesntExistsException("Could not find CA with name " + name + " and ID " + caid); } return ca; } /** * Checks if the CA certificate has expired (or is not yet valid) since last check. * Logs an info message first time that the CA certificate has expired, or every time when not yet valid. * * @return the true if the CA is expired */ private boolean hasCAExpiredNow(final CA ca) { boolean expired = false; // Check that CA hasn't expired. try { CertTools.checkValidity(ca.getCACertificate(), new Date()); } catch (CertificateExpiredException cee) { // Signers Certificate has expired, we want to make sure that the // status in the database is correctly EXPIRED for this CA // Don't set external CAs to expired though, because they should always be treated as external CAs if (ca.getStatus() != CAConstants.CA_EXPIRED && ca.getStatus() != CAConstants.CA_EXTERNAL) { log.info(intres.getLocalizedMessage("caadmin.caexpired", ca.getSubjectDN()) + " " + cee.getMessage()); expired = true; } } catch (CertificateNotYetValidException e) { // Signers Certificate is not yet valid. log.warn(intres.getLocalizedMessage("caadmin.canotyetvalid", ca.getSubjectDN()) + " " + e.getMessage()); } return expired; } /** * Internal method for getting CAData. Tries to find the CA even if the CAId is wrong due to CA certificate DN not being the same as CA DN. * * The returned CAData object is guaranteed to be upgraded and these upgrades merged back to the database. * * @param caid numerical id of CA (subjectDN.hashCode()) that we search for, or -1 of a name is to ge used instead * @param name human readable name of CA, used instead of caid if caid == -1, can be null of caid != -1 * @throws CADoesntExistsException if no CA was found */ private CAData getCAData(final int caid, final String name) throws CADoesntExistsException { CAData cadata = null; if (caid != -1) { cadata = upgradeAndMergeToDatabase(CAData.findById(entityManager, Integer.valueOf(caid))); } else { cadata = upgradeAndMergeToDatabase(CAData.findByName(entityManager, name)); } if (cadata == null) { // We should never get to here if we are searching for name, in any // case if the name does not exist, the CA really does not exist // We don't have to try to find another mapping for the CAId if (caid != -1) { // subject DN of the CA certificate might not have all objects // that is the DN of the certificate data. final Integer oRealCAId = (Integer) CACacheHelper.getCaCertHash(Integer.valueOf(caid)); // has the "real" CAID been mapped to the certificate subject // hash by a previous call? if (oRealCAId != null) { // yes, using cached value of real caid. if (log.isDebugEnabled()) { log.debug("Found a mapping from caid " + caid + " to realCaid " + oRealCAId); } cadata = CAData.findById(entityManager, oRealCAId); } else { // no, we have to search for it among all CA certs for (final CAData currentCaData : CAData.findAll(entityManager)) { final CAData currentUpgradedCaData = upgradeAndMergeToDatabase(currentCaData); final Certificate caCert = currentUpgradedCaData.getCA().getCACertificate(); if (caCert != null && caid == CertTools.getSubjectDN(caCert).hashCode()) { cadata = currentUpgradedCaData; // found. // Do also cache it if someone else is needing it later if (log.isDebugEnabled()) { log.debug( "Adding a mapping from caid " + caid + " to realCaid " + cadata.getCaId()); } CACacheHelper.putCaCertHash(Integer.valueOf(caid), Integer.valueOf(cadata.getCaId())); } if (cadata != null) { break; } } } } if (cadata == null) { String msg; if (caid != -1) { msg = intres.getLocalizedMessage("caadmin.canotexistsid", Integer.valueOf(caid)); } else { msg = intres.getLocalizedMessage("caadmin.canotexistsname", name); } log.info(msg); throw new CADoesntExistsException(msg); } } return cadata; } @Override public boolean authorizedToCANoLogging(final AuthenticationToken admin, final int caid) { final boolean ret = accessSession.isAuthorizedNoLogging(admin, StandardRules.CAACCESS.resource() + caid); if (log.isDebugEnabled() && !ret) { final String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), caid); log.debug(msg); } return ret; } @Override public boolean authorizedToCA(final AuthenticationToken admin, final int caid) { final boolean ret = accessSession.isAuthorized(admin, StandardRules.CAACCESS.resource() + caid); if (log.isDebugEnabled() && !ret) { final String msg = intres.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), caid); log.debug(msg); } return ret; } /** @return the CA object, from the database (including any upgrades) is necessary */ private CA getCa(int caId) { final Integer realCAId = CACacheHelper.getCaCertHash(Integer.valueOf(caId)); if (realCAId != null) { // Since we have found a cached "real" CA Id and the cache will use this one (if cached) caId = realCAId.intValue(); } // 1. Check (new) CaCache if it is time to sync-up with database if (CaCache.INSTANCE.shouldCheckForUpdates(caId)) { log.debug("CA with ID " + caId + " will be checked for updates."); // 2. If cache is expired or missing, first thread to discover this reloads item from database and sends it to the cache try { CAData caData = getCAData(caId, null); final int digest = caData.getProtectString(0).hashCode(); // Special for splitting out the CAToken and committing it.. // Since getCAData has already run upgradeAndMergeToDatabase we can just get the CA here.. CA ca = caData.getCA(); // Note that we store using the "real" CAId in the cache. CaCache.INSTANCE.updateWith(caData.getCaId(), digest, ca.getName(), ca); // Since caching might be disabled, we return the value returned from the database here return ca; } catch (CADoesntExistsException e) { // Ensure that it is removed from cache CaCache.INSTANCE.removeEntry(caId); } // 3. The cache compares the database data with what is in the cache // 4. If database is different from cache, replace it in the cache } // 5. Get CA from cache (or null) and be merry return CaCache.INSTANCE.getEntry(caId); } @TransactionAttribute(TransactionAttributeType.REQUIRED) @Override public int mergeCa(final CA ca) { final int caId = ca.getCAId(); CAData caData = entityManager.find(CAData.class, caId); if (caData == null) { caData = new CAData(ca.getSubjectDN(), ca.getName(), ca.getStatus(), ca); } else { // It might be the case that the calling transaction has already loaded a reference to this object // and hence we need to get the same one and perform updates on this object instead of trying to // merge a new object. caData.setCA(ca); } caData = entityManager.merge(caData); // Since loading a CA is quite complex (populating CAInfo etc), we simple purge the cache here CaCache.INSTANCE.removeEntry(caId); return caId; } /** Performs upgrades on the entity if needed within a transaction. */ private CAData upgradeAndMergeToDatabase(CAData cadata) { if (cadata == null) { return null; } CAData caDataReturn = cadata; final LinkedHashMap<Object, Object> 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(); 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<Object, Object> data, String caName) { HashMap<String, String> tokendata = (HashMap<String, String>) 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<String, String>) 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 = (String) tokendata.get(CAToken.KEYSTORE); byte[] keyStoreData = null; if (StringUtils.isNotEmpty(str)) { keyStoreData = Base64.decode(str.getBytes()); } String propertyStr = (String) 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 = (String) 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)); } 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; } }