/************************************************************************* * * * 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.certificateprofile; import java.beans.XMLEncoder; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.apache.log4j.Logger; 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.StandardRules; import org.cesecore.certificates.ca.CaSessionLocal; import org.cesecore.certificates.certificate.CertificateConstants; import org.cesecore.config.CesecoreConfiguration; import org.cesecore.internal.InternalResources; import org.cesecore.jndi.JndiConstants; import org.cesecore.util.ProfileID; /** * Bean managing certificate profiles, see CertificateProfileSession for Javadoc. * * Version moved from EJBCA: CertificateProfileSessionBean.java 11170 2011-01-12 17:08:32Z anatom * * @version $Id: CertificateProfileSessionBean.java 29589 2018-08-08 12:02:53Z bastianf $ */ @Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "CertificateProfileSessionRemote") @TransactionAttribute(TransactionAttributeType.SUPPORTS) public class CertificateProfileSessionBean implements CertificateProfileSessionLocal, CertificateProfileSessionRemote { private static final Logger LOG = Logger.getLogger(CertificateProfileSessionBean.class); /** Internal localization of logs and errors */ private static final InternalResources INTRES = InternalResources.getInstance(); @PersistenceContext(unitName = CesecoreConfiguration.PERSISTENCE_UNIT) private EntityManager entityManager; @EJB private CaSessionLocal caSession; @EJB private AuthorizationSessionLocal authorizationSession; @EJB private SecurityEventsLoggerSessionLocal logSession; @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public int addCertificateProfile(final AuthenticationToken admin, final String name, final CertificateProfile profile) throws CertificateProfileExistsException, AuthorizationDeniedException { return addCertificateProfile(admin, findFreeCertificateProfileId(), name, profile); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public int addCertificateProfile(final AuthenticationToken admin, final int id, final String name, final CertificateProfile profile) throws CertificateProfileExistsException, AuthorizationDeniedException { if (isCertificateProfileNameFixed(name)) { final String msg = INTRES.getLocalizedMessage("store.errorcertprofilefixed", name); LOG.info(msg); // Things logged: // adminInfo: certserno, remote ip etc // module (integer), CA, RA etc // eventTime // username, if the event affects a user data (not here) // certificate info, if the event affects a certificate (not here) // event id // log message (free text string) // logSession.log(admin, admin.getCaId(), LogConstants.MODULE_CA, new java.util.Date(), null, null, LogConstants.EVENT_ERROR_CERTPROFILE, // msg); throw new CertificateProfileExistsException(msg); } // We need to check that admin also have rights to edit certificate profiles authorizedToEditProfile(admin, profile, id); if (isFreeCertificateProfileId(id)) { if (CertificateProfileData.findByProfileName(entityManager, name) == null) { entityManager.persist(new CertificateProfileData(Integer.valueOf(id), name, profile)); flushProfileCache(); final String msg = INTRES.getLocalizedMessage("store.addedcertprofile", name); Map details = new LinkedHashMap(); details.put("msg", msg); logSession.log(EventTypes.CERTPROFILE_CREATION, EventStatus.SUCCESS, ModuleTypes.CERTIFICATEPROFILE, ServiceTypes.CORE, admin.toString(), null, null, null, details); return id; } else { final String msg = INTRES.getLocalizedMessage("store.errorcertprofileexists", name); Map details = new LinkedHashMap(); details.put("msg", msg); throw new CertificateProfileExistsException(msg); } } else { final String msg = INTRES.getLocalizedMessage("store.errorcertprofileexists", id); throw new CertificateProfileExistsException(msg); } } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void changeCertificateProfile(final AuthenticationToken admin, final String name, final CertificateProfile profile) throws AuthorizationDeniedException { internalChangeCertificateProfileNoFlushCache(admin, name, profile); flushProfileCache(); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void internalChangeCertificateProfileNoFlushCache(final AuthenticationToken admin, final String name, final CertificateProfile profile) throws AuthorizationDeniedException { final CertificateProfileData pdl = CertificateProfileData.findByProfileName(entityManager, name); if (pdl == null) { final String msg = INTRES.getLocalizedMessage("store.erroreditprofile", name); Map details = new LinkedHashMap(); details.put("msg", msg); logSession.log(EventTypes.CERTPROFILE_EDITING, EventStatus.FAILURE, ModuleTypes.CERTIFICATEPROFILE, ServiceTypes.CORE, admin.toString(), null, null, null, details); } else { // We need to check that admin also have rights to edit certificate profiles authorizedToEditProfile(admin, profile, pdl.getId()); // Get the diff of what changed Map diff = pdl.getCertificateProfile().diff(profile); final String msg = INTRES.getLocalizedMessage("store.editedprofile", name); // Use a LinkedHashMap because we want the details logged (in the final log string) in the order we insert them, and not randomly Map details = new LinkedHashMap(); details.put("msg", msg); for (Map.Entry entry : diff.entrySet()) { details.put(entry.getKey().toString(), entry.getValue().toString()); } // Do the actual change pdl.setCertificateProfile(profile); logSession.log(EventTypes.CERTPROFILE_EDITING, EventStatus.SUCCESS, ModuleTypes.CERTIFICATEPROFILE, ServiceTypes.CORE, admin.toString(), null, null, null, details); } } @Override public void flushProfileCache() { if (LOG.isTraceEnabled()) { LOG.trace(">flushProfileCache"); } CertificateProfileCache.INSTANCE.updateProfileCache(entityManager, true); if (LOG.isDebugEnabled()) { LOG.debug("Flushed profile cache."); } } // flushProfileCache @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void cloneCertificateProfile(final AuthenticationToken admin, final String orgname, final String newname, final List authorizedCaIds) throws CertificateProfileExistsException, CertificateProfileDoesNotExistException, AuthorizationDeniedException { CertificateProfile profile = null; if (isCertificateProfileNameFixed(newname)) { final String msg = INTRES.getLocalizedMessage("store.errorcertprofilefixed", newname); LOG.info(msg); throw new CertificateProfileExistsException(msg); } try { final int origProfileId = getCertificateProfileId(orgname); if (origProfileId == 0) { final String msg = INTRES.getLocalizedMessage("store.errorcertprofilenotexist", orgname); LOG.info(msg); throw new CertificateProfileDoesNotExistException(msg); } final CertificateProfile p = getCertificateProfile(origProfileId); profile = p.clone(); if (authorizedCaIds != null) { profile.setAvailableCAs(authorizedCaIds); } // We need to check that admin also have rights to edit certificate profiles authorizedToEditProfile(admin, profile, origProfileId); if (CertificateProfileData.findByProfileName(entityManager, newname) == null) { entityManager.persist(new CertificateProfileData(findFreeCertificateProfileId(), newname, profile)); flushProfileCache(); final String msg = INTRES.getLocalizedMessage("store.addedprofilewithtempl", newname, orgname); Map details = new LinkedHashMap(); details.put("msg", msg); logSession.log(EventTypes.CERTPROFILE_CREATION, EventStatus.SUCCESS, ModuleTypes.CERTIFICATEPROFILE, ServiceTypes.CORE, admin.toString(), null, null, null, details); } else { final String msg = INTRES.getLocalizedMessage("store.erroraddprofilewithtempl", newname, orgname); throw new CertificateProfileExistsException(msg); } } catch (CloneNotSupportedException f) { // If this happens it's a programming error. Throw an exception! throw new IllegalStateException(f); } } @Override public List getAuthorizedCertificateProfileIds(final AuthenticationToken admin, final int certprofiletype) { final ArrayList returnval = new ArrayList(); final HashSet authorizedcaids = new HashSet(caSession.getAuthorizedCaIds(admin)); final HashSet allcaids = new HashSet(caSession.getAllCaIds()); // Add fixed certificate profiles. if (certprofiletype == CertificateConstants.CERTTYPE_UNKNOWN || certprofiletype == CertificateConstants.CERTTYPE_ENDENTITY || certprofiletype == CertificateConstants.CERTTYPE_HARDTOKEN) { returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_ENDUSER)); returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_OCSPSIGNER)); returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_SERVER)); } if (certprofiletype == CertificateConstants.CERTTYPE_UNKNOWN || certprofiletype == CertificateConstants.CERTTYPE_SUBCA) { returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_SUBCA)); } if (certprofiletype == CertificateConstants.CERTTYPE_UNKNOWN || certprofiletype == CertificateConstants.CERTTYPE_ROOTCA) { returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_ROOTCA)); } if (certprofiletype == CertificateConstants.CERTTYPE_UNKNOWN || certprofiletype == CertificateConstants.CERTTYPE_HARDTOKEN) { returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_HARDTOKENAUTH)); returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_HARDTOKENAUTHENC)); returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_HARDTOKENENC)); returnval.add(Integer.valueOf(CertificateProfileConstants.CERTPROFILE_FIXED_HARDTOKENSIGN)); } final boolean rootAccess = authorizationSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource()); for (final Entry cpEntry : CertificateProfileCache.INSTANCE.getProfileCache(entityManager).entrySet()) { final CertificateProfile profile = cpEntry.getValue(); // Check if all profiles available CAs exists in authorizedcaids. if (certprofiletype == 0 || certprofiletype == profile.getType() || (profile.getType() == CertificateConstants.CERTTYPE_ENDENTITY && certprofiletype == CertificateConstants.CERTTYPE_HARDTOKEN)) { boolean allexists = true; for (final Integer nextcaid : profile.getAvailableCAs()) { if (nextcaid.intValue() == CertificateProfile.ANYCA) { allexists = true; break; } // superadmin should be able to access profiles with missing CA Ids if (!authorizedcaids.contains(nextcaid) && (!rootAccess || allcaids.contains(nextcaid))) { allexists = false; break; } } if (allexists) { returnval.add(cpEntry.getKey()); } } } return returnval; } @Override public List getAuthorizedCertificateProfileWithMissingCAs(final AuthenticationToken admin) { final ArrayList returnval = new ArrayList(); if (!authorizationSession.isAuthorizedNoLogging(admin, StandardRules.ROLE_ROOT.resource())) { return returnval; } final HashSet allcaids = new HashSet(caSession.getAllCaIds()); allcaids.add(CertificateProfile.ANYCA); for (final Entry cpEntry : CertificateProfileCache.INSTANCE.getProfileCache(entityManager).entrySet()) { final CertificateProfile profile = cpEntry.getValue(); boolean nonExistingCA = false; for (final Integer caid : profile.getAvailableCAs()) { if (!allcaids.contains(caid)) { nonExistingCA = true; break; } } if (nonExistingCA) { returnval.add(cpEntry.getKey()); } } return returnval; } // getAuthorizedCertificateProfileWithMissingCAs @Override public CertificateProfile getCertificateProfile(final int id) { if (LOG.isTraceEnabled()) { LOG.trace(">getCertificateProfile(" + id + ")"); } CertificateProfile returnval = null; if (id < CertificateProfileConstants.FIXED_CERTIFICATEPROFILE_BOUNDRY) { returnval = new CertificateProfile(id); } else { // We need to clone the profile, otherwise the cache contents will be modifyable from the outside final CertificateProfile cprofile = CertificateProfileCache.INSTANCE.getProfileCache(entityManager).get(Integer.valueOf(id)); try { if (cprofile != null) { returnval = cprofile.clone(); } } catch (CloneNotSupportedException e) { LOG.error("Should never happen: ", e); throw new IllegalStateException(e); } } if (LOG.isTraceEnabled()) { LOG.trace(" getAllCertificateProfiles() { return CertificateProfileCache.INSTANCE.getProfileCache(entityManager); } @Override public CertificateProfile getCertificateProfile(final String name) { final Integer id = CertificateProfileCache.INSTANCE.getNameIdMapCache(entityManager).get(name); if (id == null) { return null; } else { return getCertificateProfile(id); } } @Override public int getCertificateProfileId(final String certificateprofilename) { if (LOG.isTraceEnabled()) { LOG.trace(">getCertificateProfileId: " + certificateprofilename); } int returnval = 0; final Integer id = CertificateProfileCache.INSTANCE.getNameIdMapCache(entityManager).get(certificateprofilename); if (id != null) { returnval = id.intValue(); } if (LOG.isTraceEnabled()) { LOG.trace("getCertificateProfileName: " + id); } final String returnval = CertificateProfileCache.INSTANCE.getIdNameMapCache(entityManager).get(Integer.valueOf(id)); if (LOG.isTraceEnabled()) { LOG.trace(" getCertificateProfileIdToNameMap() { if (LOG.isTraceEnabled()) { LOG.trace("> result = CertificateProfileData.findAll(entityManager); final Iterator iter = result.iterator(); while (iter.hasNext()) { final CertificateProfileData pdata = iter.next(); final String name = pdata.getCertificateProfileName(); pdata.upgradeProfile(); final float version = pdata.getCertificateProfile().getVersion(); if (LOG.isDebugEnabled()) { LOG.debug("Loaded certificate profile: " + name + " with version " + version); } } flushProfileCache(); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void renameCertificateProfile(final AuthenticationToken admin, final String oldname, final String newname) throws CertificateProfileExistsException, AuthorizationDeniedException { if (isCertificateProfileNameFixed(newname)) { final String msg = INTRES.getLocalizedMessage("store.errorcertprofilefixed", newname); throw new CertificateProfileExistsException(msg); } if (isCertificateProfileNameFixed(oldname)) { final String msg = INTRES.getLocalizedMessage("store.errorcertprofilefixed", oldname); throw new CertificateProfileExistsException(msg); } if (CertificateProfileData.findByProfileName(entityManager, newname) == null) { final CertificateProfileData pdl = CertificateProfileData.findByProfileName(entityManager, oldname); if (pdl == null) { final String msg = INTRES.getLocalizedMessage("store.errorrenameprofile", oldname, newname); Map details = new LinkedHashMap(); details.put("msg", msg); logSession.log(EventTypes.CERTPROFILE_RENAMING, EventStatus.FAILURE, ModuleTypes.CERTIFICATEPROFILE, ServiceTypes.CORE, admin.toString(), null, null, null, details); } else { // We need to check that admin also have rights to edit certificate profiles authorizedToEditProfile(admin, pdl.getCertificateProfile(), pdl.getId()); pdl.setCertificateProfileName(newname); flushProfileCache(); final String msg = INTRES.getLocalizedMessage("store.renamedprofile", oldname, newname); Map details = new LinkedHashMap(); details.put("msg", msg); logSession.log(EventTypes.CERTPROFILE_RENAMING, EventStatus.SUCCESS, ModuleTypes.CERTIFICATEPROFILE, ServiceTypes.CORE, admin.toString(), null, null, null, details); } } else { final String msg = INTRES.getLocalizedMessage("store.errorcertprofileexists", newname); throw new CertificateProfileExistsException(msg); } } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void removeCertificateProfile(final AuthenticationToken admin, final String name) throws AuthorizationDeniedException { final CertificateProfileData pdl = CertificateProfileData.findByProfileName(entityManager, name); if (pdl == null) { if (LOG.isDebugEnabled()) { LOG.debug("Trying to remove a certificate profile that does not exist: " + name); } } else { // We need to check that admin also have rights to edit certificate profiles authorizedToEditProfile(admin, pdl.getCertificateProfile(), pdl.getId()); entityManager.remove(pdl); flushProfileCache(); final String msg = INTRES.getLocalizedMessage("store.removedprofile", name); Map details = new LinkedHashMap(); details.put("msg", msg); logSession.log(EventTypes.CERTPROFILE_DELETION, EventStatus.SUCCESS, ModuleTypes.CERTIFICATEPROFILE, ServiceTypes.CORE, admin.toString(), null, null, null, details); } } @Override public boolean existsCAIdInCertificateProfiles(final int caid) { for (final Entry cpEntry : CertificateProfileCache.INSTANCE.getProfileCache(entityManager).entrySet()) { final CertificateProfile certProfile = cpEntry.getValue(); if (certProfile.getType() == CertificateConstants.CERTTYPE_ENDENTITY) { for (Integer availableCaId : certProfile.getAvailableCAs()) { if (availableCaId.intValue() == caid) { if (LOG.isDebugEnabled()) { LOG.debug("CA exists in certificate profile " + cpEntry.getKey().toString()); } return true; } } } } return false; } @Override public boolean existsPublisherIdInCertificateProfiles(final int publisherid) { for (final Entry cpEntry : CertificateProfileCache.INSTANCE.getProfileCache(entityManager).entrySet()) { for (Integer availablePublisherId : cpEntry.getValue().getPublisherList()) { if (availablePublisherId.intValue() == publisherid) { if (LOG.isDebugEnabled()) { LOG.debug("Publisher exists in certificate profile " + cpEntry.getKey().toString()); } return true; } } } return false; } private boolean isCertificateProfileNameFixed(final String profilename) { if (CertificateProfile.FIXED_PROFILENAMES.contains(profilename)) { return true; } return false; } private int findFreeCertificateProfileId() { final ProfileID.DB db = new ProfileID.DB() { @Override public boolean isFree(int i) { return CertificateProfileData.findById(entityManager, Integer.valueOf(i))==null; } }; return ProfileID.getNotUsedID(db); } private boolean isFreeCertificateProfileId(final int id) { boolean foundfree = false; if ((id > CertificateProfileConstants.FIXED_CERTIFICATEPROFILE_BOUNDRY) && (CertificateProfileData.findById(entityManager, Integer.valueOf(id)) == null)) { foundfree = true; } return foundfree; } private void authorizedToEditProfile(AuthenticationToken admin, CertificateProfile profile, int id) throws AuthorizationDeniedException { // We need to check that admin also have rights to edit certificate profiles if (!authorizedToProfileWithResource(admin, profile, true, StandardRules.CERTIFICATEPROFILEEDIT.resource())) { final String msg = INTRES.getLocalizedMessage("store.editcertprofilenotauthorized", admin.toString(), id); throw new AuthorizationDeniedException(msg); } } @Override public boolean authorizedToProfileWithResource(AuthenticationToken admin, CertificateProfile profile, boolean logging, String... resources) { // We need to check that admin also have rights to the passed in resources final List rules = new ArrayList<>(Arrays.asList(resources)); // Check that admin is authorized to all CAids for (final Integer caid : profile.getAvailableCAs()) { rules.add(StandardRules.CAACCESS.resource() + caid); } // Perform authorization check boolean ret = false; if (logging) { ret = authorizationSession.isAuthorized(admin, rules.toArray(new String[rules.size()])); } else { ret = authorizationSession.isAuthorizedNoLogging(admin, rules.toArray(new String[rules.size()])); } return ret; } @Override public byte[] getProfileAsXml(final AuthenticationToken authenticationToken, final int profileId) throws CertificateProfileDoesNotExistException, AuthorizationDeniedException { CertificateProfile profile = null; profile = getCertificateProfile(profileId); if (profile == null) { throw new CertificateProfileDoesNotExistException("Could not find certificate profile with ID '" + profileId + "' in the database."); } if(!authorizedToProfileWithResource(authenticationToken, profile, true, StandardRules.CERTIFICATEPROFILEVIEW.resource())) { throw new AuthorizationDeniedException("User " + authenticationToken.toString() + " was not authorized to view certificate profile with id " + profileId); } try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLEncoder encoder = new XMLEncoder(baos)) { encoder.writeObject(profile.saveData()); encoder.close(); return baos.toByteArray(); } catch (IOException e) { String msg = "Could not encode profile with ID " + profileId + " to XML: " + e.getMessage(); LOG.debug(msg, e); throw new IllegalStateException(msg, e); } } }