/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.pki.ocsp.client.impl;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
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.CertificateID;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.operator.DigestCalculator;
import org.eclipse.jdt.annotation.NonNull;
import org.xipki.commons.common.RequestResponseDebug;
import org.xipki.commons.common.RequestResponsePair;
import org.xipki.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.common.util.StringUtil;
import org.xipki.commons.security.ConcurrentContentSigner;
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.client.api.InvalidOcspResponseException;
import org.xipki.pki.ocsp.client.api.OcspNonceUnmatchedException;
import org.xipki.pki.ocsp.client.api.OcspRequestor;
import org.xipki.pki.ocsp.client.api.OcspRequestorException;
import org.xipki.pki.ocsp.client.api.OcspResponseException;
import org.xipki.pki.ocsp.client.api.OcspTargetUnmatchedException;
import org.xipki.pki.ocsp.client.api.RequestOptions;
import org.xipki.pki.ocsp.client.api.ResponderUnreachableException;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public abstract class AbstractOcspRequestor implements OcspRequestor {
private SecurityFactory securityFactory;
private final Object signerLock = new Object();
private ConcurrentContentSigner signer;
private String signerType;
private String signerConf;
private String signerCertFile;
private SecureRandom random = new SecureRandom();
protected AbstractOcspRequestor() {
}
protected abstract byte[] send(@NonNull byte[] request, @NonNull URL responderUrl,
@NonNull RequestOptions requestOptions) throws IOException;
@Override
public OCSPResp ask(final X509Certificate issuerCert, final X509Certificate cert,
final URL responderUrl, final RequestOptions requestOptions,
final RequestResponseDebug debug) throws OcspResponseException, OcspRequestorException {
ParamUtil.requireNonNull("issuerCert", issuerCert);
ParamUtil.requireNonNull("cert", cert);
try {
if (!X509Util.issues(issuerCert, cert)) {
throw new IllegalArgumentException("cert and issuerCert do not match");
}
} catch (CertificateEncodingException ex) {
throw new OcspRequestorException(ex.getMessage(), ex);
}
return ask(issuerCert, new BigInteger[]{cert.getSerialNumber()}, responderUrl,
requestOptions, debug);
}
@Override
public OCSPResp ask(final X509Certificate issuerCert, final X509Certificate[] certs,
final URL responderUrl, final RequestOptions requestOptions,
final RequestResponseDebug debug) throws OcspResponseException, OcspRequestorException {
ParamUtil.requireNonNull("issuerCert", issuerCert);
ParamUtil.requireNonNull("certs", certs);
ParamUtil.requireMin("certs.length", certs.length, 1);
BigInteger[] serialNumbers = new BigInteger[certs.length];
for (int i = 0; i < certs.length; i++) {
X509Certificate cert = certs[i];
try {
if (!X509Util.issues(issuerCert, cert)) {
throw new IllegalArgumentException(
"cert at index " + i + " and issuerCert do not match");
}
} catch (CertificateEncodingException ex) {
throw new OcspRequestorException(ex.getMessage(), ex);
}
serialNumbers[i] = cert.getSerialNumber();
}
return ask(issuerCert, serialNumbers, responderUrl, requestOptions, debug);
}
@Override
public OCSPResp ask(final X509Certificate issuerCert, final BigInteger serialNumber,
final URL responderUrl, final RequestOptions requestOptions,
final RequestResponseDebug debug) throws OcspResponseException, OcspRequestorException {
return ask(issuerCert, new BigInteger[]{serialNumber}, responderUrl, requestOptions, debug);
}
@Override
public OCSPResp ask(final X509Certificate issuerCert, final BigInteger[] serialNumbers,
final URL responderUrl, final RequestOptions requestOptions,
final RequestResponseDebug debug) throws OcspResponseException, OcspRequestorException {
ParamUtil.requireNonNull("issuerCert", issuerCert);
ParamUtil.requireNonNull("requestOptions", requestOptions);
ParamUtil.requireNonNull("responderUrl", responderUrl);
byte[] nonce = null;
if (requestOptions.isUseNonce()) {
nonce = nextNonce(requestOptions.getNonceLen());
}
OCSPReq ocspReq = buildRequest(issuerCert, serialNumbers, nonce, requestOptions);
byte[] encodedReq;
try {
encodedReq = ocspReq.getEncoded();
} catch (IOException ex) {
throw new OcspRequestorException("could not encode OCSP request: " + ex.getMessage(),
ex);
}
RequestResponsePair msgPair = null;
if (debug != null) {
msgPair = new RequestResponsePair();
debug.add(msgPair);
msgPair.setRequest(encodedReq);
}
byte[] encodedResp;
try {
encodedResp = send(encodedReq, responderUrl, requestOptions);
} catch (IOException ex) {
throw new ResponderUnreachableException("IOException: " + ex.getMessage(), ex);
}
if (msgPair != null) {
msgPair.setResponse(encodedResp);
}
OCSPResp ocspResp;
try {
ocspResp = new OCSPResp(encodedResp);
} catch (IOException ex) {
throw new InvalidOcspResponseException("IOException: " + ex.getMessage(), ex);
}
Object respObject;
try {
respObject = ocspResp.getResponseObject();
} catch (OCSPException ex) {
throw new InvalidOcspResponseException("responseObject is invalid");
}
if (ocspResp.getStatus() != 0) {
return ocspResp;
}
if (!(respObject instanceof BasicOCSPResp)) {
return ocspResp;
}
BasicOCSPResp basicOcspResp = (BasicOCSPResp) respObject;
if (nonce != null) {
Extension nonceExtn = basicOcspResp.getExtension(
OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
if (nonceExtn == null) {
throw new OcspNonceUnmatchedException(nonce, null);
}
byte[] receivedNonce = nonceExtn.getExtnValue().getOctets();
if (!Arrays.equals(nonce, receivedNonce)) {
throw new OcspNonceUnmatchedException(nonce, receivedNonce);
}
}
SingleResp[] singleResponses = basicOcspResp.getResponses();
if (singleResponses == null || singleResponses.length == 0) {
StringBuilder sb = new StringBuilder(100);
sb.append("response with no singleResponse is returned, expected is ");
sb.append(serialNumbers.length);
throw new OcspTargetUnmatchedException(sb.toString());
}
final int countSingleResponses = singleResponses.length;
if (countSingleResponses != serialNumbers.length) {
StringBuilder sb = new StringBuilder(100);
sb.append("response with ").append(countSingleResponses).append(" singleResponse");
if (countSingleResponses > 1) {
sb.append("s");
}
sb.append(" is returned, expected is ").append(serialNumbers.length);
throw new OcspTargetUnmatchedException(sb.toString());
}
CertificateID certId = ocspReq.getRequestList()[0].getCertID();
ASN1ObjectIdentifier issuerHashAlg = certId.getHashAlgOID();
byte[] issuerKeyHash = certId.getIssuerKeyHash();
byte[] issuerNameHash = certId.getIssuerNameHash();
if (serialNumbers.length == 1) {
SingleResp singleResp = singleResponses[0];
CertificateID cid = singleResp.getCertID();
boolean issuerMatch = issuerHashAlg.equals(cid.getHashAlgOID())
&& Arrays.equals(issuerKeyHash, cid.getIssuerKeyHash())
&& Arrays.equals(issuerNameHash, cid.getIssuerNameHash());
if (!issuerMatch) {
throw new OcspTargetUnmatchedException("the issuer is not requested");
}
BigInteger serialNumber = cid.getSerialNumber();
if (!serialNumbers[0].equals(serialNumber)) {
throw new OcspTargetUnmatchedException("the serialNumber is not requested");
}
} else {
List<BigInteger> tmpSerials1 = Arrays.asList(serialNumbers);
List<BigInteger> tmpSerials2 = new ArrayList<>(tmpSerials1);
for (int i = 0; i < countSingleResponses; i++) {
SingleResp singleResp = singleResponses[i];
CertificateID cid = singleResp.getCertID();
boolean issuerMatch = issuerHashAlg.equals(cid.getHashAlgOID())
&& Arrays.equals(issuerKeyHash, cid.getIssuerKeyHash())
&& Arrays.equals(issuerNameHash, cid.getIssuerNameHash());
if (!issuerMatch) {
throw new OcspTargetUnmatchedException(
"the issuer specified in singleResponse[" + i + "] is not requested");
}
BigInteger serialNumber = cid.getSerialNumber();
if (!tmpSerials2.remove(serialNumber)) {
if (tmpSerials1.contains(serialNumber)) {
throw new OcspTargetUnmatchedException("serialNumber "
+ LogUtil.formatCsn(serialNumber)
+ "is contained in at least two singleResponses");
} else {
throw new OcspTargetUnmatchedException(
"serialNumber " + LogUtil.formatCsn(serialNumber)
+ " specified in singleResponse[" + i + "] is not requested");
}
}
} // end for
} // end if
return ocspResp;
} // method ask
private OCSPReq buildRequest(final X509Certificate caCert, final BigInteger[] serialNumbers,
final byte[] nonce, final RequestOptions requestOptions) throws OcspRequestorException {
HashAlgoType hashAlgo = HashAlgoType.getHashAlgoType(requestOptions.getHashAlgorithmId());
if (hashAlgo == null) {
throw new OcspRequestorException("unknown HashAlgo "
+ requestOptions.getHashAlgorithmId().getId());
}
List<AlgorithmIdentifier> prefSigAlgs = requestOptions.getPreferredSignatureAlgorithms();
DigestCalculator digestCalculator;
switch (hashAlgo) {
case SHA1:
digestCalculator = new SHA1DigestCalculator();
break;
case SHA224:
digestCalculator = new SHA224DigestCalculator();
break;
case SHA256:
digestCalculator = new SHA256DigestCalculator();
break;
case SHA384:
digestCalculator = new SHA384DigestCalculator();
break;
case SHA512:
digestCalculator = new SHA512DigestCalculator();
break;
case SHA3_224:
digestCalculator = new SHA3_224DigestCalculator();
break;
case SHA3_256:
digestCalculator = new SHA3_256DigestCalculator();
break;
case SHA3_384:
digestCalculator = new SHA3_384DigestCalculator();
break;
case SHA3_512:
digestCalculator = new SHA3_512DigestCalculator();
break;
default:
throw new RuntimeException("unknown HashAlgoType: " + hashAlgo);
}
OCSPReqBuilder reqBuilder = new OCSPReqBuilder();
List<Extension> extensions = new LinkedList<>();
if (nonce != null) {
Extension extn = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,
new DEROctetString(nonce));
extensions.add(extn);
}
if (prefSigAlgs != null && prefSigAlgs.size() > 0) {
ASN1EncodableVector vec = new ASN1EncodableVector();
for (AlgorithmIdentifier algId : prefSigAlgs) {
ASN1Sequence prefSigAlgObj = new DERSequence(algId);
vec.add(prefSigAlgObj);
}
ASN1Sequence extnValue = new DERSequence(vec);
Extension extn;
try {
extn = new Extension(ObjectIdentifiers.id_pkix_ocsp_prefSigAlgs, false,
new DEROctetString(extnValue));
} catch (IOException ex) {
throw new OcspRequestorException(ex.getMessage(), ex);
}
extensions.add(extn);
}
if (CollectionUtil.isNonEmpty(extensions)) {
reqBuilder.setRequestExtensions(new Extensions(extensions.toArray(new Extension[0])));
}
try {
for (BigInteger serialNumber : serialNumbers) {
CertificateID certId = new CertificateID(digestCalculator,
new X509CertificateHolder(caCert.getEncoded()), serialNumber);
reqBuilder.addRequest(certId);
}
if (requestOptions.isSignRequest()) {
synchronized (signerLock) {
if (signer == null) {
if (StringUtil.isBlank(signerType)) {
throw new OcspRequestorException("signerType is not configured");
}
if (StringUtil.isBlank(signerConf)) {
throw new OcspRequestorException("signerConf is not configured");
}
X509Certificate cert = null;
if (StringUtil.isNotBlank(signerCertFile)) {
try {
cert = X509Util.parseCert(signerCertFile);
} catch (CertificateException ex) {
throw new OcspRequestorException("could not parse certificate "
+ signerCertFile + ": " + ex.getMessage());
}
}
try {
signer = getSecurityFactory().createSigner(signerType,
new SignerConf(signerConf), cert);
} catch (Exception ex) {
throw new OcspRequestorException("could not create signer: "
+ ex.getMessage());
}
} // end if
} // end synchronized
reqBuilder.setRequestorName(signer.getCertificateAsBcObject().getSubject());
try {
return signer.build(reqBuilder, signer.getCertificateChainAsBcObjects());
} catch (NoIdleSignerException ex) {
throw new OcspRequestorException("NoIdleSignerException: " + ex.getMessage());
}
} else {
return reqBuilder.build();
} // end if
} catch (OCSPException | CertificateEncodingException | IOException ex) {
throw new OcspRequestorException(ex.getMessage(), ex);
}
} // method buildRequest
private byte[] nextNonce(final int nonceLen) {
byte[] nonce = new byte[nonceLen];
random.nextBytes(nonce);
return nonce;
}
public String getSignerConf() {
return signerConf;
}
public void setSignerConf(final String signerConf) {
this.signer = null;
this.signerConf = signerConf;
}
public String getSignerCertFile() {
return signerCertFile;
}
public void setSignerCertFile(final String signerCertFile) {
if (StringUtil.isBlank(signerCertFile)) {
return;
}
this.signer = null;
this.signerCertFile = signerCertFile;
}
public String getSignerType() {
return signerType;
}
public void setSignerType(final String signerType) {
this.signer = null;
this.signerType = ParamUtil.requireNonBlank("signerType", signerType);
}
public SecurityFactory getSecurityFactory() {
return securityFactory;
}
public void setSecurityFactory(final SecurityFactory securityFactory) {
this.securityFactory = securityFactory;
}
}