org.xipki.pki.ocsp.server.impl.OcspServer.java Source code

Java tutorial

Introduction

Here is the source code for org.xipki.pki.ocsp.server.impl.OcspServer.java

Source

/*
 *
 * Copyright (c) 2013 - 2016 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.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.net.URLDecoder;
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.servlet.http.HttpServletRequest;
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.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers;
import org.bouncycastle.asn1.isismtt.ocsp.CertHash;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.ocsp.OCSPResponse;
import org.bouncycastle.asn1.ocsp.RevokedInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
import org.bouncycastle.cert.ocsp.Req;
import org.bouncycastle.cert.ocsp.RespID;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.UnknownStatus;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.util.encoders.Hex;
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.AuditServiceRegister;
import org.xipki.commons.audit.AuditStatus;
import org.xipki.commons.audit.PciAuditEvent;
import org.xipki.commons.common.HealthCheckResult;
import org.xipki.commons.common.InvalidConfException;
import org.xipki.commons.common.ObjectCreationException;
import org.xipki.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.IoUtil;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.common.util.RandomUtil;
import org.xipki.commons.common.util.StringUtil;
import org.xipki.commons.common.util.XmlUtil;
import org.xipki.commons.datasource.DataSourceFactory;
import org.xipki.commons.datasource.DataSourceWrapper;
import org.xipki.commons.datasource.springframework.dao.DataAccessException;
import org.xipki.commons.password.PasswordResolverException;
import org.xipki.commons.security.CertRevocationInfo;
import org.xipki.commons.security.CertpathValidationModel;
import org.xipki.commons.security.ConcurrentContentSigner;
import org.xipki.commons.security.CrlReason;
import org.xipki.commons.security.HashAlgoType;
import org.xipki.commons.security.ObjectIdentifiers;
import org.xipki.commons.security.SecurityFactory;
import org.xipki.commons.security.SignerConf;
import org.xipki.commons.security.exception.NoIdleSignerException;
import org.xipki.commons.security.util.X509Util;
import org.xipki.pki.ocsp.api.CertStatus;
import org.xipki.pki.ocsp.api.CertStatusInfo;
import org.xipki.pki.ocsp.api.CertprofileOption;
import org.xipki.pki.ocsp.api.OcspMode;
import org.xipki.pki.ocsp.api.OcspStore;
import org.xipki.pki.ocsp.api.OcspStoreException;
import org.xipki.pki.ocsp.api.OcspStoreFactoryRegister;
import org.xipki.pki.ocsp.server.impl.OcspRespWithCacheInfo.ResponseCacheInfo;
import org.xipki.pki.ocsp.server.impl.jaxb.AuditOptionType;
import org.xipki.pki.ocsp.server.impl.jaxb.CertprofileOptionType;
import org.xipki.pki.ocsp.server.impl.jaxb.DatasourceType;
import org.xipki.pki.ocsp.server.impl.jaxb.EmbedCertsMode;
import org.xipki.pki.ocsp.server.impl.jaxb.FileOrPlainValueType;
import org.xipki.pki.ocsp.server.impl.jaxb.FileOrValueType;
import org.xipki.pki.ocsp.server.impl.jaxb.OCSPServer;
import org.xipki.pki.ocsp.server.impl.jaxb.ObjectFactory;
import org.xipki.pki.ocsp.server.impl.jaxb.RequestOptionType;
import org.xipki.pki.ocsp.server.impl.jaxb.ResponderType;
import org.xipki.pki.ocsp.server.impl.jaxb.ResponseOptionType;
import org.xipki.pki.ocsp.server.impl.jaxb.SignerType;
import org.xipki.pki.ocsp.server.impl.jaxb.StoreType;
import org.xipki.pki.ocsp.server.impl.store.crl.CrlCertStatusStore;
import org.xipki.pki.ocsp.server.impl.store.db.DbCertStatusStore;
import org.xml.sax.SAXException;

/**
 * @author Lijun Liao
 * @since 2.0.0
 */

public class OcspServer {

    private static class ServletPathResponderName implements Comparable<ServletPathResponderName> {

        private final String path;

        private final String responderName;

        ServletPathResponderName(final String path, final String responderName) {
            this.path = ParamUtil.requireNonNull("path", path);
            this.responderName = ParamUtil.requireNonBlank("responderName", responderName);
        }

        public String getPath() {
            return path;
        }

        public String getResponderName() {
            return responderName;
        }

        @Override
        public int compareTo(final ServletPathResponderName obj) {
            int diff = obj.path.length() - path.length();
            if (diff == 0) {
                return 0;
            }

            return (diff > 0) ? 1 : -1;
        }

    } // class ServletPathResponderName

    private static class OcspRespControl {
        boolean couldCacheInfo;
        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 Logger LOG = LoggerFactory.getLogger(OcspServer.class);

    private static final byte[] DERNullBytes = new byte[] { 5, 0 };

    private final DataSourceFactory datasourceFactory;

    private SecurityFactory securityFactory;

    private String confFile;

    private AuditServiceRegister auditServiceRegister;

    private OcspStoreFactoryRegister ocspStoreFactoryRegister;

    private Map<String, Responder> responders = new HashMap<>();

    private Map<String, ResponderSigner> signers = new HashMap<>();

    private Map<String, RequestOption> requestOptions = new HashMap<>();

    private Map<String, ResponseOption> responseOptions = new HashMap<>();

    private Map<String, AuditOption> auditOptions = new HashMap<>();

    private Map<String, CertprofileOption> certprofileOptions = new HashMap<>();

    private Map<String, OcspStore> stores = new HashMap<>();

    private List<ServletPathResponderName> servletPaths = new ArrayList<>();

    private AtomicBoolean initialized = new AtomicBoolean(false);

    public OcspServer() {
        this.datasourceFactory = new DataSourceFactory();
    }

    public void setSecurityFactory(final SecurityFactory securityFactory) {
        this.securityFactory = securityFactory;
    }

    public void setConfFile(final String confFile) {
        this.confFile = confFile;
    }

    ResponderAndRelativeUri getResponderAndRelativeUri(final HttpServletRequest request)
            throws UnsupportedEncodingException {
        ParamUtil.requireNonNull("request", request);
        String requestUri = request.getRequestURI();
        String servletPath = request.getServletPath();

        String path = "";
        int len = servletPath.length();
        if (requestUri.length() > len + 1) {
            path = requestUri.substring(len + 1);
        }

        ServletPathResponderName entry = null;
        for (ServletPathResponderName m : servletPaths) {
            if (path.startsWith(m.getPath())) {
                entry = m;
                break;
            }
        }

        if (entry == null) {
            return null;
        }

        String relativeUri = "";
        if (entry.getPath().length() > 0) {
            len += 1 + entry.getPath().length();
        }

        if (requestUri.length() > len + 1) {
            relativeUri = requestUri.substring(len + 1);
            relativeUri = URLDecoder.decode(relativeUri, "UTF-8");
        }

        return new ResponderAndRelativeUri(responders.get(entry.getResponderName()), relativeUri);
    } // method getResponderAndRelativeUri

    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 {
            doInit();
            initialized.set(true);
        } finally {
            if (initialized.get()) {
                LOG.info("started OCSPResponder server");
            } else {
                LOG.error("could not start OCSPResponder server");
            }
            auditLogPciEvent(initialized.get(), "START");
        }
    }

    private void doInit() 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<String> 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: audit
        set.clear();
        if (conf.getAuditOptions() != null) {
            for (AuditOptionType m : conf.getAuditOptions().getAuditOption()) {
                String name = m.getName();
                if (set.contains(name)) {
                    throw new InvalidConfException("duplicated definition of audit 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: certprofile
        set.clear();
        if (conf.getCertprofileOptions() != null) {
            for (CertprofileOptionType m : conf.getCertprofileOptions().getCertprofileOption()) {
                String name = m.getName();
                if (set.contains(name)) {
                    throw new InvalidConfException(
                            "duplicated definition of certprofile option named '" + name + "'");
                }
                set.add(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);
            }
        }

        //-- 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);
        }

        // audits
        if (conf.getAuditOptions() != null) {
            for (AuditOptionType m : conf.getAuditOptions().getAuditOption()) {
                AuditOption option = new AuditOption(m);
                auditOptions.put(m.getName(), option);
            }
        }

        // certprofiles
        if (conf.getCertprofileOptions() != null) {
            for (CertprofileOptionType m : conf.getCertprofileOptions().getCertprofileOption()) {
                CertprofileOption option = new CertprofileOption(m.getIncludes().getInclude(),
                        m.getExcludes().getExclude());
                certprofileOptions.put(m.getName(), option);
            }
        }

        // datasources
        Map<String, DataSourceWrapper> 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<String, Set<HashAlgoType>> storeCertHashAlgoSet = new HashMap<>();

        Map<String, ResponderOption> responderOptions = new HashMap<>();

        for (ResponderType m : conf.getResponders().getResponder()) {
            ResponderOption option = new ResponderOption(m);
            String optName = option.getAuditOptionName();
            if (optName != null && !auditOptions.containsKey(optName)) {
                throw new InvalidConfException("no auditOption named '" + optName + "' is defined");
            }

            optName = option.getCertprofileOptionName();
            if (optName != null && !certprofileOptions.containsKey(optName)) {
                throw new InvalidConfException("no certprofileOption named '" + optName + "' is defined");
            }

            optName = option.getSignerName();
            if (!signers.containsKey(optName)) {
                throw new InvalidConfException("no signer named '" + optName + "' is defined");
            }

            String reqOptName = option.getRequestOptionName();
            if (!requestOptions.containsKey(reqOptName)) {
                throw new InvalidConfException("no requestOption named '" + reqOptName + "' is defined");
            }

            String respOptName = option.getResponseOptionName();
            if (!responseOptions.containsKey(respOptName)) {
                throw new InvalidConfException("no responseOption named '" + respOptName + "' is defined");
            }

            // required HashAlgorithms for certificate
            ResponseOption respOpt = responseOptions.get(respOptName);
            Set<HashAlgoType> certHashAlgos = new HashSet<>(5);
            if (respOpt.isIncludeCerthash()) {
                if (respOpt.getCertHashAlgo() != null) {
                    certHashAlgos.add(respOpt.getCertHashAlgo());
                } else {
                    RequestOption reqOpt = requestOptions.get(reqOptName);
                    Set<HashAlgoType> algs = reqOpt.getHashAlgos();
                    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<String> names = option.getStoreNames();

            List<StoreType> storeDefs = conf.getStores().getStore();
            Set<String> storeNames = new HashSet<>(storeDefs.size());
            for (StoreType storeDef : storeDefs) {
                storeNames.add(storeDef.getName());
            }

            for (String name : names) {
                if (!storeNames.contains(name)) {
                    throw new InvalidConfException("no store named '" + name + "' is defined");
                }

                Set<HashAlgoType> hashAlgoSet = storeCertHashAlgoSet.get(name);
                if (hashAlgoSet == null) {
                    hashAlgoSet = new HashSet<>(5);
                    storeCertHashAlgoSet.put(name, hashAlgoSet);
                }

                hashAlgoSet.addAll(certHashAlgos);
            }

            responderOptions.put(m.getName(), option);
        } // end for

        // stores
        for (StoreType m : conf.getStores().getStore()) {
            OcspStore store = newStore(m, datasources, storeCertHashAlgoSet.get(m.getName()));
            stores.put(m.getName(), store);
        }

        // sort the servlet paths
        Set<String> pathTexts = new HashSet<>();
        for (String responderName : responderOptions.keySet()) {
            ServletPathResponderName path = new ServletPathResponderName(responderName, responderName);
            pathTexts.add(path.getPath());
            this.servletPaths.add(path);
        }

        for (String name : responderOptions.keySet()) {
            ResponderOption option = responderOptions.get(name);
            List<String> paths = option.getServletPaths();
            for (String path : paths) {
                if (pathTexts.contains(path)) {
                    throw new InvalidConfException("duplicated definition of servlet path '" + path + "'");
                }
                this.servletPaths.add(new ServletPathResponderName(path, name));
            }
        }

        Collections.sort(this.servletPaths);

        // responders
        for (String name : responderOptions.keySet()) {
            ResponderOption option = responderOptions.get(name);
            String aoName = option.getAuditOptionName();
            String cfoName = option.getCertprofileOptionName();

            List<OcspStore> statusStores = new ArrayList<>(option.getStoreNames().size());
            for (String storeName : option.getStoreNames()) {
                statusStores.add(stores.get(storeName));
            }

            AuditOption auditOption = (aoName == null) ? null : auditOptions.get(aoName);

            CertprofileOption certprofileOption = (cfoName == null) ? null : certprofileOptions.get(cfoName);

            Responder responder = new Responder(option, requestOptions.get(option.getRequestOptionName()),
                    responseOptions.get(option.getResponseOptionName()), auditOption, certprofileOption,
                    signers.get(option.getSignerName()), statusStores);
            responders.put(name, responder);
        } // end for
    } // method doInit

    public void shutdown() {
        LOG.info("stopped OCSP Responder");
        for (OcspStore store : stores.values()) {
            try {
                store.shutdown();
            } catch (Exception ex) {
                LogUtil.warn(LOG, ex, "shutdown store " + store.getName());
            }
        }

        auditLogPciEvent(true, "SHUTDOWN");
    }

    public OcspRespWithCacheInfo answer(final Responder responder, final OCSPReq request, final boolean viaGet,
            final AuditEvent event) {
        ParamUtil.requireNonNull("responder", responder);
        ParamUtil.requireNonNull("request", request);

        RequestOption reqOpt = responder.getRequestOption();
        ResponderSigner signer = responder.getSigner();
        ResponseOption repOpt = responder.getResponseOption();

        String msgId = null;
        if (event != null) {
            msgId = RandomUtil.nextHexLong();
            event.addEventData(OcspAuditConstants.NAME_mid, msgId);
        }

        int version = request.getVersionNumber();
        if (!reqOpt.isVersionAllowed(version)) {
            String message = "invalid request version " + version;
            LOG.warn(message);
            fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED, message);
            return createUnsuccessfulOcspResp(OcspResponseStatus.malformedRequest);
        }

        try {
            OcspRespWithCacheInfo resp = checkSignature(request, reqOpt, event);
            if (resp != null) {
                return resp;
            }

            OcspRespControl repControl = new OcspRespControl();
            repControl.couldCacheInfo = viaGet;

            List<Extension> responseExtensions = new ArrayList<>(2);

            Req[] requestList = request.getRequestList();
            // CHECKSTYLE:SKIP
            int requestsSize = requestList.length;

            Set<ASN1ObjectIdentifier> criticalExtensionOids = new HashSet<>();
            Set<?> tmp = request.getCriticalExtensionOIDs();
            if (tmp != null) {
                for (Object oid : tmp) {
                    criticalExtensionOids.add((ASN1ObjectIdentifier) oid);
                }
            }

            RespID respId = signer.getResponder(repOpt.isResponderIdByName());
            BasicOCSPRespBuilder basicOcspBuilder = new BasicOCSPRespBuilder(respId);
            ASN1ObjectIdentifier extensionType = OCSPObjectIdentifiers.id_pkix_ocsp_nonce;
            criticalExtensionOids.remove(extensionType);
            Extension nonceExtn = request.getExtension(extensionType);
            if (nonceExtn != null) {
                byte[] nonce = nonceExtn.getExtnValue().getOctets();
                int len = nonce.length;
                int min = reqOpt.getNonceMinLen();
                int max = reqOpt.getNonceMaxLen();

                if (len < min || len > max) {
                    LOG.warn("length of nonce {} not within [{},{}]", len, min, max);
                    StringBuilder sb = new StringBuilder(50);
                    sb.append("length of nonce ").append(len);
                    sb.append(" not within [").append(min).append(", ").append(max).append("]");
                    fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED, sb.toString());
                    return createUnsuccessfulOcspResp(OcspResponseStatus.malformedRequest);
                }

                repControl.couldCacheInfo = false;
                responseExtensions.add(nonceExtn);
            } else if (reqOpt.isNonceRequired()) {
                String message = "nonce required, but is not present in the request";
                LOG.warn(message);
                fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED, message);
                return createUnsuccessfulOcspResp(OcspResponseStatus.malformedRequest);
            }

            for (int i = 0; i < requestsSize; i++) {
                AuditEvent singleEvent = null;
                if (event != null) {
                    singleEvent = new AuditEvent(new Date());
                    singleEvent.setApplicationName(OcspAuditConstants.APPNAME);
                    singleEvent.setName(OcspAuditConstants.NAME_PERF);
                    singleEvent.addEventData(OcspAuditConstants.NAME_mid, msgId);
                }

                OcspRespWithCacheInfo ocspResp = null;
                try {
                    ocspResp = processCertReq(requestList[i], basicOcspBuilder, responder, reqOpt, repOpt,
                            repControl, singleEvent);
                } finally {
                    if (singleEvent != null) {
                        singleEvent.finish();
                        auditServiceRegister.getAuditService().doLogEvent(singleEvent);
                    }
                }

                if (ocspResp != null) {
                    return ocspResp;
                }
            }

            if (repControl.includeExtendedRevokeExtension) {
                responseExtensions.add(new Extension(ObjectIdentifiers.id_pkix_ocsp_extendedRevoke, true,
                        Arrays.copyOf(DERNullBytes, DERNullBytes.length)));
            }

            if (CollectionUtil.isNonEmpty(responseExtensions)) {
                basicOcspBuilder
                        .setResponseExtensions(new Extensions(responseExtensions.toArray(new Extension[0])));
            }

            ConcurrentContentSigner concurrentSigner = null;
            if (responder.getResponderOption().getMode() != OcspMode.RFC2560) {
                extensionType = ObjectIdentifiers.id_pkix_ocsp_prefSigAlgs;
                criticalExtensionOids.remove(extensionType);
                Extension ext = request.getExtension(extensionType);
                if (ext != null) {
                    ASN1Sequence preferredSigAlgs = ASN1Sequence.getInstance(ext.getParsedValue());
                    concurrentSigner = signer.getSignerForPreferredSigAlgs(preferredSigAlgs);
                }
            }

            if (CollectionUtil.isNonEmpty(criticalExtensionOids)) {
                return createUnsuccessfulOcspResp(OcspResponseStatus.malformedRequest);
            }

            if (concurrentSigner == null) {
                concurrentSigner = signer.getFirstSigner();
            }

            X509CertificateHolder[] certsInResp;
            EmbedCertsMode certsMode = repOpt.getEmbedCertsMode();
            if (certsMode == null || certsMode == EmbedCertsMode.SIGNER) {
                certsInResp = new X509CertificateHolder[] { signer.getBcCertificate() };
            } else if (certsMode == EmbedCertsMode.SIGNER_AND_CA) {
                certsInResp = signer.getBcCertificateChain();
            } else {
                // NONE
                certsInResp = null;
            }

            BasicOCSPResp basicOcspResp;
            try {
                basicOcspResp = concurrentSigner.build(basicOcspBuilder, certsInResp, new Date());
            } catch (NoIdleSignerException ex) {
                return createUnsuccessfulOcspResp(OcspResponseStatus.tryLater);
            } catch (OCSPException ex) {
                LogUtil.error(LOG, ex, "answer() basicOcspBuilder.build");
                fillAuditEvent(event, AuditLevel.ERROR, AuditStatus.FAILED,
                        "BasicOCSPRespBuilder.build() with OCSPException");
                return createUnsuccessfulOcspResp(OcspResponseStatus.internalError);
            }

            OCSPRespBuilder ocspRespBuilder = new OCSPRespBuilder();
            try {
                OCSPResp ocspResp = ocspRespBuilder.build(OcspResponseStatus.successful.getStatus(), basicOcspResp);

                if (repControl.couldCacheInfo) {
                    ResponseCacheInfo cacheInfo = new ResponseCacheInfo(repControl.cacheThisUpdate);
                    if (repControl.cacheNextUpdate != Long.MAX_VALUE) {
                        cacheInfo.setNextUpdate(repControl.cacheNextUpdate);
                    }
                    return new OcspRespWithCacheInfo(ocspResp, cacheInfo);
                } else {
                    return new OcspRespWithCacheInfo(ocspResp, null);
                }
            } catch (OCSPException ex) {
                LogUtil.error(LOG, ex, "answer() ocspRespBuilder.build");
                fillAuditEvent(event, AuditLevel.ERROR, AuditStatus.FAILED,
                        "OCSPRespBuilder.build() with OCSPException");
                return createUnsuccessfulOcspResp(OcspResponseStatus.internalError);
            }
        } catch (Throwable th) {
            LogUtil.error(LOG, th);
            fillAuditEvent(event, AuditLevel.ERROR, AuditStatus.FAILED, "internal error");
            return createUnsuccessfulOcspResp(OcspResponseStatus.internalError);
        }
    } // method ask

    private OcspRespWithCacheInfo processCertReq(Req req, BasicOCSPRespBuilder builder, Responder responder,
            RequestOption reqOpt, ResponseOption repOpt, OcspRespControl repControl, AuditEvent event)
            throws IOException {
        CertificateID certId = req.getCertID();
        String certIdHashAlgo = certId.getHashAlgOID().getId();
        HashAlgoType reqHashAlgo = HashAlgoType.getHashAlgoType(certIdHashAlgo);
        if (reqHashAlgo == null) {
            LOG.warn("unknown CertID.hashAlgorithm {}", certIdHashAlgo);
            if (event != null) {
                fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED,
                        "unknown CertID.hashAlgorithm " + certIdHashAlgo);
            }
            return createUnsuccessfulOcspResp(OcspResponseStatus.malformedRequest);
        } else if (!reqOpt.allows(reqHashAlgo)) {
            LOG.warn("CertID.hashAlgorithm {} not allowed", certIdHashAlgo);
            if (event != null) {
                fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED,
                        "not allowed CertID.hashAlgorithm " + certIdHashAlgo);
            }
            return createUnsuccessfulOcspResp(OcspResponseStatus.malformedRequest);
        }

        if (event != null) {
            event.addEventData(OcspAuditConstants.NAME_serial, certId.getSerialNumber());
        }

        CertStatusInfo certStatusInfo = null;
        OcspStore answeredStore = null;
        boolean exceptionOccurs = false;

        Date now = new Date();
        for (OcspStore store : responder.getStores()) {
            try {
                certStatusInfo = store.getCertStatus(now, reqHashAlgo, certId.getIssuerNameHash(),
                        certId.getIssuerKeyHash(), certId.getSerialNumber(), repOpt.isIncludeCerthash(),
                        repOpt.getCertHashAlgo(), responder.getCertprofileOption());
                if (certStatusInfo != null && certStatusInfo.getCertStatus() != CertStatus.ISSUER_UNKNOWN) {
                    answeredStore = store;
                    break;
                }
            } catch (OcspStoreException ex) {
                exceptionOccurs = true;
                LogUtil.error(LOG, ex, "getCertStatus() of CertStatusStore " + store.getName());
            } // end try
        } // end for

        if (certStatusInfo == null) {
            if (exceptionOccurs) {
                fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED,
                        "no CertStatusStore can answer the request");
                return createUnsuccessfulOcspResp(OcspResponseStatus.tryLater);
            } else {
                certStatusInfo = CertStatusInfo.getIssuerUnknownCertStatusInfo(new Date(), null);
            }
        } else if (answeredStore != null && responder.getResponderOption().isInheritCaRevocation()) {
            CertRevocationInfo caRevInfo = answeredStore.getCaRevocationInfo(reqHashAlgo,
                    certId.getIssuerNameHash(), certId.getIssuerKeyHash());
            if (caRevInfo != null) {
                CertStatus certStatus = certStatusInfo.getCertStatus();
                boolean replaced = false;
                if (certStatus == CertStatus.GOOD || certStatus == CertStatus.UNKNOWN) {
                    replaced = true;
                } else if (certStatus == CertStatus.REVOKED) {
                    if (certStatusInfo.getRevocationInfo().getRevocationTime()
                            .after(caRevInfo.getRevocationTime())) {
                        replaced = true;
                    }
                }

                if (replaced) {
                    CertRevocationInfo newRevInfo;
                    if (caRevInfo.getReason() == CrlReason.CA_COMPROMISE) {
                        newRevInfo = caRevInfo;
                    } else {
                        newRevInfo = new CertRevocationInfo(CrlReason.CA_COMPROMISE, caRevInfo.getRevocationTime(),
                                caRevInfo.getInvalidityTime());
                    }
                    certStatusInfo = CertStatusInfo.getRevokedCertStatusInfo(newRevInfo,
                            certStatusInfo.getCertHashAlgo(), certStatusInfo.getCertHash(),
                            certStatusInfo.getThisUpdate(), certStatusInfo.getNextUpdate(),
                            certStatusInfo.getCertprofile());
                } // end if(replaced)
            } // end if
        } // end if

        if (event != null) {
            String certprofile = certStatusInfo.getCertprofile();
            String auditCertType;
            if (certprofile != null) {
                auditCertType = responder.getAuditOption().getCertprofileMapping().get(certprofile);
                if (auditCertType == null) {
                    auditCertType = certprofile;
                }
            } else {
                auditCertType = "UNKNOWN";
            }

            event.addEventData(OcspAuditConstants.NAME_type, auditCertType);
        }

        // certStatusInfo must not be null in any case, since at least one store
        // is configured
        Date thisUpdate = certStatusInfo.getThisUpdate();
        if (thisUpdate == null) {
            thisUpdate = new Date();
        }
        Date nextUpdate = certStatusInfo.getNextUpdate();

        List<Extension> extensions = new LinkedList<>();
        boolean unknownAsRevoked = false;
        CertificateStatus bcCertStatus;
        switch (certStatusInfo.getCertStatus()) {
        case GOOD:
            bcCertStatus = null;
            break;

        case ISSUER_UNKNOWN:
            repControl.couldCacheInfo = false;
            bcCertStatus = new UnknownStatus();
            break;

        case UNKNOWN:
        case IGNORE:
            repControl.couldCacheInfo = false;
            if (responder.getResponderOption().getMode() == OcspMode.RFC2560) {
                bcCertStatus = new UnknownStatus();
            } else { // (ocspMode == OCSPMode.RFC6960)
                unknownAsRevoked = true;
                repControl.includeExtendedRevokeExtension = true;
                bcCertStatus = new RevokedStatus(new Date(0L), CrlReason.CERTIFICATE_HOLD.getCode());
            }
            break;
        case REVOKED:
            CertRevocationInfo revInfo = certStatusInfo.getRevocationInfo();
            ASN1GeneralizedTime revTime = new ASN1GeneralizedTime(revInfo.getRevocationTime());
            org.bouncycastle.asn1.x509.CRLReason tmpReason = null;
            if (repOpt.isIncludeRevReason()) {
                tmpReason = org.bouncycastle.asn1.x509.CRLReason.lookup(revInfo.getReason().getCode());
            }
            RevokedInfo tmpRevInfo = new RevokedInfo(revTime, tmpReason);
            bcCertStatus = new RevokedStatus(tmpRevInfo);

            Date invalidityDate = revInfo.getInvalidityTime();
            if (repOpt.isIncludeInvalidityDate() && invalidityDate != null
                    && !invalidityDate.equals(revInfo.getRevocationTime())) {
                Extension extension = new Extension(Extension.invalidityDate, false,
                        new ASN1GeneralizedTime(invalidityDate).getEncoded());
                extensions.add(extension);
            }
            break;
        default:
            throw new RuntimeException("unknown CertificateStatus:" + certStatusInfo.getCertStatus());
        } // end switch

        byte[] certHash = certStatusInfo.getCertHash();
        if (certHash != null) {
            ASN1ObjectIdentifier hashAlgOid = certStatusInfo.getCertHashAlgo().getOid();
            AlgorithmIdentifier hashAlgId = new AlgorithmIdentifier(hashAlgOid, DERNull.INSTANCE);
            CertHash bcCertHash = new CertHash(hashAlgId, certHash);

            byte[] encodedCertHash;
            try {
                encodedCertHash = bcCertHash.getEncoded();
            } catch (IOException ex) {
                LogUtil.error(LOG, ex, "answer() bcCertHash.getEncoded");
                if (event != null) {
                    fillAuditEvent(event, AuditLevel.ERROR, AuditStatus.FAILED,
                            "CertHash.getEncoded() with IOException");
                }
                return createUnsuccessfulOcspResp(OcspResponseStatus.internalError);
            }

            Extension extension = new Extension(ISISMTTObjectIdentifiers.id_isismtt_at_certHash, false,
                    encodedCertHash);

            extensions.add(extension);
        } // end if(certHash != null)

        if (certStatusInfo.getArchiveCutOff() != null) {
            Extension extension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_archive_cutoff, false,
                    new ASN1GeneralizedTime(certStatusInfo.getArchiveCutOff()).getEncoded());
            extensions.add(extension);
        }

        String certStatusText;
        if (bcCertStatus instanceof UnknownStatus) {
            certStatusText = "unknown";
        } else if (bcCertStatus instanceof RevokedStatus) {
            certStatusText = unknownAsRevoked ? "unknown_as_revoked" : "revoked";
        } else if (bcCertStatus == null) {
            certStatusText = "good";
        } else {
            certStatusText = "should-not-happen";
        }

        if (event != null) {
            event.setLevel(AuditLevel.INFO);
            event.setStatus(AuditStatus.SUCCESSFUL);
            event.addEventData(OcspAuditConstants.NAME_status, certStatusText);
        }

        if (LOG.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder(250);
            sb.append("certHashAlgo: ").append(certId.getHashAlgOID().getId()).append(", ");
            sb.append("issuerNameHash: ").append(Hex.toHexString(certId.getIssuerNameHash()).toUpperCase())
                    .append(", ");
            sb.append("issuerKeyHash: ").append(Hex.toHexString(certId.getIssuerKeyHash()).toUpperCase())
                    .append(", ");
            sb.append("serialNumber: ").append(LogUtil.formatCsn(certId.getSerialNumber())).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());
        }

        Extensions extns = null;
        if (CollectionUtil.isNonEmpty(extensions)) {
            extns = new Extensions(extensions.toArray(new Extension[0]));
        }

        builder.addResponse(certId, bcCertStatus, thisUpdate, nextUpdate, extns);
        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.getStores()) {
            boolean storeHealthy = store.isHealthy();
            healthy &= storeHealthy;

            HealthCheckResult storeHealth = new HealthCheckResult("CertStatusStore." + store.getName());
            storeHealth.setHealthy(storeHealthy);
            result.addChildCheck(storeHealth);
        }

        boolean signerHealthy = responder.getSigner().isHealthy();
        healthy &= signerHealthy;

        HealthCheckResult signerHealth = new HealthCheckResult("Signer");
        signerHealth.setHealthy(signerHealthy);
        result.addChildCheck(signerHealth);

        result.setHealthy(healthy);
        return result;
    } // method healthCheck

    public void setAuditServiceRegister(final AuditServiceRegister auditServiceRegister) {
        this.auditServiceRegister = auditServiceRegister;
        for (OcspStore store : stores.values()) {
            store.setAuditServiceRegister(auditServiceRegister);
        }
    }

    public void setOcspStoreFactoryRegister(final OcspStoreFactoryRegister ocspStoreFactoryRegister) {
        this.ocspStoreFactoryRegister = ocspStoreFactoryRegister;
    }

    private void auditLogPciEvent(final boolean successful, final String eventType) {
        PciAuditEvent event = new PciAuditEvent(new Date());
        event.setUserId("OCSP-SYSTEM");
        event.setEventType(eventType);
        event.setAffectedResource("CORE");
        if (successful) {
            event.setStatus(AuditStatus.SUCCESSFUL.name());
            event.setLevel(AuditLevel.INFO);
        } else {
            event.setStatus(AuditStatus.FAILED.name());
            event.setLevel(AuditLevel.ERROR);
        }
        auditServiceRegister.getAuditService().logEvent(event);
    }

    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<X509Certificate> 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<String> sigAlgos = signerType.getAlgorithms().getAlgorithm();
        List<ConcurrentContentSigner> 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<String, DataSourceWrapper> datasources,
            final Set<HashAlgoType> certHashAlgos) throws InvalidConfException {
        OcspStore store;
        String type = conf.getSource().getType();
        if ("CRL".equalsIgnoreCase(type)) {
            store = new CrlCertStatusStore();
        } 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());
        store.setAuditServiceRegister(auditServiceRegister);
        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, certHashAlgos);
        } catch (OcspStoreException ex) {
            throw new InvalidConfException(
                    "CertStatusStoreException of store " + conf.getName() + ":" + ex.getMessage(), ex);
        }

        return store;
    } // method initStore

    private OcspRespWithCacheInfo checkSignature(final OCSPReq request, final RequestOption requestOption,
            final AuditEvent event) throws OCSPException, CertificateParsingException,
            InvalidAlgorithmParameterException, OcspResponderException {
        if (!request.isSigned()) {
            if (!requestOption.isSignatureRequired()) {
                return null;
            }

            String message = "signature in request required";
            LOG.warn(message);
            fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED, message);
            return createUnsuccessfulOcspResp(OcspResponseStatus.sigRequired);
        }

        if (!requestOption.isValidateSignature()) {
            return null;
        }

        X509CertificateHolder[] certs = request.getCerts();
        if (certs == null || certs.length < 1) {
            String message = "no certificate found in request to verify the signature";
            LOG.warn(message);
            fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED, message);
            return createUnsuccessfulOcspResp(OcspResponseStatus.unauthorized);
        }

        ContentVerifierProvider cvp;
        try {
            cvp = securityFactory.getContentVerifierProvider(certs[0]);
        } catch (InvalidKeyException ex) {
            LOG.warn("securityFactory.getContentVerifierProvider, InvalidKeyException: {}", ex.getMessage());
            fillAuditEvent(event, AuditLevel.ERROR, AuditStatus.FAILED, ex.getMessage());
            return createUnsuccessfulOcspResp(OcspResponseStatus.unauthorized);
        }

        boolean sigValid = request.isSignatureValid(cvp);
        if (!sigValid) {
            String message = "request signature is invalid";
            LOG.warn(message);
            fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED, message);
            return createUnsuccessfulOcspResp(OcspResponseStatus.unauthorized);
        }

        // validate the certPath
        Date referenceTime = new Date();
        if (canBuildCertpath(certs, requestOption, referenceTime)) {
            return null;
        }

        String message = "could not build certpath for the request's signer certificate";
        LOG.warn(message);
        fillAuditEvent(event, AuditLevel.INFO, AuditStatus.FAILED, message);
        return createUnsuccessfulOcspResp(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<Certificate> certstore = new HashSet<>();

        Set<CertWithEncoded> trustAnchors = requestOption.getTrustAnchors();
        for (CertWithEncoded m : trustAnchors) {
            certstore.add(m.getCertificate());
        }

        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<X509Certificate> configuredCerts = requestOption.getCerts();
        if (CollectionUtil.isNonEmpty(configuredCerts)) {
            certstore.addAll(requestOption.getCerts());
        }

        X509Certificate[] certpath = X509Util.buildCertPath(target, certstore);
        CertpathValidationModel model = requestOption.getCertpathValidationModel();

        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 OcspRespWithCacheInfo createUnsuccessfulOcspResp(final OcspResponseStatus status) {
        OCSPResp resp = new OCSPResp(
                new OCSPResponse(new org.bouncycastle.asn1.ocsp.OCSPResponseStatus(status.getStatus()), null));
        return new OcspRespWithCacheInfo(resp, null);
    }

    private static void fillAuditEvent(final AuditEvent event, final AuditLevel level, final AuditStatus status,
            final String message) {
        if (event == null) {
            return;
        }

        if (level != null) {
            event.setLevel(level);
        }

        if (status != null) {
            event.setStatus(status);
        }

        if (message != null) {
            event.addEventData(OcspAuditConstants.NAME_message, message);
        }
    }

    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);
        }
    }

}