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 * * 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.certificate; import java.math.BigInteger; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.spec.ECParameterSpec; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.ejb.CreateException; import javax.ejb.EJB; import javax.ejb.EJBException; import javax.ejb.SessionContext; import javax.ejb.Stateless; import javax.ejb.Timeout; import javax.ejb.Timer; import javax.ejb.TimerService; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.apache.commons.lang.StringUtils; 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.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.certificates.ca.internal.CaCertificateCache; import org.cesecore.certificates.certificate.request.RequestMessage; import org.cesecore.certificates.certificateprofile.CertificateProfileConstants; import org.cesecore.certificates.crl.RevokedCertInfo; import org.cesecore.config.CesecoreConfiguration; import org.cesecore.config.OcspConfiguration; import org.cesecore.internal.InternalResources; import org.cesecore.jndi.JndiConstants; import org.cesecore.keys.util.KeyTools; import org.cesecore.util.Base64; import org.cesecore.util.CertTools; import org.cesecore.util.StringTools; import org.ejbca.cvc.PublicKeyEC; /** * @version $Id: CertificateStoreSessionBean.java 21001 2015-03-25 12:01:03Z aveen4711 $ */ @Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "CertificateStoreSessionRemote") @TransactionAttribute(TransactionAttributeType.SUPPORTS) public class CertificateStoreSessionBean implements CertificateStoreSessionRemote, CertificateStoreSessionLocal { private final static Logger log = Logger.getLogger(CertificateStoreSessionBean.class); /** Internal localization of logs and errors */ private static final InternalResources INTRES = InternalResources.getInstance(); private static final int TIMERID_CACERTIFICATECACHE = 1; @PersistenceContext(unitName = CesecoreConfiguration.PERSISTENCE_UNIT) private EntityManager entityManager; @EJB private AccessControlSessionLocal accessSession; @EJB private SecurityEventsLoggerSessionLocal logSession; // Myself needs to be looked up in postConstruct @Resource private SessionContext sessionContext; private CertificateStoreSessionLocal certificateStoreSession; /* When the sessionContext is injected, the timerService should be looked up. * This is due to the Glassfish EJB verifier complaining. */ private TimerService timerService; /** Default create for SessionBean without any creation Arguments. */ @PostConstruct public void postConstruct() { // 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 isUniqueCertificateSerialNumberIndex we want to do it on the real bean in order to get // the transaction setting (NOT_SUPPORTED) which suspends the active transaction and makes the check outside the transaction certificateStoreSession = sessionContext.getBusinessObject(CertificateStoreSessionLocal.class); timerService = sessionContext.getTimerService(); } @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void initTimers() { // Reload CA certificate cache cache, and cancel/create timers if there are no timers or if the cache is empty (probably a fresh startup) if (getTimerCount(TIMERID_CACERTIFICATECACHE) == 0 || CaCertificateCache.INSTANCE.isCacheExpired()) { reloadCaCertificateCacheAndSetTimeout(); } else { log.info("Not initing CaCertificateCache reload timers, there are already some."); } } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void storeCertificate(AuthenticationToken admin, Certificate incert, String username, String cafp, int status, int type, int certificateProfileId, String tag, long updateTime) throws AuthorizationDeniedException { // Check that user is authorized to the CA that issued this certificate int caid = CertTools.getIssuerDN(incert).hashCode(); authorizedToCA(admin, caid); storeCertificateNoAuth(admin, incert, username, cafp, status, type, certificateProfileId, tag, updateTime); } /** Local interface only */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public CertificateDataWrapper storeCertificateNoAuth(AuthenticationToken adminForLogging, Certificate incert, String username, String cafp, int status, int type, int certificateProfileId, String tag, long updateTime) { if (log.isTraceEnabled()) { log.trace(">storeCertificateNoAuth(" + username + ", " + cafp + ", " + status + ", " + type + ")"); } final PublicKey pubk = enrichEcPublicKey(incert.getPublicKey(), cafp); // Create the certificate in one go with all parameters at once. This used to be important in EJB2.1 so the persistence layer only creates // *one* single // insert statement. If we do a home.create and the some setXX, it will create one insert and one update statement to the database. // Probably not important in EJB3 anymore final CertificateData data1; final boolean useBase64CertTable = CesecoreConfiguration.useBase64CertTable(); Base64CertData base64CertData = null; if (useBase64CertTable) { // use special table for encoded data if told so. base64CertData = new Base64CertData(incert); this.entityManager.persist(new Base64CertData(incert)); } data1 = new CertificateData(incert, pubk, username, cafp, status, type, certificateProfileId, tag, updateTime, useBase64CertTable); this.entityManager.persist(data1); final String serialNo = CertTools.getSerialNumberAsString(incert); final String msg = INTRES.getLocalizedMessage("store.storecert", username, data1.getFingerprint(), data1.getSubjectDN(), data1.getIssuerDN(), serialNo); Map<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); final String caId = String.valueOf(CertTools.getIssuerDN(incert).hashCode()); logSession.log(EventTypes.CERT_STORED, EventStatus.SUCCESS, ModuleTypes.CERTIFICATE, ServiceTypes.CORE, adminForLogging.toString(), caId, serialNo, username, details); if (log.isTraceEnabled()) { log.trace("<storeCertificateNoAuth()"); } return new CertificateDataWrapper(incert, data1, base64CertData); } /** * We need special handling here of CVC certificate with EC keys, because they lack EC parameters in all certs * except the Root certificate (CVCA) */ private PublicKey enrichEcPublicKey(final PublicKey pubk, final String cafp) { PublicKey ret = pubk; if ((pubk instanceof PublicKeyEC)) { PublicKeyEC pkec = (PublicKeyEC) pubk; // The public key of IS and DV certificate (CVC) do not have any parameters so we have to do some magic to get a complete EC public key ECParameterSpec spec = pkec.getParams(); if (spec == null) { // We need to enrich this public key with parameters try { if (cafp != null) { String cafingerp = cafp; CertificateData cacert = CertificateData.findByFingerprint(entityManager, cafp); if (cacert != null) { String nextcafp = cacert.getCaFingerprint(); int bar = 0; // never go more than 5 rounds, who knows what strange things can exist in the CAFingerprint column, make sure we // never get stuck here while ((!StringUtils.equals(cafingerp, nextcafp)) && (bar++ < 5)) { cacert = CertificateData.findByFingerprint(entityManager, cafp); if (cacert == null) { break; } cafingerp = nextcafp; nextcafp = cacert.getCaFingerprint(); } if (cacert != null) { // We found a root CA certificate, hopefully ? PublicKey pkwithparams = cacert.getCertificate(this.entityManager).getPublicKey(); ret = KeyTools.getECPublicKeyWithParams(pubk, pkwithparams); } } } } catch (InvalidKeySpecException e) { if (log.isDebugEnabled()) { log.debug("Can not enrich EC public key with missing parameters: ", e); } } } } // finished with ECC key special handling return ret; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public boolean updateCertificateOnly(AuthenticationToken authenticationToken, Certificate certificate) { final String fingerprint = CertTools.getFingerprintAsString(certificate); final CertificateData certificateData = CertificateData.findByFingerprint(entityManager, fingerprint); if (certificateData == null || certificateData.getCertificate(entityManager) != null) { return false; } final boolean useBase64CertTable = CesecoreConfiguration.useBase64CertTable(); if (useBase64CertTable) { // use special table for encoded data if told so. entityManager.persist(new Base64CertData(certificate)); } else { try { certificateData.setBase64Cert(new String(Base64.encode(certificate.getEncoded()))); } catch (CertificateEncodingException e) { log.error("Failed to encode certificate for fingerprint " + fingerprint, e); return false; } } final String username = certificateData.getUsername(); final String serialNo = CertTools.getSerialNumberAsString(certificate); final String msg = INTRES.getLocalizedMessage("store.storecert", username, fingerprint, certificateData.getSubjectDN(), certificateData.getIssuerDN(), serialNo); Map<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); final String caId = String.valueOf(CertTools.getIssuerDN(certificate).hashCode()); logSession.log(EventTypes.CERT_STORED, EventStatus.SUCCESS, ModuleTypes.CERTIFICATE, ServiceTypes.CORE, authenticationToken.toString(), caId, serialNo, username, details); return true; } @Override public Collection<String> listAllCertificates(String issuerdn) { if (log.isTraceEnabled()) { log.trace(">listAllCertificates()"); } // This method was only used from CertificateDataTest and it didn't care about the expireDate, so it will only select fingerprints now. return CertificateData.findFingerprintsByIssuerDN(entityManager, CertTools.stringToBCDNString(StringTools.strip(issuerdn))); } @Override public Collection<RevokedCertInfo> listRevokedCertInfo(String issuerdn, long lastbasecrldate) { if (log.isTraceEnabled()) { log.trace(">listRevokedCertInfo()"); } return CertificateData.getRevokedCertInfos(entityManager, CertTools.stringToBCDNString(StringTools.strip(issuerdn)), lastbasecrldate); } @Override public List<Certificate> findCertificatesBySubjectAndIssuer(String subjectDN, String issuerDN) { return findCertificatesBySubjectAndIssuer(subjectDN, issuerDN, false); } @Override public List<Certificate> findCertificatesBySubjectAndIssuer(String subjectDN, String issuerDN, boolean onlyActive) { if (log.isTraceEnabled()) { log.trace( ">findCertificatesBySubjectAndIssuer(), dn='" + subjectDN + "' and issuer='" + issuerDN + "'"); } // First make a DN in our well-known format String dn = StringTools.strip(subjectDN); dn = CertTools.stringToBCDNString(dn); String issuerdn = StringTools.strip(issuerDN); issuerdn = CertTools.stringToBCDNString(issuerdn); if (log.isDebugEnabled()) { log.debug("Looking for cert with (transformed)DN: " + dn); } List<Certificate> ret = new ArrayList<Certificate>(); final Query query; if (onlyActive) { query = entityManager.createQuery("SELECT a FROM CertificateData a WHERE " + "a.subjectDN=:subjectDN AND a.issuerDN=:issuerDN" + " AND (a.status=:active OR a.status=:notifiedexpired OR (a.status=:revoked AND a.revocationReason=:onhold))" + "AND a.expireDate>:expireDate"); query.setParameter("active", CertificateConstants.CERT_ACTIVE); query.setParameter("notifiedexpired", CertificateConstants.CERT_NOTIFIEDABOUTEXPIRATION); query.setParameter("revoked", CertificateConstants.CERT_REVOKED); query.setParameter("onhold", RevokedCertInfo.REVOCATION_REASON_CERTIFICATEHOLD); query.setParameter("expireDate", System.currentTimeMillis()); } else { query = entityManager.createQuery( "SELECT a FROM CertificateData a WHERE a.subjectDN=:subjectDN AND a.issuerDN=:issuerDN"); } query.setParameter("subjectDN", subjectDN); query.setParameter("issuerDN", issuerDN); for (Object certificateData : query.getResultList()) { ret.add(((CertificateData) certificateData).getCertificate(this.entityManager)); } if (log.isTraceEnabled()) { log.trace( "<findCertificatesBySubjectAndIssuer(), dn='" + subjectDN + "' and issuer='" + issuerDN + "'"); } return ret; } @Override public Set<String> findUsernamesByIssuerDNAndSubjectDN(String issuerDN, String subjectDN) { if (log.isTraceEnabled()) { log.trace(">findUsernamesByIssuerDNAndSubjectDN(), issuer='" + issuerDN + "'"); } // First make a DN in our well-known format final String transformedIssuerDN = CertTools.stringToBCDNString(StringTools.strip(issuerDN)); final String transformedSubjectDN = CertTools.stringToBCDNString(StringTools.strip(subjectDN)); if (log.isDebugEnabled()) { log.debug("Looking for user with a certificate with issuer DN(transformed) '" + transformedIssuerDN + "' and subject DN(transformed) '" + transformedSubjectDN + "'."); } try { return CertificateData.findUsernamesBySubjectDNAndIssuerDN(entityManager, transformedSubjectDN, transformedIssuerDN); } finally { if (log.isTraceEnabled()) { log.trace("<findUsernamesByIssuerDNAndSubjectDN(), issuer='" + issuerDN + "'"); } } } @Override public Set<String> findUsernamesByIssuerDNAndSubjectKeyId(String issuerDN, byte[] subjectKeyId) { if (log.isTraceEnabled()) { log.trace(">findUsernamesByIssuerDNAndSubjectKeyId(), issuer='" + issuerDN + "'"); } // First make a DN in our well-known format final String transformedIssuerDN = CertTools.stringToBCDNString(StringTools.strip(issuerDN)); final String sSubjectKeyId = new String(Base64.encode(subjectKeyId, false)); if (log.isDebugEnabled()) { log.debug("Looking for user with a certificate with issuer DN(transformed) '" + transformedIssuerDN + "' and SubjectKeyId '" + sSubjectKeyId + "'."); } try { return CertificateData.findUsernamesByIssuerDNAndSubjectKeyId(entityManager, transformedIssuerDN, sSubjectKeyId); } finally { if (log.isTraceEnabled()) { log.trace("<findUsernamesByIssuerDNAndSubjectKeyId(), issuer='" + issuerDN + "'"); } } } @Override public String findUsernameByIssuerDnAndSerialNumber(String issuerDn, BigInteger serialNumber) { return CertificateData.findUsernameByIssuerDnAndSerialNumber(entityManager, issuerDn, serialNumber.toString()); } @SuppressWarnings("unchecked") @Override public String findUsernameByFingerprint(String fingerprint) { final Query query = entityManager .createQuery("SELECT a.username FROM CertificateData a WHERE a.fingerprint=:fingerprint"); query.setParameter("fingerprint", fingerprint); final List<String> usernames = query.getResultList(); if (usernames.isEmpty()) { return null; } else { return usernames.get(0); } } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public boolean isOnlyUsernameForSubjectKeyIdOrDnAndIssuerDN(final String issuerDN, final byte subjectKeyId[], final String subjectDN, final String username) { if (log.isTraceEnabled()) { log.trace(">isOnlyUsernameForSubjectKeyIdOrDnAndIssuerDN(), issuer='" + issuerDN + "'"); } // First make a DN in our well-known format final String transformedIssuerDN = CertTools.stringToBCDNString(StringTools.strip(issuerDN)); final String sSubjectKeyId = new String(Base64.encode(subjectKeyId, false)); final String transformedSubjectDN = CertTools.stringToBCDNString(StringTools.strip(subjectDN)); if (log.isDebugEnabled()) { log.debug("Looking for user with a certificate with issuer DN(transformed) '" + transformedIssuerDN + "' and SubjectKeyId '" + sSubjectKeyId + "' OR subject DN(transformed) '" + transformedSubjectDN + "'."); } try { final Set<String> usernames = CertificateData.findUsernamesBySubjectKeyIdOrDnAndIssuer(entityManager, transformedIssuerDN, sSubjectKeyId, transformedSubjectDN); return usernames.size() == 0 || (usernames.size() == 1 && usernames.contains(username)); } finally { if (log.isTraceEnabled()) { log.trace("<isOnlyUsernameForSubjectKeyIdOrDnAndIssuerDN(), issuer='" + issuerDN + "'"); } } } @Override public List<Certificate> findCertificatesBySubject(String subjectDN) { if (log.isTraceEnabled()) { log.trace(">findCertificatesBySubject(), dn='" + subjectDN + "'"); } // First make a DN in our well-known format String dn = StringTools.strip(subjectDN); dn = CertTools.stringToBCDNString(dn); if (log.isDebugEnabled()) { log.debug("Looking for cert with (transformed)DN: " + dn); } List<Certificate> ret = new ArrayList<Certificate>(); for (CertificateData certificate : CertificateData.findBySubjectDN(entityManager, dn)) { ret.add(certificate.getCertificate(this.entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesBySubject(), dn='" + subjectDN + "': " + ret.size()); } return ret; } @Override public X509Certificate findLatestX509CertificateBySubject(String subjectDN) { Collection<Certificate> certificates = findCertificatesBySubject(subjectDN); X509Certificate result = null; /** * Iterate through all certificates, find the X509Certificate with the newest date. */ for (Certificate certificate : certificates) { if (certificate instanceof X509Certificate) { X509Certificate x509Certificate = (X509Certificate) certificate; if (result == null || CertTools.getNotBefore(x509Certificate).after(CertTools.getNotBefore(result))) { result = x509Certificate; } } } return result; } @Override public List<Certificate> findCertificatesByExpireTimeWithLimit(Date expireTime) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByExpireTimeWithLimit(), time=" + expireTime); } // First make expiretime in well know format log.debug("Looking for certs that expire before: " + expireTime); List<CertificateData> coll = CertificateData.findByExpireDateWithLimit(entityManager, expireTime.getTime()); if (log.isDebugEnabled()) { log.debug("Found " + coll.size() + " certificates that expire before " + expireTime); } List<Certificate> ret = new ArrayList<Certificate>(); for (CertificateData certData : coll) { ret.add(certData.getCertificate(entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByExpireTimeWithLimit(), time=" + expireTime); } return ret; } @Override public List<Certificate> findCertificatesByExpireTimeWithLimit(Date expireTime, int maxNumberOfResults) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByExpireTimeWithLimit(), time=" + expireTime + " - maxNumberOfResults=" + maxNumberOfResults); } if (log.isDebugEnabled()) { log.debug("Looking for certs that expire before: " + expireTime); } List<CertificateData> coll = CertificateData.findByExpireDateWithLimit(entityManager, expireTime.getTime(), maxNumberOfResults); if (log.isDebugEnabled()) { log.debug("Found " + coll.size() + " certificates that expire before " + expireTime); } List<Certificate> ret = new ArrayList<Certificate>(); for (CertificateData certData : coll) { ret.add(certData.getCertificate(entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByExpireTimeWithLimit(), time=" + expireTime + " - maxNumberOfResults=" + maxNumberOfResults); } return ret; } @Override public List<Certificate> findCertificatesByExpireTimeAndIssuerWithLimit(Date expireTime, String issuerDN) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByExpireTimeWithLimit(), time=" + expireTime + " - issuerDN=" + issuerDN); } if (log.isDebugEnabled()) { log.debug("Looking for certs that expire before: " + expireTime); } List<CertificateData> coll = CertificateData.findByExpireDateAndIssuerWithLimit(entityManager, expireTime.getTime(), issuerDN); if (log.isDebugEnabled()) { log.debug("Found " + coll.size() + " certificates that expire before " + expireTime + " and issuerDN " + issuerDN); } List<Certificate> ret = new ArrayList<Certificate>(); for (CertificateData certData : coll) { ret.add(certData.getCertificate(entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByExpireTimeWithLimit(), time=" + expireTime + " - issuerDN=" + issuerDN); } return ret; } @Override public List<Certificate> findCertificatesByExpireTimeAndIssuerWithLimit(Date expireTime, String issuerDN, int maxNumberOfResults) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByExpireTimeWithLimit(), time=" + expireTime + " - issuerDN=" + issuerDN + " - maxNumberOfResults=" + maxNumberOfResults); } if (log.isDebugEnabled()) { log.debug("Looking for certs that expire before: " + expireTime); } List<CertificateData> coll = CertificateData.findByExpireDateAndIssuerWithLimit(entityManager, expireTime.getTime(), issuerDN, maxNumberOfResults); if (log.isDebugEnabled()) { log.debug("Found " + coll.size() + " certificates that expire before " + expireTime + " and issuerDN " + issuerDN); } List<Certificate> ret = new ArrayList<Certificate>(); for (CertificateData certData : coll) { ret.add(certData.getCertificate(entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByExpireTimeWithLimit(), time=" + expireTime + " issuerDN=" + issuerDN + " - maxNumberOfResults=" + maxNumberOfResults); } return ret; } @Override public List<Certificate> findCertificatesByExpireTimeAndTypeWithLimit(Date expireTime, int certificateType) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByExpireTimeAndTypeWithLimit(), time=" + expireTime + " - type=" + certificateType); } if (log.isDebugEnabled()) { log.debug("Looking for certs that expire before " + expireTime + " and of type " + certificateType); } List<CertificateData> coll = CertificateData.findByExpireDateAndTypeWithLimit(entityManager, expireTime.getTime(), certificateType); if (log.isDebugEnabled()) { log.debug("Found " + coll.size() + " certificates that expire before " + expireTime + " and of type " + certificateType); } List<Certificate> ret = new ArrayList<Certificate>(); for (CertificateData certData : coll) { ret.add(certData.getCertificate(entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByExpireTimeAndTypeWithLimit(), time=" + expireTime + " - type=" + certificateType); } return ret; } @Override public List<Certificate> findCertificatesByExpireTimeAndTypeWithLimit(Date expireTime, int certificateType, int maxNumberOfResults) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByExpireTimeAndTypeWithLimit(), time=" + expireTime + " - type=" + certificateType + " - maxNumberOfResults=" + maxNumberOfResults); } if (log.isDebugEnabled()) { log.debug("Looking for certs that expire before " + expireTime + " and of type " + certificateType); } List<CertificateData> coll = CertificateData.findByExpireDateAndTypeWithLimit(entityManager, expireTime.getTime(), certificateType, maxNumberOfResults); if (log.isDebugEnabled()) { log.debug("Found " + coll.size() + " certificates that expire before " + expireTime + " and of type " + certificateType); } List<Certificate> ret = new ArrayList<Certificate>(); for (CertificateData certData : coll) { ret.add(certData.getCertificate(entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByExpireTimeAndTypeWithLimit(), time=" + expireTime + " - type=" + certificateType + " - maxNumberOfResults=" + maxNumberOfResults); } return ret; } @Override public Collection<String> findUsernamesByExpireTimeWithLimit(Date expiretime) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByExpireTimeWithLimit: " + expiretime); } return CertificateData.findUsernamesByExpireTimeWithLimit(entityManager, new Date().getTime(), expiretime.getTime()); } @Override public Certificate findCertificateByIssuerAndSerno(String issuerDN, BigInteger serno) { if (log.isTraceEnabled()) { log.trace(">findCertificateByIssuerAndSerno(), dn:" + issuerDN + ", serno=" + serno.toString(16)); } // First make a DN in our well-known format String dn = CertTools.stringToBCDNString(StringTools.strip(issuerDN)); if (log.isDebugEnabled()) { log.debug("Looking for cert with (transformed)DN: " + dn); } Collection<CertificateData> coll = CertificateData.findByIssuerDNSerialNumber(entityManager, dn, serno.toString()); Certificate ret = null; if (coll.size() > 1) { String msg = INTRES.getLocalizedMessage("store.errorseveralissuerserno", issuerDN, serno.toString(16)); log.error(msg); } Certificate cert = null; // There are several certs, we will try to find the latest issued one for (CertificateData certificateData : coll) { cert = certificateData.getCertificate(this.entityManager); if (ret != null) { if (CertTools.getNotBefore(cert).after(CertTools.getNotBefore(ret))) { // cert is never than ret ret = cert; } } else { ret = cert; } } if (log.isTraceEnabled()) { log.trace("<findCertificateByIssuerAndSerno(), dn:" + issuerDN + ", serno=" + serno.toString(16)); } return ret; } @Override public CertificateInfo findFirstCertificateInfo(final String issuerDN, final BigInteger serno) { return CertificateData.findFirstCertificateInfo(entityManager, CertTools.stringToBCDNString(issuerDN), serno.toString()); } @Override public Collection<Certificate> findCertificatesByIssuerAndSernos(String issuerDN, Collection<BigInteger> sernos) { if (log.isTraceEnabled()) { log.trace(">findCertificateByIssuerAndSernos()"); } List<Certificate> ret = null; if (null == issuerDN || issuerDN.length() <= 0 || null == sernos || sernos.isEmpty()) { ret = new ArrayList<Certificate>(); } else { String dn = CertTools.stringToBCDNString(issuerDN); if (log.isDebugEnabled()) { log.debug("Looking for cert with (transformed)DN: " + dn); } ret = CertificateData.findCertificatesByIssuerDnAndSerialNumbers(entityManager, dn, sernos); } if (log.isTraceEnabled()) { log.trace("<findCertificateByIssuerAndSernos()"); } return ret; } @Override public Collection<Certificate> findCertificatesBySerno(BigInteger serno) { if (log.isTraceEnabled()) { log.trace(">findCertificatesBySerno(), serno=" + serno); } ArrayList<Certificate> ret = new ArrayList<Certificate>(); Collection<CertificateData> coll = CertificateData.findBySerialNumber(entityManager, serno.toString()); Iterator<CertificateData> iter = coll.iterator(); while (iter.hasNext()) { ret.add(iter.next().getCertificate(this.entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesBySerno(), serno=" + serno); } return ret; } @Override public String findUsernameByCertSerno(final BigInteger serno, final String issuerdn) { if (log.isTraceEnabled()) { log.trace(">findUsernameByCertSerno(), serno: " + serno.toString(16) + ", issuerdn: " + issuerdn); } final String ret = CertificateData.findLastUsernameByIssuerDNSerialNumber(entityManager, CertTools.stringToBCDNString(issuerdn), serno.toString()); if (log.isTraceEnabled()) { log.trace("<findUsernameByCertSerno(), ret=" + ret); } return ret; } @Override public List<Certificate> findCertificatesByUsername(String username) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByUsername(), username=" + username); } // This method on the entity bean does the ordering in the database List<CertificateData> coll = CertificateData.findByUsernameOrdered(entityManager, username); ArrayList<Certificate> ret = new ArrayList<Certificate>(); Iterator<CertificateData> iter = coll.iterator(); while (iter.hasNext()) { ret.add(iter.next().getCertificate(this.entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByUsername(), username=" + username); } return ret; } @Override public Collection<Certificate> findCertificatesByUsernameAndStatus(String username, int status) { if (log.isTraceEnabled()) { log.trace(">findCertificatesByUsernameAndStatus(), username=" + username); } ArrayList<Certificate> ret = new ArrayList<Certificate>(); // This method on the entity bean does the ordering in the database Collection<CertificateData> coll = CertificateData.findByUsernameAndStatus(entityManager, username, status); Iterator<CertificateData> iter = coll.iterator(); while (iter.hasNext()) { ret.add(iter.next().getCertificate(this.entityManager)); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByUsernameAndStatus(), username=" + username); } return ret; } @Override public CertificateInfo getCertificateInfo(String fingerprint) { if (log.isTraceEnabled()) { log.trace(">getCertificateInfo(): " + fingerprint); } if (fingerprint == null) { return null; } return CertificateData.getCertificateInfo(entityManager, fingerprint); } @Override public Certificate findCertificateByFingerprint(String fingerprint) { if (log.isTraceEnabled()) { log.trace(">findCertificateByFingerprint()"); } Certificate ret = null; try { CertificateData res = CertificateData.findByFingerprint(entityManager, fingerprint); if (res != null) { ret = res.getCertificate(this.entityManager); } } catch (Exception e) { log.error("Error finding certificate with fp: " + fingerprint); throw new EJBException(e); } if (log.isTraceEnabled()) { log.trace("<findCertificateByFingerprint()"); } return ret; } @SuppressWarnings("unchecked") @Override public Collection<Certificate> findCertificatesBySubjectKeyId(byte[] subjectKeyId) { final Query query = entityManager .createQuery("SELECT a FROM CertificateData a WHERE a.subjectKeyId=:subjectKeyId"); query.setParameter("subjectKeyId", new String(Base64.encode(subjectKeyId, false))); Collection<Certificate> result = new ArrayList<Certificate>(); for (CertificateData certificateData : (Collection<CertificateData>) query.getResultList()) { result.add(certificateData.getCertificate(this.entityManager)); } return result; } @Override public Collection<Certificate> findCertificatesByType(int type, String issuerDN) throws IllegalArgumentException { if (log.isTraceEnabled()) { log.trace(">findCertificatesByType()"); } if (type <= 0 || type > CertificateConstants.CERTTYPE_SUBCA + CertificateConstants.CERTTYPE_ENDENTITY + CertificateConstants.CERTTYPE_ROOTCA) { throw new IllegalArgumentException(); } Collection<Integer> ctypes = new ArrayList<Integer>(); if ((type & CertificateConstants.CERTTYPE_SUBCA) > 0) { ctypes.add(CertificateConstants.CERTTYPE_SUBCA); } if ((type & CertificateConstants.CERTTYPE_ENDENTITY) > 0) { ctypes.add(CertificateConstants.CERTTYPE_ENDENTITY); } if ((type & CertificateConstants.CERTTYPE_ROOTCA) > 0) { ctypes.add(CertificateConstants.CERTTYPE_ROOTCA); } List<Certificate> ret; if (null != issuerDN && issuerDN.length() > 0) { ret = CertificateData.findActiveCertificatesByTypeAndIssuer(entityManager, ctypes, CertTools.stringToBCDNString(issuerDN)); } else { ret = CertificateData.findActiveCertificatesByType(entityManager, ctypes); } if (log.isTraceEnabled()) { log.trace("<findCertificatesByType()"); } return ret; } @Override public List<Certificate> getCertificateChain(final CertificateInfo certinfo) { final List<Certificate> chain = new ArrayList<Certificate>(); final Set<String> seenFingerprints = new HashSet<String>(); CertificateInfo certInChain = certinfo; do { final String fingerprint = certInChain.getFingerprint(); final Certificate thecert = findCertificateByFingerprint(fingerprint); if (!seenFingerprints.add(fingerprint) || thecert == null) { break; // detected loop or missing cert. should not happen } chain.add(thecert); // roots are self-signed if (certInChain.getCAFingerprint().equals(fingerprint)) { break; } // proceed with issuer certInChain = getCertificateInfo(certInChain.getCAFingerprint()); } while (certInChain != null); // should not happen return chain; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public boolean setRevokeStatus(AuthenticationToken admin, String issuerdn, BigInteger serno, int reason, String userDataDN) throws CertificateRevokeException, AuthorizationDeniedException { return setRevokeStatus(admin, issuerdn, serno, new Date(), reason, userDataDN); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public boolean setRevokeStatus(AuthenticationToken admin, String issuerdn, BigInteger serno, Date revokedDate, int reason, String userDataDN) throws CertificateRevokeException, AuthorizationDeniedException { // authorization is handled by setRevokeStatus(admin, certificate, reason, userDataDN); Certificate certificate = findCertificateByIssuerAndSerno(issuerdn, serno); if (certificate == null) { String msg = INTRES.getLocalizedMessage("store.errorfindcertserno", null, serno); log.info(msg); throw new CertificateRevokeException(msg); } return setRevokeStatus(admin, certificate, revokedDate, reason, userDataDN); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public boolean setRevokeStatus(AuthenticationToken admin, Certificate certificate, int reason, String userDataDN) throws CertificateRevokeException, AuthorizationDeniedException { return setRevokeStatus(admin, certificate, new Date(), reason, userDataDN); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public boolean setRevokeStatus(AuthenticationToken admin, Certificate certificate, Date revokedDate, int reason, String userDataDN) throws CertificateRevokeException, AuthorizationDeniedException { if (certificate == null) { return false; } // Must be authorized to CA in order to change status is certificates issued by the CA int caid = CertTools.getIssuerDN(certificate).hashCode(); authorizedToCA(admin, caid); return setRevokeStatusNoAuth(admin, certificate, revokedDate, reason, userDataDN); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void setRevocationDate(AuthenticationToken authenticationToken, String certificateFingerprint, Date revocationDate) throws AuthorizationDeniedException { // Must be authorized to CA in order to change status is certificates issued by the CA final CertificateData certdata = CertificateData.findByFingerprint(this.entityManager, certificateFingerprint); if (certdata.getStatus() != CertificateConstants.CERT_REVOKED) { throw new UnsupportedOperationException( "Attempted to set revocation date on an unrevoked certificate."); } if (certdata.getRevocationDate() != 0) { throw new UnsupportedOperationException("Attempted to overwrite revocation date"); } final Certificate certificate = certdata.getCertificate(this.entityManager); int caid = CertTools.getIssuerDN(certificate).hashCode(); authorizedToCA(authenticationToken, caid); certdata.setRevocationDate(revocationDate); final String username = certdata.getUsername(); final String serialNo = CertTools.getSerialNumberAsString(certificate); // for logging final String msg = INTRES.getLocalizedMessage("store.revocationdateset", username, certificateFingerprint, certdata.getSubjectDN(), certdata.getIssuerDN(), serialNo, revocationDate); Map<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); //Log this as CERT_REVOKED since this data should have been added then. this.logSession.log(EventTypes.CERT_REVOKED, EventStatus.SUCCESS, ModuleTypes.CERTIFICATE, ServiceTypes.CORE, authenticationToken.toString(), String.valueOf(caid), serialNo, username, details); } /** Local interface only */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public boolean setRevokeStatusNoAuth(AuthenticationToken admin, Certificate certificate, Date revokeDate, int reason, String userDataDN) throws CertificateRevokeException { if (certificate == null) { return false; } if (log.isTraceEnabled()) { log.trace(">private setRevokeStatusNoAuth(Certificate), issuerdn=" + CertTools.getIssuerDN(certificate) + ", serno=" + CertTools.getSerialNumberAsString(certificate)); } int caid = CertTools.getIssuerDN(certificate).hashCode(); // used for logging String fp = CertTools.getFingerprintAsString(certificate); CertificateData rev = CertificateData.findByFingerprint(entityManager, fp); if (rev == null) { String msg = INTRES.getLocalizedMessage("store.errorfindcertfp", fp, CertTools.getSerialNumberAsString(certificate)); log.info(msg); throw new CertificateRevokeException(msg); } final String username = rev.getUsername(); final Date now = new Date(); final String serialNo = CertTools.getSerialNumberAsString(certificate); // for logging boolean returnVal = false; // A normal revocation if ((rev.getStatus() != CertificateConstants.CERT_REVOKED || rev.getRevocationReason() == RevokedCertInfo.REVOCATION_REASON_CERTIFICATEHOLD) && reason != RevokedCertInfo.NOT_REVOKED && reason != RevokedCertInfo.REVOCATION_REASON_REMOVEFROMCRL) { if (rev.getStatus() != CertificateConstants.CERT_REVOKED) { rev.setStatus(CertificateConstants.CERT_REVOKED); rev.setRevocationDate(revokeDate); // keep date if certificate on hold. } rev.setUpdateTime(now.getTime()); rev.setRevocationReason(reason); final String msg = INTRES.getLocalizedMessage("store.revokedcert", username, rev.getFingerprint(), Integer.valueOf(reason), rev.getSubjectDN(), rev.getIssuerDN(), serialNo); Map<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); logSession.log(EventTypes.CERT_REVOKED, EventStatus.SUCCESS, ModuleTypes.CERTIFICATE, ServiceTypes.CORE, admin.toString(), String.valueOf(caid), serialNo, username, details); returnVal = true; // we did change status } else if (((reason == RevokedCertInfo.NOT_REVOKED) || (reason == RevokedCertInfo.REVOCATION_REASON_REMOVEFROMCRL)) && (rev.getRevocationReason() == RevokedCertInfo.REVOCATION_REASON_CERTIFICATEHOLD)) { // Unrevoke, can only be done when the certificate was previously revoked with reason CertificateHold // Only allow unrevocation if the certificate is revoked and the revocation reason is CERTIFICATE_HOLD int status = CertificateConstants.CERT_ACTIVE; rev.setStatus(status); // long revocationDate = -1L; // A null Date to setRevocationDate will result in -1 stored in long column rev.setRevocationDate(null); rev.setUpdateTime(now.getTime()); rev.setRevocationReason(RevokedCertInfo.REVOCATION_REASON_REMOVEFROMCRL); final String msg = INTRES.getLocalizedMessage("store.unrevokedcert", username, rev.getFingerprint(), Integer.valueOf(reason), rev.getSubjectDN(), rev.getIssuerDN(), serialNo); Map<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); logSession.log(EventTypes.CERT_REVOKED, EventStatus.SUCCESS, ModuleTypes.CERTIFICATE, ServiceTypes.CORE, admin.toString(), String.valueOf(caid), serialNo, username, details); returnVal = true; // we did change status } else { final String msg = INTRES.getLocalizedMessage("store.ignorerevoke", serialNo, Integer.valueOf(rev.getStatus()), Integer.valueOf(reason)); log.info(msg); returnVal = false; // we did _not_ change status in the database } if (log.isTraceEnabled()) { log.trace("<private setRevokeStatusNoAuth(), issuerdn=" + CertTools.getIssuerDN(certificate) + ", serno=" + CertTools.getSerialNumberAsString(certificate)); } return returnVal; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void revokeAllCertByCA(AuthenticationToken admin, String issuerdn, int reason) throws AuthorizationDeniedException { int revoked = 0; // Must be authorized to CA in order to change status is certificates issued by the CA String bcdn = CertTools.stringToBCDNString(issuerdn); int caid = bcdn.hashCode(); authorizedToCA(admin, caid); try { final int maxRows = 10000; int firstResult = 0; // Revoking all non revoked certificates. // Update 10000 records at a time firstResult = 0; List<CertificateData> list = CertificateData.findAllNonRevokedCertificates(entityManager, bcdn, firstResult, maxRows); while (list.size() > 0) { for (int i = 0; i < list.size(); i++) { CertificateData d = list.get(i); d.setStatus(CertificateConstants.CERT_REVOKED); d.setRevocationDate(System.currentTimeMillis()); d.setRevocationReason(reason); revoked++; } firstResult += maxRows; list = CertificateData.findAllNonRevokedCertificates(entityManager, bcdn, firstResult, maxRows); } final String msg = INTRES.getLocalizedMessage("store.revokedallbyca", issuerdn, Integer.valueOf(revoked), Integer.valueOf(reason)); Map<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); logSession.log(EventTypes.CERT_REVOKED, EventStatus.SUCCESS, ModuleTypes.CERTIFICATE, ServiceTypes.CORE, admin.toString(), String.valueOf(caid), null, null, details); } catch (Exception e) { final String msg = INTRES.getLocalizedMessage("store.errorrevokeallbyca", issuerdn); log.info(msg); throw new EJBException(e); } } @Override public boolean checkIfAllRevoked(String username) { boolean returnval = true; Certificate certificate = null; Collection<Certificate> certs = findCertificatesByUsername(username); // Revoke all certs if (!certs.isEmpty()) { Iterator<Certificate> j = certs.iterator(); while (j.hasNext()) { certificate = j.next(); String fingerprint = CertTools.getFingerprintAsString(certificate); CertificateInfo info = getCertificateInfo(fingerprint); if (info != null && info.getStatus() != CertificateConstants.CERT_REVOKED) { returnval = false; break; } } } return returnval; } @Override public boolean isRevoked(String issuerDN, BigInteger serno) { if (log.isTraceEnabled()) { log.trace(">isRevoked(), dn:" + issuerDN + ", serno=" + serno.toString(16)); } // First make a DN in our well-known format String dn = CertTools.stringToBCDNString(issuerDN); boolean ret = false; try { Collection<CertificateData> coll = CertificateData.findByIssuerDNSerialNumber(entityManager, dn, serno.toString()); if (coll.size() > 0) { if (coll.size() > 1) { final String msg = INTRES.getLocalizedMessage("store.errorseveralissuerserno", issuerDN, serno.toString(16)); log.error(msg); } Iterator<CertificateData> iter = coll.iterator(); while (iter.hasNext()) { CertificateData data = iter.next(); // if any of the certificates with this serno is revoked, return true if (data.getStatus() == CertificateConstants.CERT_REVOKED) { ret = true; break; } } } else { // If there are no certificates with this serial number, return true (=revoked). Better safe than sorry! ret = true; if (log.isTraceEnabled()) { log.trace("isRevoked() did not find certificate with dn " + dn + " and serno " + serno.toString(16)); } } } catch (Exception e) { throw new EJBException(e); } if (log.isTraceEnabled()) { log.trace("<isRevoked() returned " + ret); } return ret; } @Override public CertificateStatus getStatus(String issuerDN, BigInteger serno) { if (log.isTraceEnabled()) { log.trace(">getStatus(), dn:" + issuerDN + ", serno=" + serno.toString(16)); } // First make a DN in our well-known format final String dn = CertTools.stringToBCDNString(issuerDN); try { Collection<CertificateData> coll = CertificateData.findByIssuerDNSerialNumber(entityManager, dn, serno.toString()); if (coll.size() > 1) { final String msg = INTRES.getLocalizedMessage("store.errorseveralissuerserno", issuerDN, serno.toString(16)); log.error(msg); } for (CertificateData data : coll) { final CertificateStatus result = getCertificateStatus(data); if (log.isTraceEnabled()) { log.trace("<getStatus() returned " + result + " for cert number " + serno.toString(16)); } return result; } if (log.isTraceEnabled()) { log.trace( "<getStatus() did not find certificate with dn " + dn + " and serno " + serno.toString(16)); } } catch (Exception e) { throw new EJBException(e); } return CertificateStatus.NOT_AVAILABLE; } @Override public CertificateStatusHolder getCertificateAndStatus(String issuerDN, BigInteger serno) { if (log.isTraceEnabled()) { log.trace(">getCertificateAndStatus(), dn:" + issuerDN + ", serno=" + serno.toString(16)); } // First make a DN in our well-known format final String dn = CertTools.stringToBCDNString(issuerDN); Collection<CertificateData> collection = CertificateData.findByIssuerDNSerialNumber(entityManager, dn, serno.toString()); if (collection.size() > 1) { final String msg = INTRES.getLocalizedMessage("store.errorseveralissuerserno", issuerDN, serno.toString(16)); log.error(msg); } for (CertificateData data : collection) { final CertificateStatus result = getCertificateStatus(data); if (log.isTraceEnabled()) { log.trace("<getStatus() returned " + result + " for cert number " + serno.toString(16)); } return new CertificateStatusHolder(data.getCertificate(entityManager), result); } if (log.isTraceEnabled()) { log.trace("<getCertificateAndStatus() did not find certificate with dn " + dn + " and serno " + serno.toString(16)); } return new CertificateStatusHolder(null, CertificateStatus.NOT_AVAILABLE); } /** * Algorithm: * If status is CERT_REVOKED the certificate is revoked and reason and date is picked up. * If status is CERT_ARCHIVED and reason is _NOT_ REMOVEFROMCRL or NOT_REVOKED the certificate is revoked and reason and date is picked up. * If status is CERT_ARCHIVED and reason is REMOVEFROMCRL or NOT_REVOKED the certificate is NOT revoked. * If status is neither CERT_REVOKED or CERT_ARCHIVED the certificate is NOT revoked * * @param data * @return CertificateStatus, can be compared (==) with CertificateStatus.OK, CertificateStatus.REVOKED and CertificateStatus.NOT_AVAILABLE */ private CertificateStatus getCertificateStatus(CertificateData data) { if (data == null) { return CertificateStatus.NOT_AVAILABLE; } final int pId; { final Integer tmp = data.getCertificateProfileId(); pId = tmp != null ? tmp.intValue() : CertificateProfileConstants.CERTPROFILE_NO_PROFILE; } final int status = data.getStatus(); if (status == CertificateConstants.CERT_REVOKED) { return new CertificateStatus(data.getRevocationDate(), data.getRevocationReason(), pId); } if (status != CertificateConstants.CERT_ARCHIVED) { return new CertificateStatus(CertificateStatus.OK.toString(), pId); } // If the certificate have status ARCHIVED, BUT revocationReason is REMOVEFROMCRL or NOTREVOKED, the certificate is OK // Otherwise it is a revoked certificate that has been archived and we must return REVOKED final int revReason = data.getRevocationReason(); // Read revocationReason from database if we really need to.. if (revReason == RevokedCertInfo.REVOCATION_REASON_REMOVEFROMCRL || revReason == RevokedCertInfo.NOT_REVOKED) { return new CertificateStatus(CertificateStatus.OK.toString(), pId); } return new CertificateStatus(data.getRevocationDate(), revReason, pId); } @Override public List<Object[]> findExpirationInfo(Collection<String> cas, Collection<Integer> certificateProfiles, long activeNotifiedExpireDateMin, long activeNotifiedExpireDateMax, long activeExpireDateMin) { return CertificateData.findExpirationInfo(entityManager, cas, certificateProfiles, activeNotifiedExpireDateMin, activeNotifiedExpireDateMax, activeExpireDateMin); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public boolean setStatus(AuthenticationToken admin, String fingerprint, int status) throws IllegalArgumentException, AuthorizationDeniedException { if ((status == CertificateConstants.CERT_REVOKED) || (status == CertificateConstants.CERT_ACTIVE)) { final String msg = INTRES.getLocalizedMessage("store.errorsetstatusargument", fingerprint, status); throw new IllegalArgumentException(msg); } CertificateData data = CertificateData.findByFingerprint(entityManager, fingerprint); if (data != null) { if (log.isDebugEnabled()) { log.debug("Set status " + status + " for certificate with fp: " + fingerprint); } // Must be authorized to CA in order to change status is certificates issued by the CA String bcdn = CertTools.stringToBCDNString(data.getIssuerDN()); int caid = bcdn.hashCode(); authorizedToCA(admin, caid); data.setStatus(status); final String serialNo = CertTools.getSerialNumberAsString(data.getCertificate(this.entityManager)); final String msg = INTRES.getLocalizedMessage("store.setstatus", data.getUsername(), fingerprint, status, data.getSubjectDN(), data.getIssuerDN(), serialNo); Map<String, Object> details = new LinkedHashMap<String, Object>(); details.put("msg", msg); logSession.log(EventTypes.CERT_CHANGEDSTATUS, EventStatus.SUCCESS, ModuleTypes.CERTIFICATE, ServiceTypes.CORE, admin.toString(), String.valueOf(caid), serialNo, data.getUsername(), details); } else { if (log.isDebugEnabled()) { final String msg = INTRES.getLocalizedMessage("store.setstatusfailed", fingerprint, status); log.debug(msg); } } return (data != null); } private void authorizedToCA(final AuthenticationToken admin, final int caid) throws AuthorizationDeniedException { if (!accessSession.isAuthorized(admin, StandardRules.CAACCESS.resource() + caid)) { final String msg = INTRES.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), caid); throw new AuthorizationDeniedException(msg); } } @TransactionAttribute(TransactionAttributeType.SUPPORTS) @Override public Certificate findMostRecentlyUpdatedActiveCertificate(byte[] subjectKeyId) { Certificate certificate = null; final String subjectKeyIdString = new String(Base64.encode(subjectKeyId, false)); log.debug("Searching for subjectKeyIdString " + subjectKeyIdString); final Query query = this.entityManager.createQuery( "SELECT a FROM CertificateData a WHERE a.subjectKeyId=:subjectKeyId AND a.status=:status ORDER BY a.updateTime DESC"); query.setParameter("subjectKeyId", subjectKeyIdString); query.setParameter("status", CertificateConstants.CERT_ACTIVE); query.setMaxResults(1); @SuppressWarnings("unchecked") final List<CertificateData> resultList = query.getResultList(); if (resultList.size() == 1) { certificate = resultList.get(0).getCertificate(this.entityManager); if (certificate == null && log.isDebugEnabled()) { log.debug("Reference to an issued certificate with subjectKeyId " + subjectKeyId + " found, but the certificate is not stored in the database."); } } return certificate; } @Override public String getCADnFromRequest(final RequestMessage req) { String dn = req.getIssuerDN(); if (log.isDebugEnabled()) { log.debug("Got an issuerDN: " + dn); } // If we have issuer and serialNo, we must find the CA certificate, to get the CAs subject name // If we don't have a serialNumber, we take a chance that it was actually the subjectDN (for example a RootCA) final BigInteger serno = req.getSerialNo(); if (serno != null) { if (log.isDebugEnabled()) { log.debug("Got a serialNumber: " + serno.toString(16)); } final Certificate cert = findCertificateByIssuerAndSerno(dn, serno); if (cert != null) { dn = CertTools.getSubjectDN(cert); } } if (log.isDebugEnabled()) { log.debug("Using DN: " + dn); } return dn; } // // Classes for checking Unique issuerDN/serialNumber index in the database. If we have such an index, we can allow // certificate serial number override, where user specifies the serial number to be put in the certificate. // @Override public void resetUniqueCertificateSerialNumberIndex() { log.info("Resetting isUniqueCertificateSerialNumberIndex to null."); UniqueSernoHelper.setIsUniqueCertificateSerialNumberIndex(null); } @Override public void setUniqueCertificateSerialNumberIndex(final Boolean value) { log.info("Setting isUniqueCertificateSerialNumberIndex to: " + value); UniqueSernoHelper.setIsUniqueCertificateSerialNumberIndex(value); } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public boolean isUniqueCertificateSerialNumberIndex() { // Must always run in a transaction in order to store certificates, EntityManager requires use within a transaction if (UniqueSernoHelper.getIsUniqueCertificateSerialNumberIndex() == null) { // Only create new transactions to store certificates and call this, if the variable is not initialized. // If it is already set we don't have to waste time creating a new transaction // Sets variables (but only once) that can be checked with isUniqueCertificateSerialNumberIndex(). // This part must be called first (at least once). final String userName = "checkUniqueIndexTestUserNotToBeUsed_fjasdfjsdjfsad"; // This name should only be used for this test. Made complex so that no one else will use the same. // Loading two dummy certificates. These certificates has same serial number and issuer. // It should not be possible to store both of them in the DB. final X509Certificate cert1 = UniqueSernoHelper.getTestCertificate1(); final X509Certificate cert2 = UniqueSernoHelper.getTestCertificate2(); final Certificate c1 = findCertificateByFingerprint(CertTools.getFingerprintAsString(cert1)); final Certificate c2 = findCertificateByFingerprint(CertTools.getFingerprintAsString(cert2)); if ((c1 != null) && (c2 != null)) { // already proved that not checking index for serial number. UniqueSernoHelper.setIsUniqueCertificateSerialNumberIndex(Boolean.FALSE); } final AuthenticationToken admin = new AlwaysAllowLocalAuthenticationToken( new UsernamePrincipal("Internal database constraint test")); if (c1 == null) {// storing initial certificate if no test certificate created. try { // needs to call using "certificateStoreSession." in order to honor the transaction annotations certificateStoreSession.checkForUniqueCertificateSerialNumberIndexInTransaction(admin, cert1, userName, "abcdef0123456789", CertificateConstants.CERT_INACTIVE, 0, 0, "", new Date().getTime()); } catch (Throwable e) { // NOPMD, we really need to catch all, never crash throw new RuntimeException("It should always be possible to store initial dummy certificate.", e); } } UniqueSernoHelper.setIsUniqueCertificateSerialNumberIndex(Boolean.FALSE); if (c2 == null) { // storing a second certificate with same issuer try { // needs to call using "certificateStoreSession." in order to honor the transaction annotations certificateStoreSession.checkForUniqueCertificateSerialNumberIndexInTransaction(admin, cert2, userName, "fedcba9876543210", CertificateConstants.CERT_INACTIVE, 0, 0, "", new Date().getTime()); } catch (Throwable e) { // NOPMD, we really need to catch all, never crash log.info( "certificateStoreSession.checkForUniqueCertificateSerialNumberIndexInTransaction threw Throwable (normal if there is a unique issuerDN/serialNumber index): " + e.getMessage()); log.info("Unique index in CertificateData table for certificate serial number"); // Exception is thrown when unique index is working and a certificate with same serial number is in the database. UniqueSernoHelper.setIsUniqueCertificateSerialNumberIndex(Boolean.TRUE); } } if (!UniqueSernoHelper.getIsUniqueCertificateSerialNumberIndex().booleanValue()) { // It was possible to store a second certificate with same serial number. Unique number not working. log.info(INTRES.getLocalizedMessage("createcert.not_unique_certserialnumberindex")); } // Remove potentially stored certificates so anyone can create the unique index if wanted try { certificateStoreSession.removeUniqueCertificateSerialNumberTestCertificates(); log.info("Removed rows used during test for unique certificate serial number database constraint."); } catch (Throwable e) { // NOPMD, we really need to catch all, never crash log.debug("Unable to clean up database rows used during test for unique certificate serial number." + " This is expected if DELETE is not granted to the EJBCA database user.", e); } } return UniqueSernoHelper.getIsUniqueCertificateSerialNumberIndex() != null && UniqueSernoHelper.getIsUniqueCertificateSerialNumberIndex().booleanValue(); } // We want each storage of a certificate to run in a new transactions, so we can catch errors as they happen.. @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void checkForUniqueCertificateSerialNumberIndexInTransaction(AuthenticationToken admin, Certificate incert, String username, String cafp, int status, int type, int certificateProfileId, String tag, long updateTime) throws CreateException, AuthorizationDeniedException { storeCertificate(admin, incert, username, cafp, status, type, certificateProfileId, tag, updateTime); } // We want deletion of a certificates to run in a new transactions, so we can catch errors as they happen.. @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void removeUniqueCertificateSerialNumberTestCertificates() { final X509Certificate x509Certificate1 = UniqueSernoHelper.getTestCertificate1(); final X509Certificate x509Certificate2 = UniqueSernoHelper.getTestCertificate2(); final String fingerprint1 = CertTools.getFingerprintAsString(x509Certificate1); final String fingerprint2 = CertTools.getFingerprintAsString(x509Certificate2); entityManager.createNativeQuery( "DELETE FROM Base64CertData WHERE fingerprint IN ('" + fingerprint1 + "', '" + fingerprint2 + "')") .executeUpdate(); entityManager.createNativeQuery( "DELETE FROM CertificateData WHERE fingerprint IN ('" + fingerprint1 + "', '" + fingerprint2 + "')") .executeUpdate(); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void updateLimitedCertificateDataStatus(final AuthenticationToken admin, final int caId, final String issuerDn, final BigInteger serialNumber, final Date revocationDate, final int reasonCode, final String caFingerprint) throws AuthorizationDeniedException { if (!accessSession.isAuthorizedNoLogging(admin, StandardRules.CAACCESS.resource() + caId)) { final String msg = INTRES.getLocalizedMessage("caadmin.notauthorizedtoca", admin.toString(), caId); throw new AuthorizationDeniedException(msg); } final CertificateInfo certificateInfo = findFirstCertificateInfo(issuerDn, serialNumber); final String limitedFingerprint = getLimitedCertificateDataFingerprint(issuerDn, serialNumber); final CertificateData limitedCertificateData = createLimitedCertificateData(admin, limitedFingerprint, issuerDn, serialNumber, revocationDate, reasonCode, caFingerprint); if (certificateInfo == null) { if (reasonCode == RevokedCertInfo.REVOCATION_REASON_REMOVEFROMCRL) { deleteLimitedCertificateData(limitedFingerprint); } else { // Create a limited entry log.info("Adding limited CertificateData entry with fingerprint=" + limitedFingerprint + ", serialNumber=" + serialNumber.toString(16).toUpperCase() + ", issuerDn='" + issuerDn + "'"); entityManager.persist(limitedCertificateData); } } else if (limitedFingerprint.equals(certificateInfo.getFingerprint())) { if (reasonCode == RevokedCertInfo.REVOCATION_REASON_REMOVEFROMCRL) { deleteLimitedCertificateData(limitedFingerprint); } else { if (certificateInfo.getStatus() != limitedCertificateData.getStatus() || certificateInfo.getRevocationDate().getTime() != limitedCertificateData .getRevocationDate() || certificateInfo.getRevocationReason() != limitedCertificateData.getRevocationReason()) { // Update the limited entry log.info("Updating limited CertificateData entry with fingerprint=" + limitedFingerprint + ", serialNumber=" + serialNumber.toString(16).toUpperCase() + ", issuerDn='" + issuerDn + "'"); entityManager.merge(limitedCertificateData); } else { if (log.isDebugEnabled()) { log.debug("Limited CertificateData entry with fingerprint=" + limitedFingerprint + ", serialNumber=" + serialNumber.toString(16).toUpperCase() + ", issuerDn='" + issuerDn + "' was already up to date."); } } } } else { // Refuse to update a normal entry with this method throw new UnsupportedOperationException( "Only limited certificate entries can be updated using this method."); } } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public void reloadCaCertificateCache() { log.info("Reloading CA certificate cache."); Collection<Certificate> certs = certificateStoreSession.findCertificatesByType( CertificateConstants.CERTTYPE_SUBCA + CertificateConstants.CERTTYPE_ROOTCA, null); CaCertificateCache.INSTANCE.loadCertificates(certs); } /** * When a timer expires, this method will update * * According to JSR 220 FR (18.2.2), this method may not throw any exceptions. * * @param timer The timer whose expiration caused this notification. */ @Timeout /* Glassfish 2.1.1: * "Timeout method ....timeoutHandler(javax.ejb.Timer)must have TX attribute of TX_REQUIRES_NEW or TX_REQUIRED or TX_NOT_SUPPORTED" * JBoss 5.1.0.GA: We cannot mix timer updates with our EJBCA DataSource transactions. */ @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void timeoutHandler(Timer timer) { if (log.isTraceEnabled()) { log.trace(">timeoutHandler: " + timer.getInfo().toString()); } if (timer.getInfo() instanceof Integer) { final int currentTimerId = ((Integer) timer.getInfo()).intValue(); if (currentTimerId == TIMERID_CACERTIFICATECACHE) { reloadCaCertificateCacheAndSetTimeout(); } } if (log.isTraceEnabled()) { log.trace("<timeoutHandler"); } } @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void reloadCaCertificateCacheAndSetTimeout() { if (log.isTraceEnabled()) { log.trace(">timeOutReloadCaCertificateCache"); } // Cancel any waiting timers of this type @SuppressWarnings("unchecked") final Collection<Timer> timers = timerService.getTimers(); for (final Timer timer : timers) { if (timer.getInfo() instanceof Integer) { final int currentTimerId = ((Integer) timer.getInfo()).intValue(); if (currentTimerId == TIMERID_CACERTIFICATECACHE) { timer.cancel(); } } } try { certificateStoreSession.reloadCaCertificateCache(); } finally { // Schedule a new timer of this type final long interval = OcspConfiguration.getSigningCertsValidTimeInMilliseconds(); if (interval > 0) { timerService.createTimer(interval, Integer.valueOf(TIMERID_CACERTIFICATECACHE)); } } } /** @return the number of timers where TimerInfo is an Integer and hold the specified value */ private int getTimerCount(final int id) { if (log.isTraceEnabled()) { log.trace(">getTimerCount"); } int count = 0; @SuppressWarnings("unchecked") final Collection<Timer> timers = timerService.getTimers(); for (final Timer timer : timers) { if (timer.getInfo() instanceof Integer) { final int currentTimerId = ((Integer) timer.getInfo()).intValue(); if (currentTimerId == id) { count++; } } } if (log.isTraceEnabled()) { log.trace("<getTimerCount, timers: " + count); } return count; } /** @return a limited CertificateData object based on the information we have */ private CertificateData createLimitedCertificateData(final AuthenticationToken admin, final String limitedFingerprint, final String issuerDn, final BigInteger serialNumber, final Date revocationDate, final int reasonCode, final String caFingerprint) { CertificateData certificateData = new CertificateData(); certificateData.setFingerprint(limitedFingerprint); certificateData.setSerialNumber(serialNumber.toString()); certificateData.setIssuer(issuerDn); // The idea is to set SubjectDN to an empty string. However, since Oracle treats an empty String as NULL, // and since CertificateData.SubjectDN has a constraint that it should not be NULL, we are setting it to // "CN=limited" instead of an empty string certificateData.setSubjectDN("CN=limited"); certificateData.setCertificateProfileId(new Integer(CertificateProfileConstants.CERTPROFILE_NO_PROFILE)); certificateData.setStatus(CertificateConstants.CERT_REVOKED); certificateData.setRevocationReason(reasonCode); certificateData.setRevocationDate(revocationDate); certificateData.setUpdateTime(new Long(new Date().getTime())); certificateData.setCaFingerprint(caFingerprint); return certificateData; } /** @return something that looks like a normal certificate fingerprint and is unique for each certificate entry */ private String getLimitedCertificateDataFingerprint(final String issuerDn, final BigInteger serialNumber) { return CertTools.getFingerprintAsString((issuerDn + ";" + serialNumber).getBytes()); } /** Remove limited CertificateData by fingerprint (and ensures that this is not a full entry by making sure that subjectKeyId is NULL */ private boolean deleteLimitedCertificateData(final String fingerprint) { log.info("Removing CertificateData entry with fingerprint=" + fingerprint + " and no subjectKeyId is defined."); final Query query = entityManager.createQuery( "DELETE FROM CertificateData a WHERE a.fingerprint=:fingerprint AND subjectKeyId IS NULL"); query.setParameter("fingerprint", fingerprint); final int deletedRows = query.executeUpdate(); if (log.isDebugEnabled()) { log.debug("Deleted " + deletedRows + " rows with fingerprint " + fingerprint); } return deletedRows == 1; } }