org.xipki.ocsp.server.impl.OcspServer Maven / Gradle / Ivy
The newest version!
/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xipki.ocsp.server.impl;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ocsp.OCSPRequest;
import org.bouncycastle.asn1.ocsp.OCSPResponse;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.common.HealthCheckResult;
import org.xipki.common.InvalidConfException;
import org.xipki.common.ObjectCreationException;
import org.xipki.common.TripleState;
import org.xipki.common.util.CollectionUtil;
import org.xipki.common.util.IoUtil;
import org.xipki.common.util.LogUtil;
import org.xipki.common.util.ParamUtil;
import org.xipki.common.util.StringUtil;
import org.xipki.common.util.XmlUtil;
import org.xipki.datasource.DataSourceFactory;
import org.xipki.datasource.DataSourceWrapper;
import org.xipki.datasource.springframework.dao.DataAccessException;
import org.xipki.http.servlet.ServletURI;
import org.xipki.ocsp.api.CertStatusInfo;
import org.xipki.ocsp.api.OcspMode;
import org.xipki.ocsp.api.OcspStore;
import org.xipki.ocsp.api.OcspStoreException;
import org.xipki.ocsp.api.OcspStoreFactoryRegister;
import org.xipki.ocsp.server.impl.OcspRespWithCacheInfo.ResponseCacheInfo;
import org.xipki.ocsp.server.impl.jaxb.DatasourceType;
import org.xipki.ocsp.server.impl.jaxb.EmbedCertsMode;
import org.xipki.ocsp.server.impl.jaxb.FileOrPlainValueType;
import org.xipki.ocsp.server.impl.jaxb.FileOrValueType;
import org.xipki.ocsp.server.impl.jaxb.OCSPServer;
import org.xipki.ocsp.server.impl.jaxb.ObjectFactory;
import org.xipki.ocsp.server.impl.jaxb.RequestOptionType;
import org.xipki.ocsp.server.impl.jaxb.ResponderType;
import org.xipki.ocsp.server.impl.jaxb.ResponseCacheType;
import org.xipki.ocsp.server.impl.jaxb.ResponseOptionType;
import org.xipki.ocsp.server.impl.jaxb.SignerType;
import org.xipki.ocsp.server.impl.jaxb.StoreType;
import org.xipki.ocsp.server.impl.store.crl.CrlDbCertStatusStore;
import org.xipki.ocsp.server.impl.store.db.DbCertStatusStore;
import org.xipki.ocsp.server.impl.type.CertID;
import org.xipki.ocsp.server.impl.type.EncodingException;
import org.xipki.ocsp.server.impl.type.ExtendedExtension;
import org.xipki.ocsp.server.impl.type.Extension;
import org.xipki.ocsp.server.impl.type.Extensions;
import org.xipki.ocsp.server.impl.type.OID;
import org.xipki.ocsp.server.impl.type.OcspRequest;
import org.xipki.ocsp.server.impl.type.ResponderID;
import org.xipki.ocsp.server.impl.type.TaggedCertSequence;
import org.xipki.ocsp.server.impl.type.WritableOnlyExtension;
import org.xipki.password.PasswordResolverException;
import org.xipki.security.AlgorithmCode;
import org.xipki.security.CertRevocationInfo;
import org.xipki.security.CertpathValidationModel;
import org.xipki.security.ConcurrentContentSigner;
import org.xipki.security.HashAlgoType;
import org.xipki.security.SecurityFactory;
import org.xipki.security.SignerConf;
import org.xipki.security.exception.NoIdleSignerException;
import org.xipki.security.util.X509Util;
import org.xml.sax.SAXException;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public class OcspServer {
private static class SizeComparableString implements Comparable {
private String str;
public SizeComparableString(String str) {
this.str = ParamUtil.requireNonNull("str", str);
}
@Override
public int compareTo(SizeComparableString obj) {
if (str.length() == obj.str.length()) {
return 0;
}
return (str.length() > obj.str.length()) ? 1 : -1;
}
}
private static class OcspRespControl {
boolean canCacheInfo;
boolean includeExtendedRevokeExtension;
long cacheThisUpdate;
long cacheNextUpdate;
public OcspRespControl() {
includeExtendedRevokeExtension = false;
cacheThisUpdate = 0;
cacheNextUpdate = Long.MAX_VALUE;
}
}
public static final long DFLT_CACHE_MAX_AGE = 60; // 1 minute
private static final byte[] DERNullBytes = new byte[]{0x05, 0x00};
private static final byte[] bytes_certstatus_good = new byte[]{(byte) 0x80, 0x00};
private static final byte[] bytes_certstatus_unknown = new byte[]{(byte) 0x82, 0x00};
private static final byte[] bytes_certstatus_rfc6960_unknown =
Hex.decode("a116180f31393730303130313030303030305aa0030a0106");
private static final WritableOnlyExtension extension_pkix_ocsp_extendedRevoke;
private static final Logger LOG = LoggerFactory.getLogger(OcspServer.class);
private static final Map unsuccesfulOCSPRespMap;
private final DataSourceFactory datasourceFactory;
private SecurityFactory securityFactory;
private String confFile;
private boolean master;
private ResponseCacher responseCacher;
private OcspStoreFactoryRegister ocspStoreFactoryRegister;
private Map responders = new HashMap<>();
private Map signers = new HashMap<>();
private Map requestOptions = new HashMap<>();
private Map responseOptions = new HashMap<>();
private Map stores = new HashMap<>();
private List servletPaths = new ArrayList<>();
private Map path2responderMap = new HashMap<>();
private AtomicBoolean initialized = new AtomicBoolean(false);
static {
unsuccesfulOCSPRespMap = new HashMap<>(10);
for (OcspResponseStatus status : OcspResponseStatus.values()) {
if (status == OcspResponseStatus.successful) {
continue;
}
OCSPResponse resp = new OCSPResponse(
new org.bouncycastle.asn1.ocsp.OCSPResponseStatus(status.status()), null);
byte[] encoded;
try {
encoded = resp.getEncoded();
} catch (IOException ex) {
throw new ExceptionInInitializerError(
"could not encode OCSPResp for status " + status + ": " + ex.getMessage());
}
unsuccesfulOCSPRespMap.put(status, new OcspRespWithCacheInfo(encoded, null));
}
ExtendedExtension ext = new ExtendedExtension(OID.ID_PKIX_OCSP_EXTENDEDREVOKE,
true, DERNullBytes);
byte[] encoded = new byte[ext.encodedLength()];
ext.write(encoded, 0);
extension_pkix_ocsp_extendedRevoke = new WritableOnlyExtension(encoded);
}
public OcspServer() {
this.datasourceFactory = new DataSourceFactory();
}
public void setSecurityFactory(final SecurityFactory securityFactory) {
this.securityFactory = securityFactory;
}
public void setConfFile(final String confFile) {
this.confFile = confFile;
}
Responder getResponder(final ServletURI servletUri) throws UnsupportedEncodingException {
String path = servletUri.path();
for (String servletPath : servletPaths) {
if (path.startsWith(servletPath)) {
return path2responderMap.get(servletPath);
}
}
return null;
}
Object[] getServletPathAndResponder(final ServletURI servletUri)
throws UnsupportedEncodingException {
String path = servletUri.path();
for (String servletPath : servletPaths) {
if (path.startsWith(servletPath)) {
return new Object[]{servletPath, path2responderMap.get(servletPath)};
}
}
return null;
}
public Responder getResponder(final String name) {
ParamUtil.requireNonBlank("name", name);
return responders.get(name);
}
public boolean isInitialized() {
return initialized.get();
}
public void init() throws InvalidConfException, PasswordResolverException, DataAccessException {
LOG.info("starting OCSPResponder server ...");
if (initialized.get()) {
LOG.info("already started, skipping ...");
return;
}
try {
init0();
initialized.set(true);
} finally {
if (initialized.get()) {
LOG.info("started OCSPResponder server");
} else {
LOG.error("could not start OCSPResponder server");
}
}
}
private void init0()
throws InvalidConfException, DataAccessException, PasswordResolverException {
if (confFile == null) {
throw new IllegalStateException("confFile is not set");
}
if (datasourceFactory == null) {
throw new IllegalStateException("datasourceFactory is not set");
}
if (securityFactory == null) {
throw new IllegalStateException("securityFactory is not set");
}
OCSPServer conf = parseConf(confFile);
//-- check the duplication names
Set set = new HashSet<>();
// Duplication name check: responder
for (ResponderType m : conf.getResponders().getResponder()) {
String name = m.getName();
if (set.contains(name)) {
throw new InvalidConfException(
"duplicated definition of responder named '" + name + "'");
}
if (StringUtil.isBlank(name)) {
throw new InvalidConfException("responder name must not be empty");
}
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
if (!((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')
|| (ch >= 'a' && ch <= 'z'))) {
throw new InvalidConfException("invalid OCSP responder name '" + name + "'");
}
} // end for
set.add(name);
} // end for
// Duplication name check: signer
set.clear();
for (SignerType m : conf.getSigners().getSigner()) {
String name = m.getName();
if (set.contains(name)) {
throw new InvalidConfException(
"duplicated definition of signer option named '" + name + "'");
}
set.add(name);
}
// Duplication name check: requests
set.clear();
for (RequestOptionType m : conf.getRequestOptions().getRequestOption()) {
String name = m.getName();
if (set.contains(name)) {
throw new InvalidConfException(
"duplicated definition of request option named '" + name + "'");
}
set.add(name);
}
// Duplication name check: response
set.clear();
for (ResponseOptionType m : conf.getResponseOptions().getResponseOption()) {
String name = m.getName();
if (set.contains(name)) {
throw new InvalidConfException(
"duplicated definition of response option named '" + name + "'");
}
set.add(name);
}
// Duplication name check: store
set.clear();
for (StoreType m : conf.getStores().getStore()) {
String name = m.getName();
if (set.contains(name)) {
throw new InvalidConfException(
"duplicated definition of store named '" + name + "'");
}
}
// Duplication name check: datasource
set.clear();
if (conf.getDatasources() != null) {
for (DatasourceType m : conf.getDatasources().getDatasource()) {
String name = m.getName();
if (set.contains(name)) {
throw new InvalidConfException(
"duplicated definition of datasource named '" + name + "'");
}
set.add(name);
}
}
this.master = conf.isMaster();
// Response Cache
ResponseCacheType cacheType = conf.getResponseCache();
if (cacheType != null) {
DatasourceType cacheSourceConf = cacheType.getDatasource();
DataSourceWrapper datasource;
InputStream dsStream = null;
try {
dsStream = getInputStream(cacheSourceConf.getConf());
datasource = datasourceFactory.createDataSource(cacheSourceConf.getName(),
dsStream, securityFactory.getPasswordResolver());
} catch (IOException ex) {
throw new InvalidConfException(ex.getMessage(), ex);
} finally {
close(dsStream);
}
responseCacher = new ResponseCacher(datasource, master, cacheType.getValidity());
responseCacher.init();
}
//-- initializes the responders
// signers
for (SignerType m : conf.getSigners().getSigner()) {
ResponderSigner signer = initSigner(m);
signers.put(m.getName(), signer);
}
// requests
for (RequestOptionType m : conf.getRequestOptions().getRequestOption()) {
RequestOption option = new RequestOption(m);
requestOptions.put(m.getName(), option);
}
// responses
for (ResponseOptionType m : conf.getResponseOptions().getResponseOption()) {
ResponseOption option = new ResponseOption(m);
responseOptions.put(m.getName(), option);
}
// datasources
Map datasources = new HashMap<>();
if (conf.getDatasources() != null) {
for (DatasourceType m : conf.getDatasources().getDatasource()) {
String name = m.getName();
DataSourceWrapper datasource;
InputStream dsStream = null;
try {
dsStream = getInputStream(m.getConf());
datasource = datasourceFactory.createDataSource(name,
dsStream, securityFactory.getPasswordResolver());
} catch (IOException ex) {
throw new InvalidConfException(ex.getMessage(), ex);
} finally {
close(dsStream);
}
datasources.put(name, datasource);
} // end for
} // end if
// responders
Map responderOptions = new HashMap<>();
for (ResponderType m : conf.getResponders().getResponder()) {
ResponderOption option = new ResponderOption(m);
String optName = option.signerName();
if (!signers.containsKey(optName)) {
throw new InvalidConfException("no signer named '" + optName + "' is defined");
}
String reqOptName = option.requestOptionName();
if (!requestOptions.containsKey(reqOptName)) {
throw new InvalidConfException(
"no requestOption named '" + reqOptName + "' is defined");
}
String respOptName = option.responseOptionName();
if (!responseOptions.containsKey(respOptName)) {
throw new InvalidConfException(
"no responseOption named '" + respOptName + "' is defined");
}
// required HashAlgorithms for certificate
ResponseOption respOpt = responseOptions.get(respOptName);
Set certHashAlgos = new HashSet<>(5);
if (respOpt.isIncludeCerthash()) {
if (respOpt.certHashAlgo() != null) {
certHashAlgos.add(respOpt.certHashAlgo());
} else {
RequestOption reqOpt = requestOptions.get(reqOptName);
Set algs = reqOpt.hashAlgos();
if (!CollectionUtil.isEmpty(algs)) {
certHashAlgos.addAll(algs);
} else {
HashAlgoType[] hashAlgos = new HashAlgoType[]{HashAlgoType.SHA1,
HashAlgoType.SHA224, HashAlgoType.SHA256, HashAlgoType.SHA384,
HashAlgoType.SHA512};
for (HashAlgoType hashAlgo : hashAlgos) {
certHashAlgos.add(hashAlgo);
}
}
}
}
List storeDefs = conf.getStores().getStore();
Set storeNames = new HashSet<>(storeDefs.size());
for (StoreType storeDef : storeDefs) {
storeNames.add(storeDef.getName());
}
responderOptions.put(m.getName(), option);
} // end for
// stores
for (StoreType m : conf.getStores().getStore()) {
OcspStore store = newStore(m, datasources);
stores.put(m.getName(), store);
}
// responders
for (String name : responderOptions.keySet()) {
ResponderOption option = responderOptions.get(name);
List statusStores = new ArrayList<>(option.storeNames().size());
for (String storeName : option.storeNames()) {
statusStores.add(stores.get(storeName));
}
ResponseOption responseOption = responseOptions.get(option.responseOptionName());
ResponderSigner signer = signers.get(option.signerName());
if (signer.isMacSigner()) {
if (responseOption.isResponderIdByName()) {
throw new InvalidConfException(
"could not use ResponderIdByName for signer "
+ option.signerName());
}
if (EmbedCertsMode.NONE != responseOption.embedCertsMode()) {
throw new InvalidConfException(
"could not embed certifcate in response for signer "
+ option.signerName());
}
}
Responder responder = new Responder(option,
requestOptions.get(option.requestOptionName()),
responseOption, signer, statusStores);
responders.put(name, responder);
} // end for
// servlet paths
List tmpList = new LinkedList<>();
for (String name : responderOptions.keySet()) {
Responder responder = responders.get(name);
ResponderOption option = responderOptions.get(name);
List strs = option.servletPaths();
for (String path : strs) {
tmpList.add(new SizeComparableString(path));
path2responderMap.put(path, responder);
}
}
// Sort the servlet paths according to the length of path. The first one is the
// longest, and the last one is the shortest.
Collections.sort(tmpList);
List list2 = new ArrayList<>(tmpList.size());
for (SizeComparableString m : tmpList) {
list2.add(m.str);
}
this.servletPaths = list2;
} // method init0
public void shutdown() {
LOG.info("stopped OCSP Responder");
if (responseCacher != null) {
responseCacher.shutdown();
}
for (OcspStore store : stores.values()) {
try {
store.shutdown();
} catch (Exception ex) {
LogUtil.warn(LOG, ex, "shutdown store " + store.name());
}
}
}
public OcspRespWithCacheInfo answer(final Responder responder, final byte[] request,
final boolean viaGet) {
RequestOption reqOpt = responder.requestOption();
int version;
try {
version = OcspRequest.readRequestVersion(request);
} catch (EncodingException ex) {
String message = "could not extract version from request";
LOG.warn(message);
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
if (!reqOpt.isVersionAllowed(version)) {
String message = "invalid request version " + version;
LOG.warn(message);
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
ResponderSigner signer = responder.signer();
ResponseOption repOpt = responder.responseOption();
try {
Object reqOrRrrorResp = checkSignature(request, reqOpt);
if (reqOrRrrorResp instanceof OcspRespWithCacheInfo) {
return (OcspRespWithCacheInfo) reqOrRrrorResp;
}
OcspRequest req = (OcspRequest) reqOrRrrorResp;
List requestList = req.requestList();
int requestsSize = requestList.size();
if (requestsSize > reqOpt.maxRequestListCount()) {
String message = requestsSize + " entries in RequestList, but maximal "
+ reqOpt.maxRequestListCount() + " is allowed";
LOG.warn(message);
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
OcspRespControl repControl = new OcspRespControl();
repControl.canCacheInfo = true;
ResponderID responderId = signer.getResponderId(repOpt.isResponderIdByName());
List reqExtensions = req.extensions();
List respExtensions = new LinkedList<>();
ExtendedExtension nonceExtn = removeExtension(reqExtensions, OID.ID_PKIX_OCSP_NONCE);
if (nonceExtn != null) {
if (reqOpt.nonceOccurrence() == TripleState.FORBIDDEN) {
LOG.warn("nonce forbidden, but is present in the request");
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
int len = nonceExtn.extnValueLength();
int min = reqOpt.nonceMinLen();
int max = reqOpt.nonceMaxLen();
if (len < min || len > max) {
LOG.warn("length of nonce {} not within [{},{}]", len, min, max);
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
repControl.canCacheInfo = false;
respExtensions.add(nonceExtn);
} else {
if (reqOpt.nonceOccurrence() == TripleState.REQUIRED) {
LOG.warn("nonce required, but is not present in the request");
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
}
ConcurrentContentSigner concurrentSigner = null;
if (responder.responderOption().mode() != OcspMode.RFC2560) {
ExtendedExtension extn = removeExtension(reqExtensions,
OID.ID_PKIX_OCSP_PREFSIGALGS);
if (extn != null) {
ASN1InputStream asn1Stream = new ASN1InputStream(extn.getExtnValueStream());
List prefSigAlgs;
try {
ASN1Sequence seq = ASN1Sequence.getInstance(asn1Stream.readObject());
final int size = seq.size();
prefSigAlgs = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
prefSigAlgs.add(AlgorithmIdentifier.getInstance(seq.getObjectAt(i)));
}
} finally {
asn1Stream.close();
}
concurrentSigner = signer.getSignerForPreferredSigAlgs(prefSigAlgs);
}
}
if (!reqExtensions.isEmpty()) {
boolean flag = false;
for (ExtendedExtension m : reqExtensions) {
if (m.isCritical()) {
flag = true;
break;
}
}
if (flag) {
if (LOG.isWarnEnabled()) {
List oids = new LinkedList<>();
for (ExtendedExtension m : reqExtensions) {
if (m.isCritical()) {
oids.add(m.extnType());
}
}
LOG.warn("could not process critial request extensions: {}", oids);
}
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
}
if (concurrentSigner == null) {
concurrentSigner = signer.firstSigner();
}
AlgorithmCode cacheDbSigAlgCode = null;
AlgorithmCode cacheDbCertHashAlgCode = null;
BigInteger cacheDbSerialNumber = null;
Integer cacheDbIssuerId = null;
boolean canCacheDb = (requestsSize == 1) && (responseCacher != null)
&& (nonceExtn == null) && responseCacher.isOnService();
if (canCacheDb) {
// try to find the cached response
CertID certId = requestList.get(0);
HashAlgoType reqHashAlgo = certId.issuer().hashAlgorithm();
if(!reqOpt.allows(reqHashAlgo)) {
if (reqHashAlgo != null) {
LOG.warn("CertID.hashAlgorithm {} not allowed", reqHashAlgo);
} else {
LOG.warn("CertID.hashAlgorithm {} not allowed",
certId.issuer().hashAlgorithmOID());
}
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
HashAlgoType certHashAlgo = repOpt.certHashAlgo();
if (certHashAlgo == null) {
certHashAlgo = reqHashAlgo;
}
cacheDbCertHashAlgCode = certHashAlgo.algorithmCode();
cacheDbSigAlgCode = concurrentSigner.algorithmCode();
cacheDbIssuerId = responseCacher.getIssuerId(certId.issuer());
cacheDbSerialNumber = certId.serialNumber();
if (cacheDbIssuerId != null) {
OcspRespWithCacheInfo cachedResp = responseCacher.getOcspResponse(
cacheDbIssuerId.intValue(), cacheDbSerialNumber, cacheDbSigAlgCode,
cacheDbCertHashAlgCode);
if (cachedResp != null) {
return cachedResp;
}
} else if (master) {
// store the issuer certificate in cache database.
X509Certificate issuerCert = null;
for (OcspStore store : responder.stores()) {
issuerCert = store.getIssuerCert(certId.issuer());
if (issuerCert != null) {
break;
}
}
if (issuerCert != null) {
cacheDbIssuerId = responseCacher.storeIssuer(issuerCert);
}
}
if (cacheDbIssuerId == null) {
canCacheDb = false;
}
}
OCSPRespBuilder builder = new OCSPRespBuilder(responderId);
for (int i = 0; i < requestsSize; i++) {
OcspRespWithCacheInfo failureOcspResp = processCertReq(requestList.get(i),
builder, responder, reqOpt, repOpt, repControl);
if (failureOcspResp != null) {
return failureOcspResp;
}
}
if (repControl.includeExtendedRevokeExtension) {
respExtensions.add(extension_pkix_ocsp_extendedRevoke);
}
if (!respExtensions.isEmpty()) {
Extensions extns = new Extensions(respExtensions);
builder.setResponseExtensions(extns);
}
TaggedCertSequence certsInResp;
EmbedCertsMode certsMode = repOpt.embedCertsMode();
if (certsMode == EmbedCertsMode.SIGNER) {
certsInResp = signer.sequenceOfCertificate();
} else if (certsMode == EmbedCertsMode.NONE) {
certsInResp = null;
} else {
// certsMode == EmbedCertsMode.SIGNER_AND_CA
certsInResp = signer.sequenceOfCertificateChain();
}
byte[] encodeOCSPResponse;
try {
encodeOCSPResponse = builder.buildOCSPResponse(concurrentSigner,
certsInResp, new Date());
} catch (NoIdleSignerException ex) {
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.tryLater);
} catch (OCSPException ex) {
LogUtil.error(LOG, ex, "answer() basicOcspBuilder.build");
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.internalError);
}
// cache response in database
if (canCacheDb && repControl.canCacheInfo) {
// Don't cache the response with status UNKNOWN, since this may result in DDoS
// of storage
responseCacher.storeOcspResponse(cacheDbIssuerId.intValue(),
cacheDbSerialNumber, repControl.cacheThisUpdate,
repControl.cacheNextUpdate, cacheDbSigAlgCode,
cacheDbCertHashAlgCode, encodeOCSPResponse);
}
if (viaGet && repControl.canCacheInfo) {
ResponseCacheInfo cacheInfo = new ResponseCacheInfo(repControl.cacheThisUpdate);
if (repControl.cacheNextUpdate != Long.MAX_VALUE) {
cacheInfo.setNextUpdate(repControl.cacheNextUpdate);
}
return new OcspRespWithCacheInfo(encodeOCSPResponse, cacheInfo);
} else {
return new OcspRespWithCacheInfo(encodeOCSPResponse, null);
}
} catch (Throwable th) {
LogUtil.error(LOG, th);
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.internalError);
}
} // method ask
private OcspRespWithCacheInfo processCertReq(CertID certId,
OCSPRespBuilder builder, Responder responder, RequestOption reqOpt,
ResponseOption repOpt, OcspRespControl repControl) throws IOException {
HashAlgoType reqHashAlgo = certId.issuer().hashAlgorithm();
if (!reqOpt.allows(reqHashAlgo)) {
LOG.warn("CertID.hashAlgorithm {} not allowed", reqHashAlgo);
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
CertStatusInfo certStatusInfo = null;
boolean exceptionOccurs = false;
BigInteger serial = certId.serialNumber();
Date now = new Date();
for (OcspStore store : responder.stores()) {
try {
certStatusInfo = store.getCertStatus(now, certId.issuer(), serial,
repOpt.isIncludeCerthash(), repOpt.isIncludeInvalidityDate(),
responder.responderOption().inheritCaRevocation(),
repOpt.certHashAlgo());
if (certStatusInfo != null) {
break;
}
} catch (OcspStoreException ex) {
exceptionOccurs = true;
LogUtil.error(LOG, ex, "getCertStatus() of CertStatusStore " + store.name());
}
}
if (certStatusInfo == null) {
if (exceptionOccurs) {
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.tryLater);
} else {
certStatusInfo = CertStatusInfo.getIssuerUnknownCertStatusInfo(new Date(), null);
}
} // end if
// certStatusInfo must not be null in any case, since at least one store is configured
Date thisUpdate = certStatusInfo.thisUpdate();
if (thisUpdate == null) {
thisUpdate = new Date();
}
Date nextUpdate = certStatusInfo.nextUpdate();
List extensions = new LinkedList<>();
boolean unknownAsRevoked = false;
byte[] certStatus;
switch (certStatusInfo.certStatus()) {
case GOOD:
certStatus = bytes_certstatus_good;
break;
case ISSUER_UNKNOWN:
repControl.canCacheInfo = false;
certStatus = bytes_certstatus_unknown;
break;
case UNKNOWN:
case IGNORE:
repControl.canCacheInfo = false;
if (responder.responderOption().mode() == OcspMode.RFC2560) {
certStatus = bytes_certstatus_unknown;
} else { // (ocspMode == OCSPMode.RFC6960)
unknownAsRevoked = true;
repControl.includeExtendedRevokeExtension = true;
certStatus = bytes_certstatus_rfc6960_unknown;
}
break;
case REVOKED:
CertRevocationInfo revInfo = certStatusInfo.revocationInfo();
certStatus = Template.getEncodeRevokedInfo(
repOpt.isIncludeRevReason() ? revInfo.reason() : null,
revInfo.revocationTime());
Date invalidityDate = revInfo.invalidityTime();
if (repOpt.isIncludeInvalidityDate() && invalidityDate != null
&& !invalidityDate.equals(revInfo.revocationTime())) {
extensions.add(Template.getInvalidityDateExtension(invalidityDate));
}
break;
default:
throw new RuntimeException(
"unknown CertificateStatus:" + certStatusInfo.certStatus());
} // end switch
byte[] certHash = certStatusInfo.certHash();
if (certHash != null) {
extensions.add(Template.getCertHashExtension(certStatusInfo.certHashAlgo(), certHash));
}
if (certStatusInfo.archiveCutOff() != null) {
extensions.add(Template.getArchiveOffExtension(certStatusInfo.archiveCutOff()));
}
if (LOG.isDebugEnabled()) {
String certStatusText = null;
if (Arrays.equals(certStatus, bytes_certstatus_good)) {
certStatusText = "good";
} else if (Arrays.equals(certStatus, bytes_certstatus_unknown)) {
certStatusText = "unknown";
} else if (Arrays.equals(certStatus, bytes_certstatus_rfc6960_unknown)) {
certStatusText = "RFC6969_unknown";
} else {
certStatusText = unknownAsRevoked ? "unknown_as_revoked" : "revoked";
}
StringBuilder sb = new StringBuilder(250);
sb.append("issuer: ").append(certId.issuer()).append(", ");
sb.append("serialNumber: ")
.append(LogUtil.formatCsn(certId.serialNumber()))
.append(", ");
sb.append("certStatus: ").append(certStatusText).append(", ");
sb.append("thisUpdate: ").append(thisUpdate).append(", ");
sb.append("nextUpdate: ").append(nextUpdate);
if (certHash != null) {
sb.append(", certHash: ").append(Hex.toHexString(certHash).toUpperCase());
}
LOG.debug(sb.toString());
}
if (CollectionUtil.isEmpty(extensions)) {
builder.addResponse(certId, certStatus, thisUpdate, nextUpdate, null);
} else{
builder.addResponse(certId, certStatus, thisUpdate, nextUpdate,
new Extensions(extensions));
}
repControl.cacheThisUpdate = Math.max(repControl.cacheThisUpdate, thisUpdate.getTime());
if (nextUpdate != null) {
repControl.cacheNextUpdate = Math.min(repControl.cacheNextUpdate, nextUpdate.getTime());
}
return null;
}
public HealthCheckResult healthCheck(final Responder responder) {
HealthCheckResult result = new HealthCheckResult("OCSPResponder");
boolean healthy = true;
for (OcspStore store : responder.stores()) {
boolean storeHealthy = store.isHealthy();
healthy &= storeHealthy;
HealthCheckResult storeHealth = new HealthCheckResult(
"CertStatusStore." + store.name());
storeHealth.setHealthy(storeHealthy);
result.addChildCheck(storeHealth);
}
boolean signerHealthy = responder.signer().isHealthy();
healthy &= signerHealthy;
HealthCheckResult signerHealth = new HealthCheckResult("Signer");
signerHealth.setHealthy(signerHealthy);
result.addChildCheck(signerHealth);
result.setHealthy(healthy);
return result;
} // method healthCheck
public void setOcspStoreFactoryRegister(
final OcspStoreFactoryRegister ocspStoreFactoryRegister) {
this.ocspStoreFactoryRegister = ocspStoreFactoryRegister;
}
private ResponderSigner initSigner(final SignerType signerType) throws InvalidConfException {
X509Certificate[] explicitCertificateChain = null;
X509Certificate explicitResponderCert = null;
if (signerType.getCert() != null) {
explicitResponderCert = parseCert(signerType.getCert());
}
if (explicitResponderCert != null) {
Set caCerts = null;
if (signerType.getCaCerts() != null) {
caCerts = new HashSet<>();
for (FileOrValueType certConf : signerType.getCaCerts().getCaCert()) {
caCerts.add(parseCert(certConf));
}
}
explicitCertificateChain = X509Util.buildCertPath(explicitResponderCert, caCerts);
}
String responderSignerType = signerType.getType();
String responderKeyConf = signerType.getKey();
List sigAlgos = signerType.getAlgorithms().getAlgorithm();
List singleSigners = new ArrayList<>(sigAlgos.size());
for (String sigAlgo : sigAlgos) {
try {
ConcurrentContentSigner requestorSigner = securityFactory.createSigner(
responderSignerType,
new SignerConf("algo=" + sigAlgo + "," + responderKeyConf),
explicitCertificateChain);
singleSigners.add(requestorSigner);
} catch (ObjectCreationException ex) {
throw new InvalidConfException(ex.getMessage(), ex);
}
}
try {
return new ResponderSigner(singleSigners);
} catch (CertificateException | IOException ex) {
throw new InvalidConfException(ex.getMessage(), ex);
}
} // method initSigner
private OcspStore newStore(final StoreType conf,
final Map datasources)
throws InvalidConfException {
OcspStore store;
String type = conf.getSource().getType();
if ("CRL".equalsIgnoreCase(type)) {
store = new CrlDbCertStatusStore();
} else if ("XIPKI-DB".equals(type)) {
store = new DbCertStatusStore();
} else {
try {
store = ocspStoreFactoryRegister.newOcspStore(conf.getSource().getType());
} catch (ObjectCreationException ex) {
throw new InvalidConfException("ObjectCreationException of store " + conf.getName()
+ ":" + ex.getMessage(), ex);
}
}
store.setName(conf.getName());
Integer interval = conf.getRetentionInterval();
int retentionInterva = (interval == null) ? -1 : interval.intValue();
store.setRetentionInterval(retentionInterva);
store.setUnknownSerialAsGood(getBoolean(conf.isUnknownSerialAsGood(), false));
store.setIncludeArchiveCutoff(getBoolean(conf.isIncludeArchiveCutoff(), true));
store.setIncludeCrlId(getBoolean(conf.isIncludeCrlID(), true));
store.setIgnoreExpiredCert(getBoolean(conf.isIgnoreExpiredCert(), true));
store.setIgnoreNotYetValidCert(getBoolean(conf.isIgnoreNotYetValidCert(), true));
String datasourceName = conf.getSource().getDatasource();
DataSourceWrapper datasource = null;
if (datasourceName != null) {
datasource = datasources.get(datasourceName);
if (datasource == null) {
throw new InvalidConfException("datasource named '" + datasourceName
+ "' not defined");
}
}
try {
store.init(conf.getSource().getConf(), datasource);
} catch (OcspStoreException ex) {
throw new InvalidConfException("CertStatusStoreException of store " + conf.getName()
+ ":" + ex.getMessage(), ex);
}
return store;
} // method initStore
private Object checkSignature(final byte[] request, final RequestOption requestOption)
throws OCSPException, CertificateParsingException, InvalidAlgorithmParameterException {
OCSPRequest req;
try {
if (!requestOption.isValidateSignature()) {
return OcspRequest.getInstance(request);
}
if (!OcspRequest.containsSignature(request)) {
if (requestOption.isSignatureRequired()) {
LOG.warn("signature in request required");
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.sigRequired);
} else {
return OcspRequest.getInstance(request);
}
}
try {
req = OCSPRequest.getInstance(request);
} catch (IllegalArgumentException ex) {
throw new EncodingException("could not parse OCSP request", ex);
}
} catch (EncodingException ex) {
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
OCSPReq ocspReq = new OCSPReq(req);
X509CertificateHolder[] certs = ocspReq.getCerts();
if (certs == null || certs.length < 1) {
LOG.warn("no certificate found in request to verify the signature");
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.unauthorized);
}
ContentVerifierProvider cvp;
try {
cvp = securityFactory.getContentVerifierProvider(certs[0]);
} catch (InvalidKeyException ex) {
String message = ex.getMessage();
LOG.warn("securityFactory.getContentVerifierProvider, InvalidKeyException: {}",
message);
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.unauthorized);
}
boolean sigValid = ocspReq.isSignatureValid(cvp);
if (!sigValid) {
LOG.warn("request signature is invalid");
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.unauthorized);
}
// validate the certPath
Date referenceTime = new Date();
if (canBuildCertpath(certs, requestOption, referenceTime)) {
try {
return OcspRequest.getInstance(req);
} catch (EncodingException ex) {
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.malformedRequest);
}
}
LOG.warn("could not build certpath for the request's signer certificate");
return unsuccesfulOCSPRespMap.get(OcspResponseStatus.unauthorized);
} // method checkSignature
private static boolean canBuildCertpath(final X509CertificateHolder[] certsInReq,
final RequestOption requestOption, final Date referenceTime) {
X509Certificate target;
try {
target = X509Util.toX509Cert(certsInReq[0].toASN1Structure());
} catch (CertificateException ex) {
return false;
}
Set certstore = new HashSet<>();
Set trustAnchors = requestOption.trustAnchors();
for (CertWithEncoded m : trustAnchors) {
certstore.add(m.certificate());
}
final int n = certsInReq.length;
if (n > 1) {
for (int i = 1; i < n; i++) {
Certificate cert;
try {
cert = X509Util.toX509Cert(certsInReq[i].toASN1Structure());
} catch (CertificateException ex) {
continue;
}
certstore.add(cert);
}
}
Set configuredCerts = requestOption.certs();
if (CollectionUtil.isNonEmpty(configuredCerts)) {
certstore.addAll(requestOption.certs());
}
X509Certificate[] certpath = X509Util.buildCertPath(target, certstore);
CertpathValidationModel model = requestOption.certpathValidationModel();
Date now = new Date();
if (model == null || model == CertpathValidationModel.PKIX) {
for (X509Certificate m : certpath) {
if (m.getNotBefore().after(now) || m.getNotAfter().before(now)) {
return false;
}
}
} else if (model == CertpathValidationModel.CHAIN) {
// do nothing
} else {
throw new RuntimeException("invalid CertpathValidationModel " + model.name());
}
for (int i = certpath.length - 1; i >= 0; i--) {
X509Certificate targetCert = certpath[i];
for (CertWithEncoded m : trustAnchors) {
if (m.equalsCert(targetCert)) {
return true;
}
}
}
return false;
} // method canBuildCertpath
private static boolean getBoolean(final Boolean bo, final boolean defaultValue) {
return (bo == null) ? defaultValue : bo.booleanValue();
}
private static InputStream getInputStream(final FileOrValueType conf) throws IOException {
return (conf.getFile() != null)
? new FileInputStream(IoUtil.expandFilepath(conf.getFile()))
: new ByteArrayInputStream(conf.getValue());
}
private static InputStream getInputStream(final FileOrPlainValueType conf) throws IOException {
return (conf.getFile() != null)
? new FileInputStream(IoUtil.expandFilepath(conf.getFile()))
: new ByteArrayInputStream(conf.getValue().getBytes());
}
private static void close(final InputStream stream) {
if (stream == null) {
return;
}
try {
stream.close();
} catch (IOException ex) {
LOG.warn("could not close stream: {}", ex.getMessage());
}
}
private static X509Certificate parseCert(final FileOrValueType certConf)
throws InvalidConfException {
InputStream is = null;
try {
is = getInputStream(certConf);
return X509Util.parseCert(is);
} catch (IOException | CertificateException ex) {
String msg = "could not parse certificate";
if (certConf.getFile() != null) {
msg += " from file " + certConf.getFile();
}
throw new InvalidConfException(msg);
} finally {
close(is);
}
}
private static OCSPServer parseConf(final String confFilename) throws InvalidConfException {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
SchemaFactory schemaFact = SchemaFactory.newInstance(
javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFact.newSchema(
OcspServer.class.getResource("/xsd/ocsp-conf.xsd"));
unmarshaller.setSchema(schema);
return (OCSPServer) unmarshaller.unmarshal(
new File(IoUtil.expandFilepath(confFilename)));
} catch (SAXException ex) {
throw new InvalidConfException("parse profile failed, message: " + ex.getMessage(), ex);
} catch (JAXBException ex) {
throw new InvalidConfException(
"parse profile failed, message: " + XmlUtil.getMessage(ex), ex);
}
}
private static ExtendedExtension removeExtension(List extensions,
OID extnType) {
ExtendedExtension extn = null;
for (ExtendedExtension m : extensions) {
if (extnType == m.extnType()) {
extn = m;
break;
}
}
if (extn != null) {
extensions.remove(extn);
}
return extn;
}
}