/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.pki.ca.server.impl;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.CertificateList;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
import org.bouncycastle.asn1.x509.ReasonFlags;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v2CRLBuilder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.audit.AuditEvent;
import org.xipki.commons.audit.AuditLevel;
import org.xipki.commons.audit.AuditService;
import org.xipki.commons.audit.AuditServiceRegister;
import org.xipki.commons.audit.AuditStatus;
import org.xipki.commons.common.HealthCheckResult;
import org.xipki.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.CompareUtil;
import org.xipki.commons.common.util.DateUtil;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.common.util.StringUtil;
import org.xipki.commons.security.CertRevocationInfo;
import org.xipki.commons.security.ConcurrentContentSigner;
import org.xipki.commons.security.CrlReason;
import org.xipki.commons.security.FpIdCalculator;
import org.xipki.commons.security.KeyUsage;
import org.xipki.commons.security.ObjectIdentifiers;
import org.xipki.commons.security.X509Cert;
import org.xipki.commons.security.XiSecurityConstants;
import org.xipki.commons.security.exception.NoIdleSignerException;
import org.xipki.commons.security.exception.XiSecurityException;
import org.xipki.commons.security.util.X509Util;
import org.xipki.pki.ca.api.BadCertTemplateException;
import org.xipki.pki.ca.api.BadFormatException;
import org.xipki.pki.ca.api.NameId;
import org.xipki.pki.ca.api.OperationException;
import org.xipki.pki.ca.api.OperationException.ErrorCode;
import org.xipki.pki.ca.api.RequestType;
import org.xipki.pki.ca.api.X509CertWithDbId;
import org.xipki.pki.ca.api.profile.CertValidity;
import org.xipki.pki.ca.api.profile.CertprofileException;
import org.xipki.pki.ca.api.profile.ExtensionValue;
import org.xipki.pki.ca.api.profile.ExtensionValues;
import org.xipki.pki.ca.api.profile.x509.SpecialX509CertprofileBehavior;
import org.xipki.pki.ca.api.profile.x509.SubjectInfo;
import org.xipki.pki.ca.api.profile.x509.X509CertVersion;
import org.xipki.pki.ca.api.publisher.x509.X509CertificateInfo;
import org.xipki.pki.ca.server.impl.cmp.CmpRequestorEntryWrapper;
import org.xipki.pki.ca.server.impl.cmp.CmpRequestorInfo;
import org.xipki.pki.ca.server.impl.store.CertificateStore;
import org.xipki.pki.ca.server.impl.store.X509CertWithRevocationInfo;
import org.xipki.pki.ca.server.impl.util.CaUtil;
import org.xipki.pki.ca.server.mgmt.api.CaHasRequestorEntry;
import org.xipki.pki.ca.server.mgmt.api.CaHasUserEntry;
import org.xipki.pki.ca.server.mgmt.api.CaMgmtException;
import org.xipki.pki.ca.server.mgmt.api.CaStatus;
import org.xipki.pki.ca.server.mgmt.api.CertListInfo;
import org.xipki.pki.ca.server.mgmt.api.CertListOrderBy;
import org.xipki.pki.ca.server.mgmt.api.CmpControl;
import org.xipki.pki.ca.server.mgmt.api.RequestorInfo;
import org.xipki.pki.ca.server.mgmt.api.ValidityMode;
import org.xipki.pki.ca.server.mgmt.api.x509.CrlControl;
import org.xipki.pki.ca.server.mgmt.api.x509.CrlControl.HourMinute;
import org.xipki.pki.ca.server.mgmt.api.x509.CrlControl.UpdateMode;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public class X509Ca {
private static class GrantedCertTemplate {
private final ConcurrentContentSigner signer;
private final Extensions extensions;
private final IdentifiedX509Certprofile certprofile;
private final Date grantedNotBefore;
private final Date grantedNotAfter;
private final X500Name requestedSubject;
private final SubjectPublicKeyInfo grantedPublicKey;
private final byte[] grantedPublicKeyData;
private final long fpPublicKey;
private final String warning;
private X500Name grantedSubject;
private String grantedSubjectText;
private long fpSubject;
public GrantedCertTemplate(Extensions extensions, IdentifiedX509Certprofile certprofile,
Date grantedNotBefore, Date grantedNotAfter, X500Name requestedSubject,
SubjectPublicKeyInfo grantedPublicKey, long fpPublicKey,
byte[] grantedPublicKeyData, ConcurrentContentSigner signer, String warning) {
this.extensions = extensions;
this.certprofile = certprofile;
this.grantedNotBefore = grantedNotBefore;
this.grantedNotAfter = grantedNotAfter;
this.requestedSubject = requestedSubject;
this.grantedPublicKey = grantedPublicKey;
this.grantedPublicKeyData = grantedPublicKeyData;
this.fpPublicKey = fpPublicKey;
this.signer = signer;
this.warning = warning;
}
public void setGrantedSubject(X500Name subject) {
this.grantedSubject = subject;
this.grantedSubjectText = X509Util.getRfc4519Name(subject);
this.fpSubject = X509Util.fpCanonicalizedName(subject);
}
}
private class ExpiredCertsRemover implements Runnable {
private boolean inProcess;
@Override
public void run() {
int keepDays = caInfo.getKeepExpiredCertInDays();
if (keepDays < 0) {
return;
}
if (inProcess) {
return;
}
inProcess = true;
final Date expiredAt = new Date(
System.currentTimeMillis() - DAY_IN_MS * (keepDays + 1));
try {
int num = removeExpirtedCerts(expiredAt, CaAuditConstants.MSGID_CA_routine);
LOG.info("removed {} certificates expired at {}", num, expiredAt.toString());
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not remove expired certificates");
} finally {
inProcess = false;
}
} // method run
} // class ExpiredCertsRemover
private class CrlGenerationService implements Runnable {
@Override
public void run() {
X509CrlSignerEntryWrapper crlSigner = getCrlSigner();
if (crlSigner == null
|| crlSigner.getCrlControl().getUpdateMode() != UpdateMode.interval) {
return;
}
if (crlGenInProcess.get()) {
return;
}
crlGenInProcess.set(true);
try {
doRun();
} catch (Throwable th) {
LogUtil.error(LOG, th);
} finally {
crlGenInProcess.set(false);
}
} // method run
private void doRun() throws OperationException {
final long signWindowMin = 20;
Date thisUpdate = new Date();
long minSinceCrlBaseTime = (thisUpdate.getTime() - caInfo.getCrlBaseTime().getTime())
/ MS_PER_MINUTE;
CrlControl control = getCrlSigner().getCrlControl();
int interval;
if (control.getIntervalMinutes() != null && control.getIntervalMinutes() > 0) {
long intervalMin = control.getIntervalMinutes();
interval = (int) (minSinceCrlBaseTime / intervalMin);
long baseTimeInMin = interval * intervalMin;
if (minSinceCrlBaseTime - baseTimeInMin > signWindowMin) {
// only generate CRL within the time window
return;
}
} else if (control.getIntervalDayTime() != null) {
HourMinute hm = control.getIntervalDayTime();
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.setTime(thisUpdate);
int minute = cal.get(Calendar.HOUR_OF_DAY) * 60 + cal.get(Calendar.MINUTE);
int scheduledMinute = hm.getHour() * 60 + hm.getMinute();
if (minute < scheduledMinute || minute - scheduledMinute > signWindowMin) {
return;
}
interval = (int) (minSinceCrlBaseTime % MINUTE_PER_DAY);
} else {
throw new RuntimeException("should not reach here, neither interval minutes"
+ " nor dateTime is specified");
}
boolean deltaCrl;
if (interval % control.getFullCrlIntervals() == 0) {
deltaCrl = false;
} else if (control.getDeltaCrlIntervals() > 0
&& interval % control.getDeltaCrlIntervals() == 0) {
deltaCrl = true;
} else {
return;
}
if (deltaCrl && !certstore.hasCrl(caIdent)) {
// DeltaCRL will be generated only if fullCRL exists
return;
}
long nowInSecond = thisUpdate.getTime() / MS_PER_SECOND;
long thisUpdateOfCurrentCrl = certstore.getThisUpdateOfCurrentCrl(caIdent);
if (nowInSecond - thisUpdateOfCurrentCrl <= (signWindowMin + 5) * 60) {
// CRL was just generated within SIGN_WINDOW_MIN + 5 minutes
return;
}
// find out the next interval for fullCRL and deltaCRL
int nextFullCrlInterval = 0;
int nextDeltaCrlInterval = 0;
for (int i = interval + 1;; i++) {
if (i % control.getFullCrlIntervals() == 0) {
nextFullCrlInterval = i;
break;
}
if (nextDeltaCrlInterval != 0 && control.getDeltaCrlIntervals() != 0
&& i % control.getDeltaCrlIntervals() == 0) {
nextDeltaCrlInterval = i;
}
}
int intervalOfNextUpdate;
if (deltaCrl) {
intervalOfNextUpdate = nextDeltaCrlInterval == 0 ? nextFullCrlInterval
: Math.min(nextFullCrlInterval, nextDeltaCrlInterval);
} else {
if (nextDeltaCrlInterval == 0) {
intervalOfNextUpdate = nextFullCrlInterval;
} else {
intervalOfNextUpdate = control.isExtendedNextUpdate() ? nextFullCrlInterval
: Math.min(nextFullCrlInterval, nextDeltaCrlInterval);
}
}
Date nextUpdate;
if (control.getIntervalMinutes() != null) {
int minutesTillNextUpdate = (intervalOfNextUpdate - interval)
* control.getIntervalMinutes() + control.getOverlapMinutes();
nextUpdate = new Date(MS_PER_SECOND * (nowInSecond + minutesTillNextUpdate * 60));
} else {
HourMinute hm = control.getIntervalDayTime();
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.setTime(new Date(nowInSecond * MS_PER_SECOND));
cal.add(Calendar.DAY_OF_YEAR, (intervalOfNextUpdate - interval));
cal.set(Calendar.HOUR_OF_DAY, hm.getHour());
cal.set(Calendar.MINUTE, hm.getMinute());
cal.add(Calendar.MINUTE, control.getOverlapMinutes());
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
nextUpdate = cal.getTime();
}
long maxIdOfDeltaCrlCache;
try {
maxIdOfDeltaCrlCache = certstore.getMaxIdOfDeltaCrlCache(caIdent);
generateCrl(deltaCrl, thisUpdate, nextUpdate, CaAuditConstants.MSGID_CA_routine);
} catch (Throwable th) {
LogUtil.error(LOG, th);
return;
}
try {
certstore.clearDeltaCrlCache(caIdent, maxIdOfDeltaCrlCache);
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not clear DeltaCRLCache of CA " + caIdent);
}
} // method doRun
} // class CrlGenerationService
private class SuspendedCertsRevoker implements Runnable {
private boolean inProcess;
@Override
public void run() {
if (caInfo.getRevokeSuspendedCertsControl() == null) {
return;
}
if (inProcess) {
return;
}
inProcess = true;
try {
LOG.debug("revoking suspended certificates");
int num = revokeSuspendedCerts(CaAuditConstants.MSGID_CA_routine);
if (num == 0) {
LOG.debug("revoked {} suspended certificates of CA '{}'", num, caIdent);
} else {
LOG.info("revoked {} suspended certificates of CA '{}'", num, caIdent);
}
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not revoke suspended certificates");
} finally {
inProcess = false;
}
} // method run
} // class SuspendedCertsRevoker
private static final long MS_PER_SECOND = 1000L;
private static final long MS_PER_MINUTE = 60000L;
private static final int MINUTE_PER_DAY = 24 * 60;
private static final long DAY_IN_MS = MS_PER_MINUTE * MINUTE_PER_DAY;
private static final long MAX_CERT_TIME_MS = 253402300799982L; //9999-12-31-23-59-59
private static final Logger LOG = LoggerFactory.getLogger(X509Ca.class);
private final X509CaInfo caInfo;
private final NameId caIdent;
private final X509Cert caCert;
private final CertificateStore certstore;
private final CaIdNameMap caIdNameMap;
private final boolean masterMode;
private final CaManagerImpl caManager;
private Boolean tryNssToVerify;
private AtomicBoolean crlGenInProcess = new AtomicBoolean(false);
private ScheduledFuture<?> crlGenerationService;
private ScheduledFuture<?> expiredCertsRemover;
private ScheduledFuture<?> suspendedCertsRevoker;
private AuditServiceRegister auditServiceRegister;
private final ConcurrentSkipListSet<Long> publicKeyCertsInProcess
= new ConcurrentSkipListSet<>();
private final ConcurrentSkipListSet<Long> subjectCertsInProcess
= new ConcurrentSkipListSet<>();
public X509Ca(final CaManagerImpl caManager, final X509CaInfo caInfo,
final CertificateStore certstore)
throws OperationException {
this.caManager = ParamUtil.requireNonNull("caManager", caManager);
this.masterMode = caManager.isMasterMode();
this.caIdNameMap = caManager.getIdNameMap();
this.caInfo = ParamUtil.requireNonNull("caInfo", caInfo);
this.caIdent = caInfo.getIdent();
this.caCert = caInfo.getCertificate();
this.certstore = ParamUtil.requireNonNull("certstore", certstore);
if (caInfo.isSignerRequired()) {
try {
caInfo.initSigner(caManager.getSecurityFactory());
} catch (XiSecurityException ex) {
LogUtil.error(LOG, ex, "security.createSigner caSigner for CA " + caIdent);
throw new OperationException(ErrorCode.SYSTEM_FAILURE, ex);
}
}
X509CrlSignerEntryWrapper crlSigner = getCrlSigner();
if (crlSigner != null) {
// CA signs the CRL
if (caManager.getCrlSignerWrapper(caInfo.getCrlSignerName()) == null
&& !X509Util.hasKeyusage(caCert.getCert(), KeyUsage.cRLSign)) {
final String msg = "CRL signer does not have keyusage cRLSign";
LOG.error(msg);
throw new OperationException(ErrorCode.SYSTEM_FAILURE, msg);
}
}
if (!masterMode) {
return;
}
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
publisher.caAdded(caCert);
}
Random random = new Random();
// CRL generation services
this.crlGenerationService = caManager.getScheduledThreadPoolExecutor().scheduleAtFixedRate(
new CrlGenerationService(), 60 + random.nextInt(60), 60, TimeUnit.SECONDS);
final int minutesOfDay = 24 * 60;
this.expiredCertsRemover = caManager.getScheduledThreadPoolExecutor().scheduleAtFixedRate(
new ExpiredCertsRemover(), minutesOfDay + random.nextInt(60), minutesOfDay,
TimeUnit.MINUTES);
this.suspendedCertsRevoker = caManager.getScheduledThreadPoolExecutor().scheduleAtFixedRate(
new SuspendedCertsRevoker(), random.nextInt(60), 60, TimeUnit.MINUTES);
} // constructor
public X509CaInfo getCaInfo() {
return caInfo;
}
public CmpControl getCmpControl() {
String name = caInfo.getCmpControlName();
return (name == null) ? null : caManager.getCmpControlObject(name);
}
public X509Certificate getCertificate(final BigInteger serialNumber)
throws CertificateException, OperationException {
X509CertificateInfo certInfo = certstore.getCertificateInfoForSerial(caIdent,
caCert, serialNumber, caIdNameMap);
return (certInfo == null) ? null : certInfo.getCert().getCert();
}
/**
*
* @param subjectName Subject of the certificate.
* @param transactionId <code>null</code> for all transactionIds.
*/
public List<X509Certificate> getCertificate(final X500Name subjectName,
final byte[] transactionId) throws OperationException {
return certstore.getCertificate(subjectName, transactionId);
}
public KnowCertResult knowsCertificate(final X509Certificate cert) throws OperationException {
ParamUtil.requireNonNull("cert", cert);
if (!caInfo.getSubject().equals(X509Util.getRfc4519Name(cert.getIssuerX500Principal()))) {
return KnowCertResult.UNKNOWN;
}
return certstore.knowsCertForSerial(caIdent, cert.getSerialNumber());
}
public X509CertWithRevocationInfo getCertWithRevocationInfo(final BigInteger serialNumber)
throws CertificateException, OperationException {
return certstore.getCertWithRevocationInfo(caIdent, serialNumber, caIdNameMap);
}
public byte[] getCertRequest(final BigInteger serialNumber)
throws OperationException {
return certstore.getCertRequest(caIdent, serialNumber);
}
public void checkCsr(CertificationRequest csr)
throws OperationException {
ParamUtil.requireNonNull("csr", csr);
if (!caManager.getSecurityFactory().verifyPopo(
csr, getCmpControl().getPopoAlgoValidator())) {
LOG.warn("could not validate POP for the pkcs#10 requst");
throw new OperationException(ErrorCode.BAD_POP);
}
}
public List<CertListInfo> listCertificates(final X500Name subjectPattern, final Date validFrom,
final Date validTo, final CertListOrderBy orderBy, final int numEntries)
throws OperationException {
return certstore.listCertificates(caIdent, subjectPattern, validFrom,
validTo, orderBy, numEntries);
}
public NameId authenticateUser(final String user, final byte[] password)
throws OperationException {
return certstore.authenticateUser(user.toUpperCase(), password);
}
public NameId getUserIdent(final int userId) throws OperationException {
return certstore.getUserIdent(userId);
}
public ByUserRequestorInfo getByUserRequestor(final NameId userIdent)
throws OperationException {
CaHasUserEntry caHasUser = certstore.getCaHasUser(caIdent, userIdent);
return (caHasUser == null) ? null : caManager.createByUserRequestor(caHasUser);
}
public X509CRL getCurrentCrl()
throws OperationException {
return getCrl(null);
}
public X509CRL getCrl(final BigInteger crlNumber)
throws OperationException {
LOG.info(" START getCrl: ca={}, crlNumber={}", caIdent, crlNumber);
boolean successful = false;
try {
byte[] encodedCrl = certstore.getEncodedCrl(caIdent, crlNumber);
if (encodedCrl == null) {
return null;
}
try {
X509CRL crl = X509Util.parseCrl(encodedCrl);
successful = true;
if (LOG.isInfoEnabled()) {
String timeStr = new Time(crl.getThisUpdate()).getTime();
LOG.info("SUCCESSFUL getCrl: ca={}, thisUpdate={}", caIdent, timeStr);
}
return crl;
} catch (CRLException | CertificateException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE, ex);
} catch (RuntimeException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE, ex);
}
} finally {
if (!successful) {
LOG.info(" FAILED getCrl: ca={}", caIdent);
}
}
} // method getCrl
public CertificateList getBcCurrentCrl()
throws OperationException {
return getBcCrl(null);
}
public CertificateList getBcCrl(final BigInteger crlNumber)
throws OperationException {
LOG.info(" START getCrl: ca={}, crlNumber={}", caIdent, crlNumber);
boolean successful = false;
try {
byte[] encodedCrl = certstore.getEncodedCrl(caIdent, crlNumber);
if (encodedCrl == null) {
return null;
}
try {
CertificateList crl = CertificateList.getInstance(encodedCrl);
successful = true;
if (LOG.isInfoEnabled()) {
LOG.info("SUCCESSFUL getCrl: ca={}, thisUpdate={}", caIdent,
crl.getThisUpdate().getTime());
}
return crl;
} catch (RuntimeException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE, ex);
}
} finally {
if (!successful) {
LOG.info(" FAILED getCrl: ca={}", caIdent);
}
}
} // method getCrl
private void cleanupCrlsWithoutException(final String msgId)
throws OperationException {
try {
cleanupCrls(msgId);
} catch (Throwable th) {
LOG.warn("could not cleanup CRLs.{}: {}", th.getClass().getName(), th.getMessage());
}
}
private void cleanupCrls(final String msgId) throws OperationException {
int numCrls = caInfo.getNumCrls();
LOG.info(" START cleanupCrls: ca={}, numCrls={}", caIdent, numCrls);
boolean successful = false;
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_cleanup_CRL, msgId);
try {
int num = (numCrls <= 0) ? 0
: certstore.cleanupCrls(caIdent, caInfo.getNumCrls());
successful = true;
event.addEventData(CaAuditConstants.NAME_num, num);
LOG.info("SUCCESSFUL cleanupCrls: ca={}, num={}", caIdent, num);
} catch (RuntimeException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE, ex);
} finally {
if (!successful) {
LOG.info(" FAILED cleanupCrls: ca={}", caIdent);
}
finish(event, successful);
}
} // method cleanupCrls
public X509CRL generateCrlOnDemand(final String msgId)
throws OperationException {
X509CrlSignerEntryWrapper crlSigner = getCrlSigner();
if (crlSigner == null) {
throw new OperationException(ErrorCode.NOT_PERMITTED, "CA could not generate CRL");
}
if (crlGenInProcess.get()) {
throw new OperationException(ErrorCode.SYSTEM_UNAVAILABLE, "TRY_LATER");
}
crlGenInProcess.set(true);
try {
Date thisUpdate = new Date();
Date nextUpdate = getCrlNextUpdate(thisUpdate);
if (nextUpdate != null && !nextUpdate.after(thisUpdate)) {
nextUpdate = null;
}
long maxIdOfDeltaCrlCache = certstore.getMaxIdOfDeltaCrlCache(caIdent);
X509CRL crl = generateCrl(false, thisUpdate, nextUpdate, msgId);
if (crl == null) {
return null;
}
try {
certstore.clearDeltaCrlCache(caIdent, maxIdOfDeltaCrlCache);
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not clear DeltaCRLCache of CA " + caIdent);
}
return crl;
} finally {
crlGenInProcess.set(false);
}
} // method generateCrlOnDemand
private X509CRL generateCrl(final boolean deltaCrl, final Date thisUpdate,
final Date nextUpdate, final String msgId) throws OperationException {
boolean successful = false;
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_gen_CRL, msgId);
try {
X509CRL crl = doGenerateCrl(deltaCrl, thisUpdate, nextUpdate, event, msgId);
successful = true;
return crl;
} finally {
finish(event, successful);
}
}
private X509CRL doGenerateCrl(final boolean deltaCrl, final Date thisUpdate,
final Date nextUpdate, final AuditEvent event, final String msgId)
throws OperationException {
X509CrlSignerEntryWrapper crlSigner = getCrlSigner();
if (crlSigner == null) {
throw new OperationException(ErrorCode.NOT_PERMITTED, "CRL generation is not allowed");
}
LOG.info(" START generateCrl: ca={}, deltaCRL={}, nextUpdate={}", caIdent, deltaCrl,
nextUpdate);
event.addEventData(CaAuditConstants.NAME_crlType, deltaCrl ? "DELTA_CRL" : "FULL_CRL");
if (nextUpdate == null) {
event.addEventData(CaAuditConstants.NAME_nextUpdate, "null");
} else {
event.addEventData(CaAuditConstants.NAME_nextUpdate,
DateUtil.toUtcTimeyyyyMMddhhmmss(nextUpdate));
if (nextUpdate.getTime() - thisUpdate.getTime() < 10 * 60 * MS_PER_SECOND) {
// less than 10 minutes
throw new OperationException(ErrorCode.CRL_FAILURE,
"nextUpdate and thisUpdate are too close");
}
}
CrlControl crlControl = crlSigner.getCrlControl();
boolean successful = false;
try {
ConcurrentContentSigner tmpCrlSigner = crlSigner.getSigner();
CrlControl control = crlSigner.getCrlControl();
boolean directCrl;
X500Name crlIssuer;
if (tmpCrlSigner == null) {
directCrl = true;
crlIssuer = caInfo.getPublicCaInfo().getX500Subject();
} else {
directCrl = false;
crlIssuer = X500Name.getInstance(
tmpCrlSigner.getCertificate().getSubjectX500Principal().getEncoded());
}
X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(crlIssuer, thisUpdate);
if (nextUpdate != null) {
crlBuilder.setNextUpdate(nextUpdate);
}
final int numEntries = 100;
List<CertRevInfoWithSerial> revInfos;
boolean isFirstCrlEntry = true;
Date notExpireAt;
if (control.isIncludeExpiredCerts()) {
notExpireAt = new Date(0);
} else {
// 10 minutes buffer
notExpireAt = new Date(thisUpdate.getTime() - 600L * MS_PER_SECOND);
}
long startId = 1;
do {
if (deltaCrl) {
revInfos = certstore.getCertsForDeltaCrl(caIdent, startId, numEntries,
control.isOnlyContainsCaCerts(), control.isOnlyContainsUserCerts());
} else {
revInfos = certstore.getRevokedCerts(caIdent, notExpireAt, startId,
numEntries, control.isOnlyContainsCaCerts(),
control.isOnlyContainsUserCerts());
}
long maxId = 1;
for (CertRevInfoWithSerial revInfo : revInfos) {
if (revInfo.getId() > maxId) {
maxId = revInfo.getId();
}
CrlReason reason = revInfo.getReason();
if (crlControl.isExcludeReason() && reason != CrlReason.REMOVE_FROM_CRL) {
reason = CrlReason.UNSPECIFIED;
}
Date revocationTime = revInfo.getRevocationTime();
Date invalidityTime = revInfo.getInvalidityTime();
switch (crlControl.getInvalidityDateMode()) {
case FORBIDDEN:
invalidityTime = null;
break;
case OPTIONAL:
break;
case REQUIRED:
if (invalidityTime == null) {
invalidityTime = revocationTime;
}
break;
default:
throw new RuntimeException("unknown TripleState: "
+ crlControl.getInvalidityDateMode());
}
BigInteger serial = revInfo.getSerial();
LOG.debug("added cert ca={} serial={} to CRL", caIdent, serial);
if (directCrl || !isFirstCrlEntry) {
if (invalidityTime != null) {
crlBuilder.addCRLEntry(serial, revocationTime, reason.getCode(),
invalidityTime);
} else {
crlBuilder.addCRLEntry(serial, revocationTime, reason.getCode());
}
continue;
}
List<Extension> extensions = new ArrayList<>(3);
if (reason != CrlReason.UNSPECIFIED) {
Extension ext = createReasonExtension(reason.getCode());
extensions.add(ext);
}
if (invalidityTime != null) {
Extension ext = createInvalidityDateExtension(invalidityTime);
extensions.add(ext);
}
Extension ext = createCertificateIssuerExtension(
caInfo.getPublicCaInfo().getX500Subject());
extensions.add(ext);
crlBuilder.addCRLEntry(serial, revocationTime,
new Extensions(extensions.toArray(new Extension[0])));
isFirstCrlEntry = false;
} // end for
startId = maxId + 1;
}
while (revInfos.size() >= numEntries);
// end do
BigInteger crlNumber = caInfo.nextCrlNumber();
event.addEventData(CaAuditConstants.NAME_crlNumber, crlNumber);
boolean onlyUserCerts = crlControl.isOnlyContainsUserCerts();
boolean onlyCaCerts = crlControl.isOnlyContainsCaCerts();
if (onlyUserCerts && onlyCaCerts) {
throw new RuntimeException(
"should not reach here, onlyUserCerts and onlyCACerts are both true");
}
try {
// AuthorityKeyIdentifier
byte[] akiValues = directCrl
? caInfo.getPublicCaInfo().getSubjectKeyIdentifer()
: crlSigner.getSubjectKeyIdentifier();
AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(akiValues);
crlBuilder.addExtension(Extension.authorityKeyIdentifier, false, aki);
// add extension CRL Number
crlBuilder.addExtension(Extension.cRLNumber, false, new ASN1Integer(crlNumber));
// IssuingDistributionPoint
if (onlyUserCerts || onlyCaCerts || !directCrl) {
IssuingDistributionPoint idp = new IssuingDistributionPoint(
(DistributionPointName) null, // distributionPoint,
onlyUserCerts, // onlyContainsUserCerts,
onlyCaCerts, // onlyContainsCACerts,
(ReasonFlags) null, // onlySomeReasons,
!directCrl, // indirectCRL,
false); // onlyContainsAttributeCerts
crlBuilder.addExtension(Extension.issuingDistributionPoint, true, idp);
}
// freshestCRL
List<String> deltaCrlUris = getCaInfo().getPublicCaInfo().getDeltaCrlUris();
if (control.getDeltaCrlIntervals() > 0 && CollectionUtil.isNonEmpty(deltaCrlUris)) {
CRLDistPoint cdp = CaUtil.createCrlDistributionPoints(deltaCrlUris,
caInfo.getPublicCaInfo().getX500Subject(), crlIssuer);
crlBuilder.addExtension(Extension.freshestCRL, false, cdp);
}
} catch (CertIOException ex) {
LogUtil.error(LOG, ex, "crlBuilder.addExtension");
throw new OperationException(ErrorCode.INVALID_EXTENSION, ex);
}
addXipkiCertset(crlBuilder, deltaCrl, control, notExpireAt, onlyCaCerts, onlyUserCerts);
ConcurrentContentSigner concurrentSigner = (tmpCrlSigner == null)
? caInfo.getSigner(null) : tmpCrlSigner;
X509CRLHolder crlHolder;
try {
crlHolder = concurrentSigner.build(crlBuilder);
} catch (NoIdleSignerException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE, "NoIdleSignerException: "
+ ex.getMessage());
}
try {
X509CRL crl = X509Util.toX509Crl(crlHolder.toASN1Structure());
caInfo.getCaEntry().setNextCrlNumber(crlNumber.longValue() + 1);
caManager.commitNextCrlNo(caIdent, caInfo.getCaEntry().getNextCrlNumber());
publishCrl(crl);
successful = true;
LOG.info("SUCCESSFUL generateCrl: ca={}, crlNumber={}, thisUpdate={}", caIdent,
crlNumber, crl.getThisUpdate());
if (!deltaCrl) {
// clean up the CRL
cleanupCrlsWithoutException(msgId);
}
return crl;
} catch (CRLException | CertificateException ex) {
throw new OperationException(ErrorCode.CRL_FAILURE, ex);
}
} finally {
if (!successful) {
LOG.info(" FAILED generateCrl: ca={}", caIdent);
}
}
} // method generateCrl
/**
* Add XiPKI extension CrlCertSet.
*
* <pre>
* Xipki-CrlCertSet ::= SET OF Xipki-CrlCert
*
* Xipki-CrlCert ::= SEQUENCE {
* serial INTEGER
* cert [0] EXPLICIT Certificate OPTIONAL
* profileName [1] EXPLICIT UTF8String OPTIONAL
* }
* </pre>
*/
private void addXipkiCertset(final X509v2CRLBuilder crlBuilder, final boolean deltaCrl,
final CrlControl control, final Date notExpireAt,
final boolean onlyCaCerts, final boolean onlyUserCerts) throws OperationException {
if (deltaCrl || !control.isXipkiCertsetIncluded()) {
return;
}
ASN1EncodableVector vector = new ASN1EncodableVector();
final int numEntries = 100;
long startId = 1;
List<SerialWithId> serials;
do {
serials = certstore.getCertSerials(caIdent, notExpireAt, startId, numEntries, false,
onlyCaCerts, onlyUserCerts);
long maxId = 1;
for (SerialWithId sid : serials) {
if (sid.getId() > maxId) {
maxId = sid.getId();
}
ASN1EncodableVector vec = new ASN1EncodableVector();
vec.add(new ASN1Integer(sid.getSerial()));
Integer profileId = null;
if (control.isXipkiCertsetCertIncluded()) {
X509CertificateInfo certInfo;
try {
certInfo = certstore.getCertificateInfoForId(caIdent, caCert,
sid.getId(), caIdNameMap);
} catch (CertificateException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE,
"CertificateException: " + ex.getMessage());
}
Certificate cert = Certificate.getInstance(certInfo.getCert().getEncodedCert());
vec.add(new DERTaggedObject(true, 0, cert));
if (control.isXipkiCertsetProfilenameIncluded()) {
profileId = certInfo.getProfile().getId();
}
} else if (control.isXipkiCertsetProfilenameIncluded()) {
profileId = certstore.getCertProfileForId(caIdent, sid.getId());
}
if (profileId != null) {
String profileName = caIdNameMap.getCertprofileName(profileId);
vec.add(new DERTaggedObject(true, 1, new DERUTF8String(profileName)));
}
vector.add(new DERSequence(vec));
} // end for
startId = maxId + 1;
}
while (serials.size() >= numEntries);
// end do
try {
crlBuilder.addExtension(ObjectIdentifiers.id_xipki_ext_crlCertset, false,
new DERSet(vector));
} catch (CertIOException ex) {
throw new OperationException(ErrorCode.INVALID_EXTENSION,
"CertIOException: " + ex.getMessage());
}
}
public X509CertificateInfo regenerateCertificate(final CertTemplateData certTemplate,
final RequestorInfo requestor, final RequestType reqType, final byte[] transactionId,
final String msgId) throws OperationException {
return regenerateCertificates(Arrays.asList(certTemplate), requestor, reqType,
transactionId, msgId).get(0);
}
public List<X509CertificateInfo> regenerateCertificates(
final List<CertTemplateData> certTemplates, final RequestorInfo requestor,
final RequestType reqType, final byte[] transactionId, final String msgId)
throws OperationException {
return generateCertificates(certTemplates, requestor, true, reqType, transactionId, msgId);
}
public boolean publishCertificate(final X509CertificateInfo certInfo) {
return doPublishCertificate(certInfo) == 0;
}
/**
*
* @param certInfo certificate to be published.
* @return 0 for published successfully, 1 if could not be published to CA certstore and
* any publishers, 2 if could be published to CA certstore but not to all publishers.
*/
private int doPublishCertificate(final X509CertificateInfo certInfo) {
ParamUtil.requireNonNull("certInfo", certInfo);
if (certInfo.isAlreadyIssued()) {
return 0;
}
if (!certstore.addCertificate(certInfo)) {
return 1;
}
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
if (!publisher.isAsyn()) {
boolean successful;
try {
successful = publisher.certificateAdded(certInfo);
} catch (RuntimeException ex) {
successful = false;
LogUtil.warn(LOG, ex, "could not publish certificate to the publisher "
+ publisher.getIdent());
}
if (successful) {
continue;
}
} // end if
Long certId = certInfo.getCert().getCertId();
try {
certstore.addToPublishQueue(publisher.getIdent(), certId.longValue(), caIdent);
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not add entry to PublishQueue");
return 2;
}
} // end for
return 0;
} // method doPublishCertificate
public boolean republishCertificates(final List<String> publisherNames, final int numThreads) {
List<IdentifiedX509CertPublisher> publishers;
if (publisherNames == null) {
publishers = getPublishers();
} else {
publishers = new ArrayList<>(publisherNames.size());
for (String publisherName : publisherNames) {
IdentifiedX509CertPublisher publisher = null;
for (IdentifiedX509CertPublisher p : getPublishers()) {
if (p.getIdent().getName().equals(publisherName)) {
publisher = p;
break;
}
}
if (publisher == null) {
throw new IllegalArgumentException(
"could not find publisher " + publisherName + " for CA " + caIdent);
}
publishers.add(publisher);
}
} // end if
if (CollectionUtil.isEmpty(publishers)) {
return true;
}
CaStatus status = caInfo.getStatus();
caInfo.setStatus(CaStatus.INACTIVE);
boolean onlyRevokedCerts = true;
for (IdentifiedX509CertPublisher publisher : publishers) {
if (publisher.publishsGoodCert()) {
onlyRevokedCerts = false;
}
NameId publisherIdent = publisher.getIdent();
try {
LOG.info("clearing PublishQueue for publisher {}", publisherIdent);
certstore.clearPublishQueue(caIdent, publisherIdent);
LOG.info(" cleared PublishQueue for publisher {}", publisherIdent);
} catch (OperationException ex) {
LogUtil.error(LOG, ex, "could not clear PublishQueue for publisher");
}
} // end for
try {
for (IdentifiedX509CertPublisher publisher : publishers) {
boolean successful = publisher.caAdded(caCert);
if (!successful) {
LOG.error("republish CA certificate {} to publisher {} failed", caIdent,
publisher.getIdent());
return false;
}
}
if (caInfo.getRevocationInfo() != null) {
for (IdentifiedX509CertPublisher publisher : publishers) {
boolean successful = publisher.caRevoked(caCert, caInfo.getRevocationInfo());
if (!successful) {
LOG.error("republishing CA revocation to publisher {} failed",
publisher.getIdent());
return false;
}
}
} // end if
CertRepublisher republisher = new CertRepublisher(caIdent, caCert,
caIdNameMap, certstore, publishers, onlyRevokedCerts, numThreads);
return republisher.republish();
} finally {
caInfo.setStatus(status);
}
} // method republishCertificates
public boolean clearPublishQueue(final List<String> publisherNames) throws CaMgmtException {
if (publisherNames == null) {
try {
certstore.clearPublishQueue(caIdent, null);
return true;
} catch (OperationException ex) {
throw new CaMgmtException(ex.getMessage(), ex);
}
}
for (String publisherName : publisherNames) {
NameId publisherIdent = caIdNameMap.getPublisher(publisherName);
try {
certstore.clearPublishQueue(caIdent, publisherIdent);
} catch (OperationException ex) {
throw new CaMgmtException(ex.getMessage(), ex);
}
}
return true;
} // method clearPublishQueue
public boolean publishCertsInQueue() {
boolean allSuccessful = true;
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
if (!publishCertsInQueue(publisher)) {
allSuccessful = false;
}
}
return allSuccessful;
}
private boolean publishCertsInQueue(final IdentifiedX509CertPublisher publisher) {
ParamUtil.requireNonNull("publisher", publisher);
final int numEntries = 500;
while (true) {
List<Long> certIds;
try {
certIds = certstore.getPublishQueueEntries(caIdent, publisher.getIdent(),
numEntries);
} catch (OperationException ex) {
LogUtil.error(LOG, ex);
return false;
}
if (CollectionUtil.isEmpty(certIds)) {
break;
}
for (Long certId : certIds) {
X509CertificateInfo certInfo;
try {
certInfo = certstore.getCertificateInfoForId(caIdent, caCert, certId,
caIdNameMap);
} catch (OperationException | CertificateException ex) {
LogUtil.error(LOG, ex);
return false;
}
boolean successful = publisher.certificateAdded(certInfo);
if (!successful) {
LOG.error("republishing certificate id={} failed", certId);
return false;
}
try {
certstore.removeFromPublishQueue(publisher.getIdent(), certId);
} catch (OperationException ex) {
LogUtil.warn(LOG, ex, "could not remove republished cert id=" + certId
+ " and publisher=" + publisher.getIdent());
continue;
}
} // end for
} // end while
return true;
} // method publishCertsInQueue
private boolean publishCrl(final X509CRL crl) {
if (!certstore.addCrl(caIdent, crl)) {
return false;
}
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
try {
publisher.crlAdded(caCert, crl);
} catch (RuntimeException ex) {
LogUtil.error(LOG, ex, "could not publish CRL to the publisher "
+ publisher.getIdent());
}
} // end for
return true;
} // method publishCrl
public X509CertWithRevocationInfo revokeCertificate(final BigInteger serialNumber,
final CrlReason reason, final Date invalidityTime, final String msgId)
throws OperationException {
if (caInfo.isSelfSigned() && caInfo.getSerialNumber().equals(serialNumber)) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
"insufficient permission to revoke CA certificate");
}
CrlReason tmpReason = reason;
if (tmpReason == null) {
tmpReason = CrlReason.UNSPECIFIED;
}
switch (tmpReason) {
case CA_COMPROMISE:
case AA_COMPROMISE:
case REMOVE_FROM_CRL:
throw new OperationException(ErrorCode.NOT_PERMITTED,
"Insufficient permission revoke certificate with reason "
+ tmpReason.getDescription());
case UNSPECIFIED:
case KEY_COMPROMISE:
case AFFILIATION_CHANGED:
case SUPERSEDED:
case CESSATION_OF_OPERATION:
case CERTIFICATE_HOLD:
case PRIVILEGE_WITHDRAWN:
break;
default:
throw new RuntimeException("unknown CRL reason " + tmpReason);
} // switch (reason)
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_revoke_cert, msgId);
boolean successful = true;
try {
X509CertWithRevocationInfo ret = doRevokeCertificate(serialNumber, reason,
invalidityTime, false, event);
successful = (ret != null);
return ret;
} finally {
finish(event, successful);
}
} // method revokeCertificate
public X509CertWithDbId unrevokeCertificate(final BigInteger serialNumber, final String msgId)
throws OperationException {
if (caInfo.isSelfSigned() && caInfo.getSerialNumber().equals(serialNumber)) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
"insufficient permission unrevoke CA certificate");
}
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_unrevoke_CERT, msgId);
boolean successful = true;
try {
X509CertWithDbId ret = doUnrevokeCertificate(serialNumber, false, event);
successful = true;
return ret;
} finally {
finish(event, successful);
}
} // method unrevokeCertificate
public X509CertWithDbId removeCertificate(final BigInteger serialNumber, String msgId)
throws OperationException {
if (caInfo.isSelfSigned() && caInfo.getSerialNumber().equals(serialNumber)) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
"insufficient permission remove CA certificate");
}
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_remove_cert, msgId);
boolean successful = true;
try {
X509CertWithDbId ret = doRemoveCertificate(serialNumber, event);
successful = (ret != null);
return ret;
} finally {
finish(event, successful);
}
} // method removeCertificate
private X509CertWithDbId doRemoveCertificate(final BigInteger serialNumber,
final AuditEvent event)
throws OperationException {
event.addEventData(CaAuditConstants.NAME_serial, LogUtil.formatCsn(serialNumber));
X509CertWithRevocationInfo certWithRevInfo =
certstore.getCertWithRevocationInfo(caIdent, serialNumber, caIdNameMap);
if (certWithRevInfo == null) {
return null;
}
boolean successful = true;
X509CertWithDbId certToRemove = certWithRevInfo.getCert();
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
boolean singleSuccessful;
try {
singleSuccessful = publisher.certificateRemoved(caCert, certToRemove);
} catch (RuntimeException ex) {
singleSuccessful = false;
LogUtil.warn(LOG, ex, "could not remove certificate to the publisher "
+ publisher.getIdent());
}
if (singleSuccessful) {
continue;
}
successful = false;
X509Certificate cert = certToRemove.getCert();
if (LOG.isErrorEnabled()) {
LOG.error("removing certificate issuer='{}', serial={}, subject='{}' from publisher"
+ " {} failed.", X509Util.getRfc4519Name(cert.getIssuerX500Principal()),
LogUtil.formatCsn(cert.getSerialNumber()),
X509Util.getRfc4519Name(cert.getSubjectX500Principal()), publisher.getIdent());
}
} // end for
if (!successful) {
return null;
}
certstore.removeCertificate(caIdent, serialNumber);
return certToRemove;
} // method doRemoveCertificate
private X509CertWithRevocationInfo doRevokeCertificate(final BigInteger serialNumber,
final CrlReason reason, final Date invalidityTime, final boolean force,
final AuditEvent event) throws OperationException {
String hexSerial = LogUtil.formatCsn(serialNumber);
event.addEventData(CaAuditConstants.NAME_serial, hexSerial);
event.addEventData(CaAuditConstants.NAME_reason, reason.getDescription());
if (invalidityTime != null) {
event.addEventData(CaAuditConstants.NAME_invalidityTime,
DateUtil.toUtcTimeyyyyMMddhhmmss(invalidityTime));
}
LOG.info(
" START revokeCertificate: ca={}, serialNumber={}, reason={}, invalidityTime={}",
caIdent, hexSerial, reason.getDescription(), invalidityTime);
X509CertWithRevocationInfo revokedCert = null;
CertRevocationInfo revInfo = new CertRevocationInfo(reason, new Date(), invalidityTime);
revokedCert = certstore.revokeCertificate(caIdent, serialNumber, revInfo,
force, shouldPublishToDeltaCrlCache(), caIdNameMap);
if (revokedCert == null) {
return null;
}
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
if (!publisher.isAsyn()) {
boolean successful;
try {
successful = publisher.certificateRevoked(caCert, revokedCert.getCert(),
revokedCert.getCertprofile(), revokedCert.getRevInfo());
} catch (RuntimeException ex) {
successful = false;
LogUtil.error(LOG, ex,
"could not publish revocation of certificate to the publisher "
+ publisher.getIdent());
}
if (successful) {
continue;
}
} // end if
Long certId = revokedCert.getCert().getCertId();
try {
certstore.addToPublishQueue(publisher.getIdent(), certId.longValue(), caIdent);
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not add entry to PublishQueue");
}
} // end for
if (LOG.isInfoEnabled()) {
LOG.info("SUCCESSFUL revokeCertificate: ca={}, serialNumber={}, reason={},"
+ " invalidityTime={}, revocationResult=REVOKED",
caIdent, hexSerial, reason.getDescription(), invalidityTime);
}
return revokedCert;
} // method doRevokeCertificate
private X509CertWithRevocationInfo revokeSuspendedCert(final BigInteger serialNumber,
final CrlReason reason, final String msgId)
throws OperationException {
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_revoke_suspendedCert, msgId);
boolean successful = false;
try {
X509CertWithRevocationInfo ret = doRevokeSuspendedCert(serialNumber, reason, event);
successful = (ret != null);
return ret;
} finally {
finish(event, successful);
}
}
private X509CertWithRevocationInfo doRevokeSuspendedCert(final BigInteger serialNumber,
final CrlReason reason, final AuditEvent event)
throws OperationException {
String hexSerial = LogUtil.formatCsn(serialNumber);
event.addEventData(CaAuditConstants.NAME_serial, hexSerial);
event.addEventData(CaAuditConstants.NAME_reason, reason.getDescription());
if (LOG.isInfoEnabled()) {
LOG.info(" START revokeSuspendedCert: ca={}, serialNumber={}, reason={}",
caIdent, hexSerial, reason.getDescription());
}
X509CertWithRevocationInfo revokedCert = certstore.revokeSuspendedCert(caIdent,
serialNumber, reason, shouldPublishToDeltaCrlCache(), caIdNameMap);
if (revokedCert == null) {
return null;
}
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
if (!publisher.isAsyn()) {
boolean successful;
try {
successful = publisher.certificateRevoked(caCert, revokedCert.getCert(),
revokedCert.getCertprofile(), revokedCert.getRevInfo());
} catch (RuntimeException ex) {
successful = false;
LogUtil.error(LOG, ex,
"could not publish revocation of certificate to the publisher "
+ publisher.getIdent());
}
if (successful) {
continue;
}
} // end if
Long certId = revokedCert.getCert().getCertId();
try {
certstore.addToPublishQueue(publisher.getIdent(), certId.longValue(), caIdent);
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not add entry to PublishQueue");
}
} // end for
if (LOG.isInfoEnabled()) {
LOG.info("SUCCESSFUL revokeSuspendedCert: ca={}, serialNumber={}, reason={}",
caIdent, hexSerial, reason.getDescription());
}
return revokedCert;
} // method doRevokeSuspendedCert
private X509CertWithDbId doUnrevokeCertificate(final BigInteger serialNumber,
final boolean force, final AuditEvent event) throws OperationException {
String hexSerial = LogUtil.formatCsn(serialNumber);
event.addEventData(CaAuditConstants.NAME_serial, hexSerial);
LOG.info(" START unrevokeCertificate: ca={}, serialNumber={}", caIdent, hexSerial);
X509CertWithDbId unrevokedCert = certstore.unrevokeCertificate(caIdent,
serialNumber, force, shouldPublishToDeltaCrlCache(), caIdNameMap);
if (unrevokedCert == null) {
return null;
}
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
if (!publisher.isAsyn()) {
boolean successful;
try {
successful = publisher.certificateUnrevoked(caCert, unrevokedCert);
} catch (RuntimeException ex) {
successful = false;
LogUtil.error(LOG, ex,
"could not publish unrevocation of certificate to the publisher "
+ publisher.getIdent());
}
if (successful) {
continue;
}
} // end if
Long certId = unrevokedCert.getCertId();
try {
certstore.addToPublishQueue(publisher.getIdent(), certId.longValue(), caIdent);
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not add entry to PublishQueue");
}
} // end for
LOG.info(
"SUCCESSFUL unrevokeCertificate: ca={}, serialNumber={}, revocationResult=UNREVOKED",
caIdent, hexSerial);
return unrevokedCert;
} // doUnrevokeCertificate
private boolean shouldPublishToDeltaCrlCache() {
X509CrlSignerEntryWrapper crlSigner = getCrlSigner();
if (crlSigner == null) {
return false;
}
CrlControl control = crlSigner.getCrlControl();
if (control.getUpdateMode() == UpdateMode.onDemand) {
return false;
}
int deltaCrlInterval = control.getDeltaCrlIntervals();
return deltaCrlInterval != 0 && deltaCrlInterval < control.getFullCrlIntervals();
} // method shouldPublishToDeltaCrlCache
public void revokeCa(final CertRevocationInfo revocationInfo, final String msgId)
throws OperationException {
ParamUtil.requireNonNull("revocationInfo", revocationInfo);
caInfo.setRevocationInfo(revocationInfo);
if (caInfo.isSelfSigned()) {
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_revoke_cert, msgId);
boolean successful = true;
try {
X509CertWithRevocationInfo ret = doRevokeCertificate(caInfo.getSerialNumber(),
revocationInfo.getReason(), revocationInfo.getInvalidityTime(), true,
event);
successful = (ret != null);
} finally {
finish(event, successful);
}
}
boolean failed = false;
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
NameId ident = publisher.getIdent();
boolean successful = publisher.caRevoked(caCert, revocationInfo);
if (successful) {
LOG.info("published event caRevoked of CA {} to publisher {}", caIdent, ident);
} else {
failed = true;
LOG.error("could not publish event caRevoked of CA {} to publisher {}", caIdent,
ident);
}
}
if (failed) {
final String message = "could not event caRevoked of CA " + caIdent
+ " to at least one publisher";
throw new OperationException(ErrorCode.SYSTEM_FAILURE, message);
}
} // method revokeCa
public void unrevokeCa(final String msgId) throws OperationException {
caInfo.setRevocationInfo(null);
if (caInfo.isSelfSigned()) {
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_unrevoke_CERT, msgId);
boolean successful = true;
try {
doUnrevokeCertificate(caInfo.getSerialNumber(), true, event);
successful = true;
} finally {
finish(event, successful);
}
}
boolean failed = false;
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
NameId ident = publisher.getIdent();
boolean successful = publisher.caUnrevoked(caCert);
if (successful) {
LOG.info("published event caUnrevoked of CA {} to publisher {}", caIdent, ident);
} else {
failed = true;
LOG.error("could not publish event caUnrevoked of CA {} to publisher {}", caIdent,
ident);
}
}
if (failed) {
final String message = "could not event caUnrevoked of CA " + caIdent
+ " to at least one publisher";
throw new OperationException(ErrorCode.SYSTEM_FAILURE, message);
}
} // method unrevokeCa
public long addRequest(final byte[] request) throws OperationException {
return certstore.addRequest(request);
}
public void addRequestCert(final long requestId, final long certId) throws OperationException {
certstore.addRequestCert(requestId, certId);
}
private List<IdentifiedX509CertPublisher> getPublishers() {
return caManager.getIdentifiedPublishersForCa(caIdent.getName());
}
public List<X509CertificateInfo> generateCertificates(
final List<CertTemplateData> certTemplates,
final RequestorInfo requestor, final RequestType reqType,
final byte[] transactionId, final String msgId)
throws OperationException {
return generateCertificates(certTemplates, requestor, false, reqType,
transactionId, msgId);
}
private List<X509CertificateInfo> generateCertificates(
final List<CertTemplateData> certTemplates,
final RequestorInfo requestor, final boolean keyUpdate,
final RequestType reqType, final byte[] transactionId, final String msgId)
throws OperationExceptionWithIndex {
ParamUtil.requireNonEmpty("certTemplates", certTemplates);
final int n = certTemplates.size();
List<GrantedCertTemplate> gcts = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
CertTemplateData certTemplate = certTemplates.get(i);
try {
GrantedCertTemplate gct = createGrantedCertTemplate(certTemplate,
requestor, keyUpdate);
gcts.add(gct);
} catch (OperationException ex) {
throw new OperationExceptionWithIndex(i, ex);
}
}
List<X509CertificateInfo> certInfos = new ArrayList<>(n);
OperationExceptionWithIndex exception = null;
for (int i = 0; i < n; i++) {
if (exception != null) {
break;
}
GrantedCertTemplate gct = gcts.get(i);
final NameId certprofilIdent = gct.certprofile.getIdent();
final String subjectText = gct.grantedSubjectText;
LOG.info(" START generateCertificate: CA={}, profile={}, subject='{}'", caIdent,
certprofilIdent, subjectText);
boolean successful = false;
try {
X509CertificateInfo certInfo = generateCertificate(gct, requestor,
false, reqType, transactionId, msgId);
successful = true;
certInfos.add(certInfo);
if (LOG.isInfoEnabled()) {
String prefix = certInfo.isAlreadyIssued() ? "RETURN_OLD_CERT" : "SUCCESSFUL";
X509CertWithDbId cert = certInfo.getCert();
LOG.info(
"{} generateCertificate: CA={}, profile={}, subject='{}', serialNumber={}",
prefix, caIdent, certprofilIdent, cert.getSubject(),
LogUtil.formatCsn(cert.getCert().getSerialNumber()));
}
} catch (OperationException ex) {
exception = new OperationExceptionWithIndex(i, ex);
} catch (Throwable th) {
exception = new OperationExceptionWithIndex(i,
new OperationException(ErrorCode.SYSTEM_FAILURE, th));
} finally {
if (!successful) {
LOG.warn(" FAILED generateCertificate: CA={}, profile={}, subject='{}'",
caIdent, certprofilIdent, subjectText);
}
}
}
if (exception != null) {
LOG.error("could not generate certificate for request[{}], reverted all generated"
+ " certificates", exception.getIndex());
// delete generated certificates
for (X509CertificateInfo m : certInfos) {
BigInteger serial = m.getCert().getCert().getSerialNumber();
try {
removeCertificate(serial, msgId);
} catch (Throwable thr) {
LogUtil.error(LOG, thr, "could not delete certificate serial=" + serial);
}
}
LogUtil.warn(LOG, exception);
throw exception;
}
return certInfos;
}
public X509CertificateInfo generateCertificate(final CertTemplateData certTemplate,
final RequestorInfo requestor, final RequestType reqType, final byte[] transactionId,
final String msgId)
throws OperationException {
ParamUtil.requireNonNull("certTemplate", certTemplate);
return generateCertificates(Arrays.asList(certTemplate), requestor,
reqType, transactionId, msgId).get(0);
}
private X509CertificateInfo generateCertificate(final GrantedCertTemplate gct,
final RequestorInfo requestor, final boolean keyUpdate, final RequestType reqType,
final byte[] transactionId, final String msgId)
throws OperationException {
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_gen_cert, msgId);
boolean successful = false;
try {
X509CertificateInfo ret = doGenerateCertificate(gct, requestor,
keyUpdate, reqType, transactionId, event);
successful = (ret != null);
return ret;
} finally {
finish(event, successful);
}
}
private X509CertificateInfo doGenerateCertificate(final GrantedCertTemplate gct,
final RequestorInfo requestor, final boolean keyUpdate, final RequestType reqType,
final byte[] transactionId, final AuditEvent event)
throws OperationException {
ParamUtil.requireNonNull("gct", gct);
event.addEventData(CaAuditConstants.NAME_reqSubject,
X509Util.getRfc4519Name(gct.requestedSubject));
event.addEventData(CaAuditConstants.NAME_certprofile, gct.certprofile.getIdent().getName());
event.addEventData(CaAuditConstants.NAME_notBefore,
DateUtil.toUtcTimeyyyyMMddhhmmss(gct.grantedNotBefore));
event.addEventData(CaAuditConstants.NAME_notAfter,
DateUtil.toUtcTimeyyyyMMddhhmmss(gct.grantedNotAfter));
adaptGrantedSubejct(gct);
IdentifiedX509Certprofile certprofile = gct.certprofile;
boolean publicKeyCertInProcessExisted = publicKeyCertsInProcess.add(gct.fpPublicKey);
if (!publicKeyCertInProcessExisted) {
if (!certprofile.isDuplicateKeyPermitted()) {
throw new OperationException(ErrorCode.ALREADY_ISSUED,
"certificate with the given public key already in process");
}
}
if (!subjectCertsInProcess.add(gct.fpSubject)) {
if (!certprofile.isDuplicateSubjectPermitted()) {
if (!publicKeyCertInProcessExisted) {
publicKeyCertsInProcess.remove(gct.fpPublicKey);
}
throw new OperationException(ErrorCode.ALREADY_ISSUED,
"certificate with the given subject " + gct.grantedSubjectText
+ " already in process");
}
}
try {
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
caInfo.getPublicCaInfo().getX500Subject(), caInfo.nextSerial(),
gct.grantedNotBefore, gct.grantedNotAfter, gct.grantedSubject,
gct.grantedPublicKey);
X509CertificateInfo ret;
try {
X509CrlSignerEntryWrapper crlSigner = getCrlSigner();
X509Certificate crlSignerCert = (crlSigner == null) ? null : crlSigner.getCert();
ExtensionValues extensionTuples = certprofile.getExtensions(
gct.requestedSubject, gct.grantedSubject, gct.extensions,
gct.grantedPublicKey, caInfo.getPublicCaInfo(), crlSignerCert,
gct.grantedNotBefore, gct.grantedNotAfter);
if (extensionTuples != null) {
for (ASN1ObjectIdentifier extensionType : extensionTuples.getExtensionTypes()) {
ExtensionValue extValue = extensionTuples.getExtensionValue(extensionType);
certBuilder.addExtension(extensionType, extValue.isCritical(),
extValue.getValue());
}
}
X509CertificateHolder certHolder;
try {
certHolder = gct.signer.build(certBuilder);
} catch (NoIdleSignerException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE, ex);
}
Certificate bcCert = certHolder.toASN1Structure();
byte[] encodedCert = bcCert.getEncoded();
int maxCertSize = gct.certprofile.getMaxCertSize();
if (maxCertSize > 0) {
int certSize = encodedCert.length;
if (certSize > maxCertSize) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
String.format("certificate exceeds the maximal allowed size: %d > %d",
certSize, maxCertSize));
}
}
X509Certificate cert;
try {
cert = X509Util.toX509Cert(bcCert);
} catch (CertificateException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE,
"should not happen, could not parse generated certificate");
}
if (!verifySignature(cert)) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE,
"could not verify the signature of generated certificate");
}
X509CertWithDbId certWithMeta = new X509CertWithDbId(cert, encodedCert);
ret = new X509CertificateInfo(certWithMeta, caIdent, caCert,
gct.grantedPublicKeyData, gct.certprofile.getIdent(), requestor.getIdent());
if (requestor instanceof ByUserRequestorInfo) {
ret.setUser((((ByUserRequestorInfo) requestor).getUserId()));
}
ret.setReqType(reqType);
ret.setTransactionId(transactionId);
ret.setRequestedSubject(gct.requestedSubject);
if (doPublishCertificate(ret) == 1) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE,
"could not save certificate");
}
} catch (BadCertTemplateException ex) {
throw new OperationException(ErrorCode.BAD_CERT_TEMPLATE, ex);
} catch (OperationException ex) {
throw ex;
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not generate certificate");
throw new OperationException(ErrorCode.SYSTEM_FAILURE, th);
}
if (gct.warning != null) {
ret.setWarningMessage(gct.warning);
}
return ret;
} finally {
publicKeyCertsInProcess.remove(gct.fpPublicKey);
subjectCertsInProcess.remove(gct.fpSubject);
}
} // method doGenerateCertificate
private void adaptGrantedSubejct(GrantedCertTemplate gct) throws OperationException {
boolean duplicateSubjectPermitted = caInfo.isDuplicateSubjectPermitted();
if (duplicateSubjectPermitted && !gct.certprofile.isDuplicateSubjectPermitted()) {
duplicateSubjectPermitted = false;
}
if (duplicateSubjectPermitted) {
return;
}
long fpSubject = X509Util.fpCanonicalizedName(gct.grantedSubject);
String grantedSubjectText = X509Util.getRfc4519Name(gct.grantedSubject);
final boolean incSerial = gct.certprofile.incSerialNumberIfSubjectExists();
final boolean certIssued = certstore.isCertForSubjectIssued(caIdent, fpSubject);
if (certIssued && !incSerial) {
throw new OperationException(ErrorCode.ALREADY_ISSUED,
"certificate for the given subject " + grantedSubjectText + " already issued");
}
if (!certIssued) {
return;
}
X500Name subject = gct.grantedSubject;
String latestSn;
try {
Object[] objs = incSerialNumber(gct.certprofile, subject, null);
latestSn = certstore.getLatestSerialNumber((X500Name) objs[0]);
} catch (BadFormatException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE, ex);
}
boolean foundUniqueSubject = false;
// maximal 100 tries
for (int i = 0; i < 100; i++) {
try {
Object[] objs = incSerialNumber(gct.certprofile, subject, latestSn);
subject = (X500Name) objs[0];
if (CompareUtil.equalsObject(latestSn, objs[1])) {
break;
}
latestSn = (String) objs[1];
} catch (BadFormatException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE, ex);
}
foundUniqueSubject = !certstore.isCertForSubjectIssued(
caIdent, X509Util.fpCanonicalizedName(subject));
if (foundUniqueSubject) {
break;
}
}
if (!foundUniqueSubject) {
throw new OperationException(ErrorCode.ALREADY_ISSUED,
"certificate for the given subject " + grantedSubjectText + " and profile "
+ gct.certprofile.getIdent()
+ " already issued, and could not create new unique serial number");
}
gct.setGrantedSubject(subject);
}
private GrantedCertTemplate createGrantedCertTemplate(final CertTemplateData certTemplate,
final RequestorInfo requestor, final boolean keyUpdate)
throws OperationException {
ParamUtil.requireNonNull("certTemplate", certTemplate);
if (caInfo.getRevocationInfo() != null) {
throw new OperationException(ErrorCode.NOT_PERMITTED, "CA is revoked");
}
IdentifiedX509Certprofile certprofile = getX509Certprofile(
certTemplate.getCertprofileName());
if (certprofile == null) {
throw new OperationException(ErrorCode.UNKNOWN_CERT_PROFILE,
"unknown cert profile " + certTemplate.getCertprofileName());
}
ConcurrentContentSigner signer = caInfo.getSigner(certprofile.getSignatureAlgorithms());
if (signer == null) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE,
"CA does not support any signature algorithm restricted by the cert profile");
}
final NameId certprofileIdent = certprofile.getIdent();
if (certprofile.getVersion() != X509CertVersion.v3) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE,
"unknown cert version " + certprofile.getVersion());
}
if (certprofile.isOnlyForRa()) {
if (requestor == null || !requestor.isRa()) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
"profile " + certprofileIdent + " not applied to non-RA");
}
}
X500Name requestedSubject = removeEmptyRdns(certTemplate.getSubject());
if (!certprofile.isSerialNumberInReqPermitted()) {
RDN[] rdns = requestedSubject.getRDNs(ObjectIdentifiers.DN_SN);
if (rdns != null && rdns.length > 0) {
throw new OperationException(ErrorCode.BAD_CERT_TEMPLATE,
"subjectDN SerialNumber in request is not permitted");
}
}
Date now = new Date();
Date reqNotBefore ;
if (certTemplate.getNotBefore() != null && certTemplate.getNotBefore().after(now)) {
reqNotBefore = certTemplate.getNotBefore();
} else {
reqNotBefore = now;
}
Date grantedNotBefore = certprofile.getNotBefore(reqNotBefore);
// notBefore in the past is not permitted
if (grantedNotBefore.before(now)) {
grantedNotBefore = now;
}
if (certprofile.hasMidnightNotBefore()) {
grantedNotBefore = setToMidnight(grantedNotBefore, certprofile.getTimezone());
}
if (grantedNotBefore.before(caInfo.getNotBefore())) {
grantedNotBefore = caInfo.getNotBefore();
if (certprofile.hasMidnightNotBefore()) {
grantedNotBefore = setToMidnight(grantedNotBefore, certprofile.getTimezone());
}
}
long time = caInfo.getNoNewCertificateAfter();
if (grantedNotBefore.getTime() > time) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
"CA is not permitted to issue certifate after " + new Date(time));
}
SubjectPublicKeyInfo grantedPublicKeyInfo;
try {
grantedPublicKeyInfo = X509Util.toRfc3279Style(certTemplate.getPublicKeyInfo());
} catch (InvalidKeySpecException ex) {
throw new OperationException(ErrorCode.BAD_CERT_TEMPLATE,
"invalid SubjectPublicKeyInfo");
}
// public key
try {
grantedPublicKeyInfo = certprofile.checkPublicKey(grantedPublicKeyInfo);
} catch (BadCertTemplateException ex) {
throw new OperationException(ErrorCode.BAD_CERT_TEMPLATE, ex);
}
Date gsmckFirstNotBefore = null;
if (certprofile.getSpecialCertprofileBehavior()
== SpecialX509CertprofileBehavior.gematik_gSMC_K) {
gsmckFirstNotBefore = grantedNotBefore;
RDN[] cnRdns = requestedSubject.getRDNs(ObjectIdentifiers.DN_CN);
if (cnRdns != null && cnRdns.length > 0) {
String requestedCn = X509Util.rdnValueToString(cnRdns[0].getFirst().getValue());
Long gsmckFirstNotBeforeInSecond =
certstore.getNotBeforeOfFirstCertStartsWithCommonName(requestedCn,
certprofileIdent);
if (gsmckFirstNotBeforeInSecond != null) {
gsmckFirstNotBefore = new Date(gsmckFirstNotBeforeInSecond * MS_PER_SECOND);
}
// append the commonName with '-' + yyyyMMdd
SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMdd");
dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
String yyyyMMdd = dateF.format(gsmckFirstNotBefore);
String suffix = "-" + yyyyMMdd;
// append the -yyyyMMdd to the commonName
RDN[] rdns = requestedSubject.getRDNs();
for (int i = 0; i < rdns.length; i++) {
if (ObjectIdentifiers.DN_CN.equals(rdns[i].getFirst().getType())) {
rdns[i] = new RDN(ObjectIdentifiers.DN_CN,
new DERUTF8String(requestedCn + suffix));
}
}
requestedSubject = new X500Name(rdns);
} // end if
} // end if
// subject
SubjectInfo subjectInfo;
try {
subjectInfo = certprofile.getSubject(requestedSubject);
} catch (CertprofileException ex) {
throw new OperationException(ErrorCode.SYSTEM_FAILURE,
"exception in cert profile " + certprofileIdent);
} catch (BadCertTemplateException ex) {
throw new OperationException(ErrorCode.BAD_CERT_TEMPLATE, ex);
}
X500Name grantedSubject = subjectInfo.getGrantedSubject();
// make sure that empty subject is not permitted
ASN1ObjectIdentifier[] attrTypes = grantedSubject.getAttributeTypes();
if (attrTypes == null || attrTypes.length == 0) {
throw new OperationException(ErrorCode.BAD_CERT_TEMPLATE,
"empty subject is not permitted");
}
// make sure that the grantedSubject does not equal the CA's subject
if (X509Util.canonicalizName(grantedSubject).equals(
caInfo.getPublicCaInfo().getC14nSubject())) {
throw new OperationException(ErrorCode.ALREADY_ISSUED,
"certificate with the same subject as CA is not allowed");
}
boolean duplicateKeyPermitted = caInfo.isDuplicateKeyPermitted();
if (duplicateKeyPermitted && !certprofile.isDuplicateKeyPermitted()) {
duplicateKeyPermitted = false;
}
byte[] subjectPublicKeyData = grantedPublicKeyInfo.getPublicKeyData().getBytes();
long fpPublicKey = FpIdCalculator.hash(subjectPublicKeyData);
if (keyUpdate) {
CertStatus certStatus = certstore.getCertStatusForSubject(caIdent, grantedSubject);
if (certStatus == CertStatus.REVOKED) {
throw new OperationException(ErrorCode.CERT_REVOKED);
} else if (certStatus == CertStatus.UNKNOWN) {
throw new OperationException(ErrorCode.UNKNOWN_CERT);
}
} else {
if (!duplicateKeyPermitted) {
if (certstore.isCertForKeyIssued(caIdent, fpPublicKey)) {
throw new OperationException(ErrorCode.ALREADY_ISSUED,
"certificate for the given public key already issued");
}
}
// duplicateSubject check will be processed later
} // end if(keyUpdate)
StringBuilder msgBuilder = new StringBuilder();
if (subjectInfo.getWarning() != null) {
msgBuilder.append(", ").append(subjectInfo.getWarning());
}
CertValidity validity = certprofile.getValidity();
if (validity == null) {
validity = caInfo.getMaxValidity();
} else if (validity.compareTo(caInfo.getMaxValidity()) > 0) {
validity = caInfo.getMaxValidity();
}
Date maxNotAfter = validity.add(grantedNotBefore);
if (maxNotAfter.getTime() > MAX_CERT_TIME_MS) {
maxNotAfter = new Date(MAX_CERT_TIME_MS);
}
// CHECKSTYLE:SKIP
Date origMaxNotAfter = maxNotAfter;
if (certprofile.getSpecialCertprofileBehavior()
== SpecialX509CertprofileBehavior.gematik_gSMC_K) {
String str = certprofile.getParameter(
SpecialX509CertprofileBehavior.PARAMETER_MAXLIFTIME);
long maxLifetimeInDays = Long.parseLong(str);
Date maxLifetime = new Date(gsmckFirstNotBefore.getTime()
+ maxLifetimeInDays * DAY_IN_MS - MS_PER_SECOND);
if (maxNotAfter.after(maxLifetime)) {
maxNotAfter = maxLifetime;
}
}
Date grantedNotAfter = certTemplate.getNotAfter();
if (grantedNotAfter != null) {
if (grantedNotAfter.after(maxNotAfter)) {
grantedNotAfter = maxNotAfter;
msgBuilder.append(", notAfter modified");
}
} else {
grantedNotAfter = maxNotAfter;
}
if (grantedNotAfter.after(caInfo.getNotAfter())) {
ValidityMode mode = caInfo.getValidityMode();
if (mode == ValidityMode.CUTOFF) {
grantedNotAfter = caInfo.getNotAfter();
} else if (mode == ValidityMode.STRICT) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
"notAfter outside of CA's validity is not permitted");
} else if (mode == ValidityMode.LAX) {
// permitted
} else {
throw new RuntimeException(
"should not reach here, unknown CA ValidityMode " + mode);
} // end if (mode)
} // end if (notAfter)
if (certprofile.hasMidnightNotBefore() && !maxNotAfter.equals(origMaxNotAfter)) {
Calendar cal = Calendar.getInstance(certprofile.getTimezone());
cal.setTime(new Date(grantedNotAfter.getTime() - DAY_IN_MS));
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 0);
grantedNotAfter = cal.getTime();
}
String warning = null;
if (msgBuilder.length() > 2) {
warning = msgBuilder.substring(2);
}
GrantedCertTemplate gct = new GrantedCertTemplate(certTemplate.getExtensions(), certprofile,
grantedNotBefore, grantedNotAfter, requestedSubject, grantedPublicKeyInfo,
fpPublicKey, subjectPublicKeyData, signer, warning);
gct.setGrantedSubject(grantedSubject);
return gct;
} // method createGrantedCertTemplate
public IdentifiedX509Certprofile getX509Certprofile(final String certprofileName) {
if (certprofileName == null) {
return null;
}
Set<String> profileNames = caManager.getCertprofilesForCa(caIdent.getName());
return (profileNames == null || !profileNames.contains(certprofileName))
? null : caManager.getIdentifiedCertprofile(certprofileName);
} // method getX509Certprofile
public boolean supportsCertProfile(final String certprofileName) {
ParamUtil.requireNonNull("certprofileLocalName", certprofileName);
Set<String> profileNames = caManager.getCertprofilesForCa(caIdent.getName());
return profileNames.contains(certprofileName.toUpperCase());
}
public CmpRequestorInfo getRequestor(final X500Name requestorSender) {
if (requestorSender == null) {
return null;
}
Set<CaHasRequestorEntry> requestorEntries = caManager.getRequestorsForCa(
caIdent.getName());
if (CollectionUtil.isEmpty(requestorEntries)) {
return null;
}
for (CaHasRequestorEntry m : requestorEntries) {
CmpRequestorEntryWrapper entry = caManager.getCmpRequestorWrapper(
m.getRequestorIdent().getName());
if (entry.getCert().getSubjectAsX500Name().equals(requestorSender)) {
return new CmpRequestorInfo(m, entry.getCert());
}
}
return null;
} // method getRequestor
public CmpRequestorInfo getRequestor(final X509Certificate requestorCert) {
if (requestorCert == null) {
return null;
}
Set<CaHasRequestorEntry> requestorEntries =
caManager.getRequestorsForCa(caIdent.getName());
if (CollectionUtil.isEmpty(requestorEntries)) {
return null;
}
for (CaHasRequestorEntry m : requestorEntries) {
CmpRequestorEntryWrapper entry = caManager.getCmpRequestorWrapper(
m.getRequestorIdent().getName());
if (entry.getCert().getCert().equals(requestorCert)) {
return new CmpRequestorInfo(m, entry.getCert());
}
}
return null;
}
public CaManagerImpl getCaManager() {
return caManager;
}
private Date getCrlNextUpdate(final Date thisUpdate) {
ParamUtil.requireNonNull("thisUpdate", thisUpdate);
CrlControl control = getCrlSigner().getCrlControl();
if (control.getUpdateMode() != UpdateMode.interval) {
return null;
}
int intervalsTillNextCrl = 0;
for (int i = 1;; i++) {
if (i % control.getFullCrlIntervals() == 0) {
intervalsTillNextCrl = i;
break;
} else if (!control.isExtendedNextUpdate() && control.getDeltaCrlIntervals() > 0) {
if (i % control.getDeltaCrlIntervals() == 0) {
intervalsTillNextCrl = i;
break;
}
}
}
Date nextUpdate;
if (control.getIntervalMinutes() != null) {
int minutesTillNextUpdate = intervalsTillNextCrl * control.getIntervalMinutes()
+ control.getOverlapMinutes();
nextUpdate = new Date(MS_PER_SECOND * (thisUpdate.getTime() / MS_PER_SECOND / 60
+ minutesTillNextUpdate) * 60);
} else {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.setTime(thisUpdate);
cal.add(Calendar.DAY_OF_YEAR, intervalsTillNextCrl);
cal.set(Calendar.HOUR_OF_DAY, control.getIntervalDayTime().getHour());
cal.set(Calendar.MINUTE, control.getIntervalDayTime().getMinute());
cal.add(Calendar.MINUTE, control.getOverlapMinutes());
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
nextUpdate = cal.getTime();
}
return nextUpdate;
} // method getCrlNextUpdate
private int removeExpirtedCerts(final Date expiredAtTime, final String msgId)
throws OperationException {
LOG.debug("revoking suspended certificates");
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_remove_expiredCerts, msgId);
boolean successful = false;
try {
int num = doRemoveExpirtedCerts(expiredAtTime, event, msgId);
LOG.info("removed {} expired certificates of CA {}", num, caIdent);
successful = true;
return num;
} finally {
finish(event, successful);
}
}
private int doRemoveExpirtedCerts(final Date expiredAtTime, final AuditEvent event,
final String msgId)
throws OperationException {
ParamUtil.requireNonNull("expiredtime", expiredAtTime);
if (!masterMode) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
"CA could not remove expired certificates in slave mode");
}
event.addEventData(CaAuditConstants.NAME_expiredAt, expiredAtTime);
final int numEntries = 100;
final long expiredAt = expiredAtTime.getTime() / 1000;
int sum = 0;
while (true) {
List<BigInteger> serials = certstore.getExpiredCertSerials(caIdent, expiredAt,
numEntries);
if (CollectionUtil.isEmpty(serials)) {
return sum;
}
for (BigInteger serial : serials) {
// do not delete CA's own certificate
if ((caInfo.isSelfSigned() && caInfo.getSerialNumber().equals(serial))) {
continue;
}
try {
if (removeCertificate(serial, msgId) != null) {
sum++;
}
} catch (OperationException ex) {
LOG.info("removed {} expired certificates of CA {}", sum, caIdent);
LogUtil.error(LOG, ex, "could not remove expired certificate with serial"
+ serial);
throw ex;
}
} // end for
} // end while (true)
} // method removeExpirtedCerts
private int revokeSuspendedCerts(final String msgId) throws OperationException {
LOG.debug("revoking suspended certificates");
AuditEvent event = newPerfAuditEvent(CaAuditConstants.TYPE_revoke_suspendedCert, msgId);
boolean successful = false;
try {
int num = doRevokeSuspendedCerts(event, msgId);
LOG.info("revoked {} suspended certificates of CA {}", num, caIdent);
successful = true;
return num;
} finally {
finish(event, successful);
}
}
private int doRevokeSuspendedCerts(final AuditEvent event, final String msgId)
throws OperationException {
if (!masterMode) {
throw new OperationException(ErrorCode.NOT_PERMITTED,
"CA could not remove expired certificates in slave mode");
}
final int numEntries = 100;
CertValidity val = caInfo.getRevokeSuspendedCertsControl().getUnchangedSince();
long ms;
switch (val.getUnit()) {
case DAY:
ms = val.getValidity() * DAY_IN_MS;
break;
case HOUR:
ms = val.getValidity() * DAY_IN_MS / 24;
break;
case YEAR:
ms = val.getValidity() * 365 * DAY_IN_MS;
break;
default:
throw new RuntimeException("should not reach here, unknown Validity Unit "
+ val.getUnit());
}
final long latestLastUpdatedAt = (System.currentTimeMillis() - ms) / 1000; // seconds
final CrlReason reason = caInfo.getRevokeSuspendedCertsControl().getTargetReason();
int sum = 0;
while (true) {
List<BigInteger> serials = certstore.getSuspendedCertSerials(caIdent,
latestLastUpdatedAt, numEntries);
if (CollectionUtil.isEmpty(serials)) {
return sum;
}
for (BigInteger serial : serials) {
boolean revoked = false;
try {
revoked = revokeSuspendedCert(serial, reason, msgId) != null;
if (revoked) {
sum++;
}
} catch (OperationException ex) {
LOG.info("revoked {} suspended certificates of CA {}", sum, caIdent);
LogUtil.error(LOG, ex, "could not revoke suspended certificate with serial"
+ serial);
throw ex;
} // end try
} // end for
} // end while (true)
} // method removeExpirtedCerts
public HealthCheckResult healthCheck() {
HealthCheckResult result = new HealthCheckResult("X509CA");
boolean healthy = true;
ConcurrentContentSigner signer = caInfo.getSigner(null);
if (signer != null) {
boolean caSignerHealthy = signer.isHealthy();
healthy &= caSignerHealthy;
HealthCheckResult signerHealth = new HealthCheckResult("Signer");
signerHealth.setHealthy(caSignerHealthy);
result.addChildCheck(signerHealth);
}
boolean databaseHealthy = certstore.isHealthy();
healthy &= databaseHealthy;
HealthCheckResult databaseHealth = new HealthCheckResult("Database");
databaseHealth.setHealthy(databaseHealthy);
result.addChildCheck(databaseHealth);
X509CrlSignerEntryWrapper crlSigner = getCrlSigner();
if (crlSigner != null && crlSigner.getSigner() != null) {
boolean crlSignerHealthy = crlSigner.getSigner().isHealthy();
healthy &= crlSignerHealthy;
HealthCheckResult crlSignerHealth = new HealthCheckResult("CRLSigner");
crlSignerHealth.setHealthy(crlSignerHealthy);
result.addChildCheck(crlSignerHealth);
}
for (IdentifiedX509CertPublisher publisher : getPublishers()) {
boolean ph = publisher.isHealthy();
healthy &= ph;
HealthCheckResult publisherHealth = new HealthCheckResult("Publisher");
publisherHealth.setHealthy(publisher.isHealthy());
result.addChildCheck(publisherHealth);
}
result.setHealthy(healthy);
return result;
} // method healthCheck
public void setAuditServiceRegister(final AuditServiceRegister auditServiceRegister) {
this.auditServiceRegister = ParamUtil.requireNonNull("auditServiceRegister",
auditServiceRegister);
}
private AuditService getAuditService() {
return auditServiceRegister.getAuditService();
}
private AuditEvent newPerfAuditEvent(final String eventType, final String msgId) {
return newAuditEvent(CaAuditConstants.NAME_PERF, eventType, msgId);
}
private AuditEvent newAuditEvent(final String name, final String eventType,
final String msgId) {
ParamUtil.requireNonNull("name", name);
ParamUtil.requireNonNull("eventType", eventType);
ParamUtil.requireNonNull("msgId", msgId);
AuditEvent event = new AuditEvent(new Date());
event.setApplicationName(CaAuditConstants.APPNAME);
event.setName(name);
event.addEventData(CaAuditConstants.NAME_CA, caIdent.getName());
event.addEventType(eventType);
event.addEventData(CaAuditConstants.NAME_mid, msgId);
return event;
}
private boolean verifySignature(final X509Certificate cert) {
ParamUtil.requireNonNull("cert", cert);
PublicKey caPublicKey = caCert.getCert().getPublicKey();
try {
final String provider = XiSecurityConstants.PROVIDER_NAME_NSS;
if (tryNssToVerify == null) {
// Not for ECDSA
if (caPublicKey instanceof ECPublicKey) {
tryNssToVerify = Boolean.FALSE;
} else if (Security.getProvider(provider) == null) {
LOG.info("security provider {} is not registered", provider);
tryNssToVerify = Boolean.FALSE;
} else {
byte[] tbs = cert.getTBSCertificate();
byte[] signatureValue = cert.getSignature();
String sigAlgName = cert.getSigAlgName();
try {
Signature verifier = Signature.getInstance(sigAlgName, provider);
verifier.initVerify(caPublicKey);
verifier.update(tbs);
boolean sigValid = verifier.verify(signatureValue);
LOG.info("use {} to verify {} signature", provider, sigAlgName);
tryNssToVerify = Boolean.TRUE;
return sigValid;
} catch (Exception ex) {
LOG.info("could not use {} to verify {} signature", provider, sigAlgName);
tryNssToVerify = Boolean.FALSE;
}
}
}
if (tryNssToVerify) {
byte[] tbs = cert.getTBSCertificate();
byte[] signatureValue = cert.getSignature();
String sigAlgName = cert.getSigAlgName();
Signature verifier = Signature.getInstance(sigAlgName, provider);
verifier.initVerify(caPublicKey);
verifier.update(tbs);
return verifier.verify(signatureValue);
} else {
cert.verify(caPublicKey);
return true;
}
} catch (SignatureException | InvalidKeyException | CertificateException
| NoSuchAlgorithmException | NoSuchProviderException ex) {
LOG.debug("{} while verifying signature: {}", ex.getClass().getName(), ex.getMessage());
return false;
}
} // method verifySignature
private X509CrlSignerEntryWrapper getCrlSigner() {
String crlSignerName = caInfo.getCrlSignerName();
X509CrlSignerEntryWrapper crlSigner = (crlSignerName == null) ? null
: caManager.getCrlSignerWrapper(crlSignerName);
return crlSigner;
}
public NameId getCaIdent() {
return caIdent;
}
public String getHexSha1OfCert() {
return caInfo.getCaEntry().getHexSha1OfCert();
}
void shutdown() {
if (crlGenerationService != null) {
crlGenerationService.cancel(false);
crlGenerationService = null;
}
if (expiredCertsRemover != null) {
expiredCertsRemover.cancel(false);
expiredCertsRemover = null;
}
if (suspendedCertsRevoker != null) {
suspendedCertsRevoker.cancel(false);
suspendedCertsRevoker = null;
}
ScheduledThreadPoolExecutor executor = caManager.getScheduledThreadPoolExecutor();
if (executor != null) {
executor.purge();
}
}
private static Extension createReasonExtension(final int reasonCode) {
CRLReason crlReason = CRLReason.lookup(reasonCode);
try {
return new Extension(Extension.reasonCode, false, crlReason.getEncoded());
} catch (IOException ex) {
throw new IllegalArgumentException("error encoding reason: " + ex.getMessage(), ex);
}
}
private static Extension createInvalidityDateExtension(final Date invalidityDate) {
try {
ASN1GeneralizedTime asnTime = new ASN1GeneralizedTime(invalidityDate);
return new Extension(Extension.invalidityDate, false, asnTime.getEncoded());
} catch (IOException ex) {
throw new IllegalArgumentException("error encoding reason: " + ex.getMessage(), ex);
}
}
private static Extension createCertificateIssuerExtension(final X500Name certificateIssuer) {
try {
GeneralNames generalNames = new GeneralNames(new GeneralName(certificateIssuer));
return new Extension(Extension.certificateIssuer, true, generalNames.getEncoded());
} catch (IOException ex) {
throw new IllegalArgumentException("error encoding reason: " + ex.getMessage(), ex);
}
}
// remove the RDNs with empty content
private static X500Name removeEmptyRdns(final X500Name name) {
RDN[] rdns = name.getRDNs();
List<RDN> tmpRdns = new ArrayList<>(rdns.length);
boolean changed = false;
for (RDN rdn : rdns) {
String textValue = X509Util.rdnValueToString(rdn.getFirst().getValue());
if (StringUtil.isBlank(textValue)) {
changed = true;
} else {
tmpRdns.add(rdn);
}
}
return changed ? new X500Name(tmpRdns.toArray(new RDN[0])) : name;
} // method removeEmptyRdns
private static Object[] incSerialNumber(final IdentifiedX509Certprofile profile,
final X500Name origName, final String latestSn) throws BadFormatException {
RDN[] rdns = origName.getRDNs();
int commonNameIndex = -1;
int serialNumberIndex = -1;
for (int i = 0; i < rdns.length; i++) {
RDN rdn = rdns[i];
ASN1ObjectIdentifier type = rdn.getFirst().getType();
if (ObjectIdentifiers.DN_CN.equals(type)) {
commonNameIndex = i;
} else if (ObjectIdentifiers.DN_SERIALNUMBER.equals(type)) {
serialNumberIndex = i;
}
}
String newSerialNumber = profile.incSerialNumber(latestSn);
RDN serialNumberRdn = new RDN(ObjectIdentifiers.DN_SERIALNUMBER,
new DERPrintableString(newSerialNumber));
X500Name newName;
if (serialNumberIndex != -1) {
rdns[serialNumberIndex] = serialNumberRdn;
newName = new X500Name(rdns);
} else {
List<RDN> newRdns = new ArrayList<>(rdns.length + 1);
if (commonNameIndex == -1) {
newRdns.add(serialNumberRdn);
}
for (int i = 0; i < rdns.length; i++) {
newRdns.add(rdns[i]);
if (i == commonNameIndex) {
newRdns.add(serialNumberRdn);
}
}
newName = new X500Name(newRdns.toArray(new RDN[0]));
}
return new Object[]{newName, newSerialNumber};
} // method incSerialNumber
private static Date setToMidnight(final Date date, final TimeZone timezone) {
Calendar cal = Calendar.getInstance(timezone);
// the next midnight time
cal.setTime(new Date(date.getTime() + DAY_IN_MS - 1));
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
private void finish(final AuditEvent event, final boolean successful) {
event.finish();
event.setLevel(successful ? AuditLevel.INFO : AuditLevel.ERROR);
event.setStatus(successful ? AuditStatus.SUCCESSFUL : AuditStatus.FAILED);
getAuditService().logEvent(event);
}
}