Java tutorial
/* * * This file is part of the XiPKI project. * Copyright (c) 2014 - 2015 Lijun Liao * Author: 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.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.nist.NISTObjectIdentifiers; 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.ContentSigner; import org.bouncycastle.operator.DigestCalculator; import org.xipki.common.RequestResponseDebug; import org.xipki.common.RequestResponsePair; import org.xipki.common.util.CollectionUtil; import org.xipki.common.util.StringUtil; import org.xipki.common.util.X509Util; import org.xipki.ocsp.client.api.InvalidOCSPResponseException; import org.xipki.ocsp.client.api.OCSPNonceUnmatchedException; import org.xipki.ocsp.client.api.OCSPRequestor; import org.xipki.ocsp.client.api.OCSPRequestorException; import org.xipki.ocsp.client.api.OCSPResponseException; import org.xipki.ocsp.client.api.OCSPTargetUnmatchedException; import org.xipki.ocsp.client.api.RequestOptions; import org.xipki.ocsp.client.api.ResponderUnreachableException; import org.xipki.security.api.ConcurrentContentSigner; import org.xipki.security.api.NoIdleSignerException; import org.xipki.security.api.SecurityFactory; /** * @author Lijun Liao */ 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 abstract byte[] send(byte[] request, URL responderUrl, RequestOptions requestOptions) throws IOException; protected AbstractOCSPRequestor() { } @Override public OCSPResp ask(final X509Certificate issuerCert, final X509Certificate cert, final URL responderUrl, final RequestOptions requestOptions, final RequestResponseDebug debug) throws OCSPResponseException, OCSPRequestorException { try { if (X509Util.issues(issuerCert, cert) == false) { throw new IllegalArgumentException("cert and issuerCert do not match"); } } catch (CertificateEncodingException e) { throw new OCSPRequestorException(e.getMessage(), e); } 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 { BigInteger[] serialNumbers = new BigInteger[certs.length]; for (int i = 0; i < certs.length; i++) { X509Certificate cert = certs[i]; try { if (X509Util.issues(issuerCert, cert) == false) { throw new IllegalArgumentException("cert at index " + i + " and issuerCert do not match"); } } catch (CertificateEncodingException e) { throw new OCSPRequestorException(e.getMessage(), e); } 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 { if (requestOptions == null) { throw new IllegalArgumentException("requestOptions could not be null"); } 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 e) { throw new OCSPRequestorException("could not encode OCSP request: " + e.getMessage(), e); } 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 e) { throw new ResponderUnreachableException("IOException: " + e.getMessage(), e); } if (debug != null) { msgPair.setRequest(encodedResp); } OCSPResp ocspResp; try { ocspResp = new OCSPResp(encodedResp); } catch (IOException e) { throw new InvalidOCSPResponseException("IOException: " + e.getMessage(), e); } Object respObject; try { respObject = ocspResp.getResponseObject(); } catch (OCSPException e) { throw new InvalidOCSPResponseException("responseObject is invalid"); } if (ocspResp.getStatus() != 0) { return ocspResp; } if (respObject instanceof BasicOCSPResp == false) { 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) == false) { throw new OCSPNonceUnmatchedException(nonce, receivedNonce); } } SingleResp[] singleResponses = basicOCSPResp.getResponses(); final int countSingleResponses = singleResponses == null ? 0 : singleResponses.length; if (countSingleResponses != serialNumbers.length) { StringBuilder sb = new StringBuilder(100); sb.append("response with ").append(countSingleResponses).append(" singleRessponse"); 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 m = singleResponses[0]; CertificateID cid = m.getCertID(); boolean issuerMatch = issuerHashAlg.equals(cid.getHashAlgOID()) && Arrays.equals(issuerKeyHash, cid.getIssuerKeyHash()) && Arrays.equals(issuerNameHash, cid.getIssuerNameHash()); if (issuerMatch == false) { throw new OCSPTargetUnmatchedException("the issuer is not requested"); } BigInteger serialNumber = cid.getSerialNumber(); if (serialNumbers[0].equals(serialNumber) == false) { 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 < singleResponses.length; i++) { SingleResp m = singleResponses[i]; CertificateID cid = m.getCertID(); boolean issuerMatch = issuerHashAlg.equals(cid.getHashAlgOID()) && Arrays.equals(issuerKeyHash, cid.getIssuerKeyHash()) && Arrays.equals(issuerNameHash, cid.getIssuerNameHash()); if (issuerMatch == false) { throw new OCSPTargetUnmatchedException( "the issuer specified in singleResponse[" + i + "] is not requested"); } BigInteger serialNumber = cid.getSerialNumber(); if (tmpSerials2.remove(serialNumber) == false) { if (tmpSerials1.contains(serialNumber)) { throw new OCSPTargetUnmatchedException( "serialNumber " + serialNumber + "is contained in at least two singleResponses"); } else { throw new OCSPTargetUnmatchedException( "the serialNumber specified in singleResponse[" + i + "] is not requested"); } } } } return ocspResp; } private OCSPReq buildRequest(final X509Certificate caCert, final BigInteger[] serialNumbers, final byte[] nonce, final RequestOptions requestOptions) throws OCSPRequestorException { ASN1ObjectIdentifier hashAlgId = requestOptions.getHashAlgorithmId(); List<AlgorithmIdentifier> prefSigAlgs = requestOptions.getPreferredSignatureAlgorithms(); DigestCalculator digestCalculator; if (NISTObjectIdentifiers.id_sha224.equals(hashAlgId)) { digestCalculator = new SHA224DigestCalculator(); } else if (NISTObjectIdentifiers.id_sha256.equals(hashAlgId)) { digestCalculator = new SHA256DigestCalculator(); } else if (NISTObjectIdentifiers.id_sha384.equals(hashAlgId)) { digestCalculator = new SHA384DigestCalculator(); } else if (NISTObjectIdentifiers.id_sha512.equals(hashAlgId)) { digestCalculator = new SHA512DigestCalculator(); } else { digestCalculator = new SHA1DigestCalculator(); } 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 v = new ASN1EncodableVector(); for (AlgorithmIdentifier algId : prefSigAlgs) { ASN1Sequence prefSigAlgObj = new DERSequence(algId); v.add(prefSigAlgObj); } ASN1Sequence extnValue = new DERSequence(v); Extension extn; try { extn = new Extension(id_pkix_ocsp_prefSigAlgs, false, new DEROctetString(extnValue)); } catch (IOException e) { throw new OCSPRequestorException(e.getMessage(), e); } extensions.add(extn); } if (CollectionUtil.isNotEmpty(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 e) { throw new OCSPRequestorException( "could not parse certificate " + signerCertFile + ": " + e.getMessage()); } } try { signer = getSecurityFactory().createSigner(signerType, signerConf, cert); } catch (Exception e) { throw new OCSPRequestorException("could not create signer: " + e.getMessage()); } } } ContentSigner singleSigner; try { singleSigner = signer.borrowContentSigner(); } catch (NoIdleSignerException e) { throw new OCSPRequestorException("NoIdleSignerException: " + e.getMessage()); } reqBuilder.setRequestorName(signer.getCertificateAsBCObject().getSubject()); try { return reqBuilder.build(singleSigner, signer.getCertificateChainAsBCObjects()); } finally { signer.returnContentSigner(singleSigner); } } else { return reqBuilder.build(); } } catch (OCSPException | CertificateEncodingException | IOException e) { throw new OCSPRequestorException(e.getMessage(), e); } } 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) { this.signer = null; this.signerCertFile = signerCertFile; } public String getSignerType() { return signerType; } public void setSignerType(final String signerType) { this.signer = null; this.signerType = signerType; } public SecurityFactory getSecurityFactory() { return securityFactory; } public void setSecurityFactory(final SecurityFactory securityFactory) { this.securityFactory = securityFactory; } }