OCSPValidator.java
- /*
- * GovWay - A customizable API Gateway
- * https://govway.org
- *
- * Copyright (c) 2005-2025 Link.it srl (https://link.it).
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3, as published by
- * the Free Software Foundation.
- *
- * 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- package org.openspcoop2.utils.certificate.ocsp;
- import java.math.BigInteger;
- import java.security.cert.CRLReason;
- import java.security.cert.CertificateExpiredException;
- import java.security.cert.CertificateNotYetValidException;
- import java.security.cert.X509Certificate;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import org.apache.commons.lang.StringUtils;
- import org.bouncycastle.asn1.DEROctetString;
- import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
- import org.bouncycastle.asn1.ocsp.ResponderID;
- import org.bouncycastle.asn1.x500.X500Name;
- import org.bouncycastle.asn1.x509.Extension;
- import org.bouncycastle.asn1.x509.Extensions;
- import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
- import org.bouncycastle.cert.X509CertificateHolder;
- import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
- import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
- import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
- import org.bouncycastle.cert.ocsp.BasicOCSPResp;
- import org.bouncycastle.cert.ocsp.CertificateID;
- import org.bouncycastle.cert.ocsp.OCSPReq;
- import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
- import org.bouncycastle.cert.ocsp.OCSPResp;
- import org.bouncycastle.cert.ocsp.RevokedStatus;
- import org.bouncycastle.cert.ocsp.SingleResp;
- import org.bouncycastle.cert.ocsp.UnknownStatus;
- import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
- import org.bouncycastle.operator.ContentVerifierProvider;
- import org.bouncycastle.operator.DigestCalculator;
- import org.bouncycastle.operator.DigestCalculatorProvider;
- import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
- import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
- import org.openspcoop2.utils.LoggerBuffer;
- import org.openspcoop2.utils.Utilities;
- import org.openspcoop2.utils.UtilsException;
- import org.openspcoop2.utils.UtilsMultiException;
- import org.openspcoop2.utils.certificate.CRLDistributionPoint;
- import org.openspcoop2.utils.certificate.CertificateInfo;
- import org.openspcoop2.utils.certificate.ExtendedKeyUsage;
- import org.openspcoop2.utils.certificate.KeyStore;
- import org.openspcoop2.utils.date.DateManager;
- import org.openspcoop2.utils.date.DateUtils;
- import org.openspcoop2.utils.io.Base64Utilities;
- import org.openspcoop2.utils.random.RandomGenerator;
- import org.openspcoop2.utils.random.SecureRandomAlgorithm;
- import org.openspcoop2.utils.transport.TransportUtils;
- import org.openspcoop2.utils.transport.http.HttpConstants;
- import org.openspcoop2.utils.transport.http.HttpRequest;
- import org.openspcoop2.utils.transport.http.HttpRequestMethod;
- import org.openspcoop2.utils.transport.http.HttpResponse;
- import org.openspcoop2.utils.transport.http.HttpUtilities;
- import org.slf4j.Logger;
- /**
- * OCSPValidator
- *
- * @author Poli Andrea (apoli@link.it)
- * @author $Author$
- * @version $Rev$, $Date$
- */
- public class OCSPValidator {
- private OCSPValidator() {}
-
- public static CertificateStatus check(Logger log, OCSPRequestParams params) throws UtilsException {
- LoggerBuffer lb = new LoggerBuffer();
- lb.setLogDebug(log);
- lb.setLogError(log);
- return check(lb, params, null);
- }
- public static CertificateStatus check(Logger log, OCSPRequestParams params, String crlInput) throws UtilsException {
- LoggerBuffer lb = new LoggerBuffer();
- lb.setLogDebug(log);
- lb.setLogError(log);
- return check(lb, params, crlInput);
- }
- public static CertificateStatus check(LoggerBuffer log, OCSPRequestParams params) throws UtilsException {
- return check(log, params, null);
- }
- public static CertificateStatus check(LoggerBuffer log, OCSPRequestParams params, String crlInput) throws UtilsException {
-
- if(params==null) {
- throw new UtilsException("Params is null");
- }
- if(params.getCertificate()==null) {
- throw new UtilsException("Certificate not provided");
- }
- if(params.getConfig()==null) {
- throw new UtilsException("OCSP config not provided");
- }
- if(params.isSelfSigned()) {
- return CertificateStatus.SELF_SIGNED();
- }
- int indexLimit = 1000;
- int index = 0;
- OCSPRequestParams req = params;
- CertificateStatus principalStatus = null;
- while(index<indexLimit) {
- CertificateStatus status = checkEngine(log, req, crlInput);
- if(principalStatus==null) {
- principalStatus = status;
- }
-
- if(status.isREVOKED() || status.isEXPIRED() || status.isUNKNOWN()) {
- return status;
- }
- else {
- if(params.getConfig().isCertificateChainVerify()
- &&
- !req.isSelfSigned() // altrimenti sono arrivato alla ca radice
- ) {
- OCSPRequestParams newReq = OCSPRequestParams.build(log, req.getIssuerCertificate(), params.getConfigTrustStore(),
- params.getConfig(), params.getReader());
- req = newReq;
- }
- else {
- return principalStatus; // ritorno lo stato di verifica del certificato principale
- }
- }
- index++;
- }
- throw new UtilsException("Certificate chain too big");
- }
- private static CertificateStatus checkEngine(LoggerBuffer log, OCSPRequestParams params, String crlInput) throws UtilsException {
- if(params==null) {
- throw new UtilsException("Params is null");
- }
- if(params.getCertificate()==null) {
- throw new UtilsException("Certificate not provided");
- }
- if(params.getConfig()==null) {
- throw new UtilsException("OCSP config not provided");
- }
- String prefixCert = "OCSP [certificate: "+params.getCertificate().getSubjectDN()+"] ";
- CertificateInfo certificateInfo = new CertificateInfo(params.getCertificate(), "certificate");
- log.debug(prefixCert+"issuer: "+params.getCertificate().getIssuerDN());
- try {
- log.debug(prefixCert+"CAissuer: "+(certificateInfo.getAuthorityInformationAccess()!=null ? certificateInfo.getAuthorityInformationAccess().getCAIssuers() : null));
- }catch(Exception t) {
- log.debug(prefixCert+"CAissuer: read error: "+t.getMessage(),t);
- }
- try {
- log.debug(prefixCert+"OCSP: "+(certificateInfo.getAuthorityInformationAccess()!=null ? certificateInfo.getAuthorityInformationAccess().getOCSPs() : null));
- }catch(Exception t) {
- log.debug(prefixCert+"OCSP: read error: "+t.getMessage(),t);
- }
- try {
- if(certificateInfo.getCRLDistributionPoints()!=null &&
- certificateInfo.getCRLDistributionPoints().getCRLDistributionPoints()!=null &&
- !certificateInfo.getCRLDistributionPoints().getCRLDistributionPoints().isEmpty()) {
- int indexCRL = 0;
- for (CRLDistributionPoint point : certificateInfo.getCRLDistributionPoints().getCRLDistributionPoints()) {
- log.debug(prefixCert+"CRL-"+indexCRL+"-Issuer: "+point.getCRLIssuers());
- log.debug(prefixCert+"CRL-"+indexCRL+": "+point.getDistributionPointNames());
- indexCRL++;
- }
- }
- else {
- log.debug(prefixCert+"CRL: null");
- }
- }catch(Exception t) {
- log.debug(prefixCert+"CRL: read error: "+t.getMessage(),t);
- }
-
- Date date = DateManager.getDate();
- boolean isCA = false;
- try {
- isCA = certificateInfo.isCA();
- }catch(Exception e) {
- throw new UtilsException(e.getMessage(),e);
- }
-
- // controllo validità temporale
- try {
- if(isCA) {
- if(params.getConfig().isCheckCAValidity()) {
- log.debug(prefixCert+"Check validity ..."); // la faccio sempre per avere l'errore puntuale
- certificateInfo.checkValid(date);
- }
- }
- else {
- if(params.getConfig().isCheckValidity()) {
- log.debug(prefixCert+"Check validity ..."); // la faccio sempre per avere l'errore puntuale
- certificateInfo.checkValid(date);
- }
- }
- }catch(CertificateExpiredException t) {
- return CertificateStatus.EXPIRED(prefixCert+t.getMessage(), certificateInfo.getNotAfter());
- }catch(CertificateNotYetValidException t) {
- return CertificateStatus.EXPIRED(prefixCert+t.getMessage(), certificateInfo.getNotBefore());
- }catch(Exception t) {
- return CertificateStatus.EXPIRED(prefixCert+t.getMessage(), certificateInfo.getNotAfter());
- }
-
- if(certificateInfo.isSelfSigned()) {
- log.debug(prefixCert+"Self signed");
- return CertificateStatus.SELF_SIGNED();
- }
-
- if(params.getIssuerCertificate()==null) {
- if(params.getConfig().isRejectsCertificateWithoutCA()) {
- throw new UtilsException(prefixCert+"IssuerCertificate not provided");
- }
- else {
- return CertificateStatus.ISSUER_NOT_FOUND();
- }
- }
-
- if(params.getConfig().isCrl()) {
- // viene richiesta una validazione CRL alternativa al OCSP
- return checkCRLEngine(log, params, crlInput, date, prefixCert);
- }
- if (params.getResponderURIs() == null || params.getResponderURIs().isEmpty()) {
- boolean reject = true;
- if(isCA) {
- reject = params.getConfig().isRejectsCAWithoutResponderUrl();
- }
- else {
- reject = params.getConfig().isRejectsCertificateWithoutResponderUrl();
- }
- if(reject) {
- throw new UtilsException(prefixCert+"At least one OCSP responder required");
- }
- else {
- if(isCA && params.getConfig().isCrlCaCheck()) {
- return checkCRLEngine(log, params, crlInput, date, prefixCert);
- }
- else {
- return CertificateStatus.OCSP_RESPONDER_NOT_FOUND();
- }
- }
- }
- StringBuilder sbError = new StringBuilder();
-
- for (String responderURI : params.getResponderURIs()) {
- String prefix =prefixCert+ "["+responderURI+"] ";
- OCSPResponseCode responseCode = null;
- UtilsException error = null;
-
- OCSPRequestSigned ocspRequest = null;
- try {
- log.debug(prefix+"Build request ...");
- ocspRequest = buildOCSPReq(log, prefix, params);
- }catch(Exception t) {
- responseCode = OCSPResponseCode.OCSP_BUILD_REQUEST_FAILED;
- error = new UtilsException(prefixCert+"(url: "+responderURI+"): "+t.getMessage(),t);
- log.error(prefix+"costruzione richiesta fallita: "+t.getMessage(),t);
- /** gestito sotto con gli stati throw error; */
- }
-
- BasicOCSPResp ocspResp = null;
- if(ocspRequest!=null) {
- try {
- log.debug(prefix+"Invoke ocsp ...");
- OCSPResp ocspResponse = invokeOCSP(log, ocspRequest, responderURI, params);
-
- log.debug(prefix+"Analyze response ...");
- responseCode = OCSPResponseCode.toOCSPResponseCode(ocspResponse.getStatus());
-
- if(OCSPResponseCode.SUCCESSFUL.equals(responseCode)) {
- if(ocspResponse.getResponseObject()==null) {
- throw new UtilsException("OCSP response object not found");
- }
- else if (ocspResponse.getResponseObject() instanceof BasicOCSPResp) {
- ocspResp = (BasicOCSPResp) ocspResponse.getResponseObject();
- } else {
- throw new UtilsException("Invalid or unknown OCSP response");
- }
- }
-
- }catch(Exception t) {
- responseCode = OCSPResponseCode.OCSP_INVOKE_FAILED;
- error = new UtilsException(prefixCert+"(url: "+responderURI+"): "+t.getMessage(),t);
- log.error(prefix+"invocazione servizio ocsp fallita: "+t.getMessage(),t);
- /** gestito sotto con gli stati throw error; */
- }
- }
-
- try {
- if(OCSPResponseCode.SUCCESSFUL.equals(responseCode) && ocspResp!=null) {
- return analyzeResponse(log, prefix, params, date, ocspRequest, ocspResp);
- }
- else {
-
- String msgFailed = responseCode.getMessage();
- if(error!=null) {
- msgFailed = msgFailed+"; "+error.getMessage();
- }
- String exceptionMessage = "OCSP response error ("+responseCode.getCode()+" - "+responseCode.name()+"): "+msgFailed;
-
- if(params.getConfig().getResponderBreakStatus()==null ||
- params.getConfig().getResponderBreakStatus().isEmpty() ||
- params.getConfig().getResponderBreakStatus().contains(responseCode)) {
- throw new UtilsException(exceptionMessage);
- }
- else {
- // continue verso il prossimo responder url
- // salvo la prima risposta, e se dopo aver provato tutte le url non ho trovato una risposta valido ritorna la prima
- if(sbError.length()>0) {
- sbError.append("\n");
- }
- sbError.append("[").append(responderURI).append("] ");
- sbError.append(exceptionMessage);
- }
- }
- }catch(Exception t) {
- log.error(prefix+"analisi fallita: "+t.getMessage(),t);
- throw new UtilsException(prefixCert+"OCSP analysis failed (url: "+responderURI+"): "+t.getMessage(),t);
- }
- }
-
- throw new UtilsException("OCSP analysis failed.\n"+sbError.toString());
- }
- private static OCSPRequestSigned buildOCSPReq(LoggerBuffer log, String prefix, OCSPRequestParams params) throws UtilsException {
- try {
- OCSPRequestSigned ocspRequestSigned = new OCSPRequestSigned();
- DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().build();
- DigestCalculator digestCalculator = digestCalculatorProvider.get(CertificateID.HASH_SHA1);
- JcaCertificateID certificateID = new JcaCertificateID(digestCalculator, params.getIssuerCertificate(), params.getCertificate().getSerialNumber());
- // Nounce extension (evitare reply attacks)
- SecureRandomAlgorithm secureRandomAlgorithm = params.getConfig().getSecureRandomAlgorithm();
- if(secureRandomAlgorithm==null) {
- secureRandomAlgorithm = SecureRandomAlgorithm.SHA1PRNG;
- }
- RandomGenerator randomGenerator = new RandomGenerator(true, secureRandomAlgorithm);
- BigInteger nounce = BigInteger.valueOf(Math.abs(randomGenerator.nextInt()));
- DEROctetString derNounceString = new DEROctetString(nounce.toByteArray());
- Extension nounceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, derNounceString);
- Extensions extensions = new Extensions(nounceExtension);
- OCSPReqBuilder builder = new OCSPReqBuilder();
- if(params.getConfig().isNonce()) {
- log.debug(prefix+"Build nonce value '"+derNounceString+"' ...");
- builder.addRequest(certificateID, extensions); // l'estensione nonce viene aggiunta come 'Request Single Extensions' dentro 'Requestor List'
- builder.setRequestExtensions(extensions); // l'estensione nonce viene aggiunta come 'Request Extensions' nel livello principale
- }
- else {
- builder.addRequest(certificateID);
- }
- OCSPReq ocspRequest = builder.build();
- ocspRequestSigned.request = ocspRequest;
- ocspRequestSigned.certificateID = certificateID;
- ocspRequestSigned.nounce = nounce;
- return ocspRequestSigned;
- }catch(Exception t) {
- throw new UtilsException("Build OCSP Request failed: "+t.getMessage(),t);
- }
- }
- private static OCSPResp invokeOCSP(LoggerBuffer log, OCSPRequestSigned ocspRequest, String responderURI, OCSPRequestParams params) throws UtilsException {
- try {
-
- /**java.io.File f = new java.io.File("/tmp/ocsp.request");
- org.openspcoop2.utils.resources.FileSystemUtilities.writeFile(f, ocspRequest.request.getEncoded());*/
- // Per verificare serializzare response su file e usare il comando: openssl ocsp -reqin /tmp/ocsp.request -noverify -text
-
- if(!responderURI.trim().startsWith("http") && !responderURI.trim().startsWith("file")) {
- throw new UtilsException("Unsupported protocol");
- }
-
- HttpRequest req = new HttpRequest();
- req.setMethod(HttpRequestMethod.POST);
- req.setContentType(HttpConstants.CONTENT_TYPE_OCSP_REQUEST);
- req.setContent(ocspRequest.request.getEncoded());
-
- responderURI = responderURI.trim();
- if(params.getConfig().getForwardProxyUrl()!=null && StringUtils.isNotEmpty(params.getConfig().getForwardProxyUrl())) {
- String forwardProxyUrl = params.getConfig().getForwardProxyUrl();
- String remoteLocation = params.getConfig().isForwardProxyBase64() ? Base64Utilities.encodeAsString(responderURI.getBytes()) : responderURI;
- if(params.getConfig().getForwardProxyHeader()!=null && StringUtils.isNotEmpty(params.getConfig().getForwardProxyHeader())) {
- req.addHeader(params.getConfig().getForwardProxyHeader(), remoteLocation);
- }
- else if(params.getConfig().getForwardProxyQueryParameter()!=null && StringUtils.isNotEmpty(params.getConfig().getForwardProxyQueryParameter())) {
- Map<String, List<String>> queryParameters = new HashMap<>();
- TransportUtils.addParameter(queryParameters,params.getConfig().getForwardProxyQueryParameter(), remoteLocation);
- forwardProxyUrl = TransportUtils.buildUrlWithParameters(queryParameters, forwardProxyUrl, false, log.getLogDebug());
- }
- else {
- throw new UtilsException("Forward Proxy configuration error: header and query parameter not found");
- }
- req.setUrl(forwardProxyUrl);
- }
- else {
- req.setUrl(responderURI);
- }
-
- if(req.getUrl().startsWith("https")) {
- req.setHostnameVerifier(params.getConfig().isExternalResourcesHostnameVerifier());
- req.setTrustAllCerts(params.getConfig().isExternalResourcesTrustAllCerts());
- if(params.getHttpsTrustStore()!=null) {
- req.setTrustStore(params.getHttpsTrustStore().getKeystore());
- }
- if(params.getHttpsKeyStore()!=null) {
- req.setKeyStore(params.getHttpsKeyStore().getKeystore());
- req.setKeyAlias(params.getConfig().getExternalResourcesKeyAlias());
- req.setKeyPassword(params.getConfig().getExternalResourcesKeyPassword());
- }
- }
- req.setConnectTimeout(params.getConfig().getConnectTimeout());
- req.setReadTimeout(params.getConfig().getReadTimeout());
- HttpResponse res = HttpUtilities.httpInvoke(req);
- List<Integer> returnCodeValid = params.getConfig().getResponderReturnCodeOk();
- if(returnCodeValid==null) {
- returnCodeValid = new ArrayList<>();
- }
- if(returnCodeValid.isEmpty()) {
- returnCodeValid.add(200);
- }
- boolean isValid = false;
- for (Integer rt : returnCodeValid) {
- if(rt!=null && rt.intValue() == res.getResultHTTPOperation()) {
- isValid = true;
- break;
- }
- }
- byte[] response = res.getContent();
-
- /**java.io.File f = new java.io.File("/tmp/ocsp.response");
- org.openspcoop2.utils.resources.FileSystemUtilities.writeFile(f, response);*/
- // Per verificare serializzare response su file e usare il comando: openssl ocsp -respin /tmp/ocsp.response -noverify -text
- if(isValid) {
- if(response!=null && response.length>0) {
- return new OCSPResp(res.getContent());
- }
- else {
- throw new UtilsException("OCSP empty response (http code: "+res.getResultHTTPOperation()+")");
- }
- }
- else {
- String error = null;
- if(response.length<=(2048)) {
- error = Utilities.convertToPrintableText(response, 2048);
- if(error!=null && error.contains("Visualizzazione non riuscita")) {
- error = null;
- }
- }
- if(error==null) {
- error="";
- }
- else {
- error = ": "+error;
- }
- throw new UtilsException("OCSP response error (http code: "+res.getResultHTTPOperation()+")"+error);
- }
- }catch(Exception t) {
- throw new UtilsException("Invoke OCSP '"+responderURI+"' failed: "+t.getMessage(),t);
- }
- }
- private static CertificateStatus analyzeResponse(LoggerBuffer log, String prefix, OCSPRequestParams params, Date date, OCSPRequestSigned requestSigned, BasicOCSPResp basicOcspResponse)
- throws UtilsException {
- // Verifico la risposta ottenuta dal OCSP
- verifyResponse(log, prefix, params, date, requestSigned, basicOcspResponse);
-
- // Analizzo la risposta, la quale potrebbe contenere informazioni anche per altri certificati.
- SingleResp expectedResponseForCertificate = null;
- for (SingleResp resp : basicOcspResponse.getResponses()) {
- if (isEquals(requestSigned.certificateID, resp.getCertID())) {
- expectedResponseForCertificate = resp;
- break;
- }
- }
- if (expectedResponseForCertificate == null) {
- throw new UtilsException("OSPC Response does not contain info for certificate supplied in the OCSP request");
- }
- org.bouncycastle.cert.ocsp.CertificateStatus certStatus = expectedResponseForCertificate.getCertStatus();
-
- // NO! lo stato null è proprio il GOOD. GOOD è un alias a null
- /**if(certStatus==null) {
- throw new UtilsException("OSPC Response does not contain status info for certificate supplied in the OCSP request");
- }*/
- if (certStatus == org.bouncycastle.cert.ocsp.CertificateStatus.GOOD) {
- return CertificateStatus.GOOD();
- }
- else if (certStatus instanceof RevokedStatus) {
- RevokedStatus revoked = (RevokedStatus)certStatus;
- CRLReason reason = null;
- if (revoked.hasRevocationReason()) {
- reason = CRLReason.values()[revoked.getRevocationReason()];
- }
- return CertificateStatus.REVOKED(reason, revoked.getRevocationTime());
- }
- else if (certStatus instanceof UnknownStatus) {
- return CertificateStatus.UNKNOWN();
- }
- else {
- throw new UtilsException("OSPC Response contain unknown revocation status ("+certStatus+")");
- }
-
- }
- private static boolean isEquals(JcaCertificateID ocspRequestCertificateId, CertificateID ocspResponseCertificateId) {
- if (ocspRequestCertificateId == null || ocspResponseCertificateId == null)
- return false;
- if (ocspRequestCertificateId == ocspResponseCertificateId)
- return true;
- boolean serialNumberEquals = ocspRequestCertificateId.getSerialNumber()!=null && ocspResponseCertificateId.getSerialNumber()!=null && ocspRequestCertificateId.getSerialNumber().equals(ocspResponseCertificateId.getSerialNumber());
- boolean nameHashEquals = ocspRequestCertificateId.getIssuerNameHash()!=null && ocspResponseCertificateId.getIssuerNameHash()!=null && Arrays.equals(ocspRequestCertificateId.getIssuerNameHash(), ocspResponseCertificateId.getIssuerNameHash());
- boolean keyHashEquals = ocspRequestCertificateId.getIssuerKeyHash()!=null && ocspResponseCertificateId.getIssuerKeyHash()!=null && Arrays.equals(ocspRequestCertificateId.getIssuerKeyHash(), ocspResponseCertificateId.getIssuerKeyHash());
- return serialNumberEquals && nameHashEquals && keyHashEquals;
- }
- private static void verifyResponse(LoggerBuffer log, String prefix, OCSPRequestParams params, Date date, OCSPRequestSigned requestSigned, BasicOCSPResp basicOcspResponse) throws UtilsException {
- List<X509CertificateHolder> certs = new ArrayList<>(Arrays.asList(basicOcspResponse.getCerts()));
- try {
- // certificati ritornati con la risposta ocsp
- X509CertificateHolder[] ospcCerts = basicOcspResponse.getCerts();
- if(ospcCerts!=null && ospcCerts.length>0) {
- certs.addAll(Arrays.asList(ospcCerts));
- }
-
- // certificato dell'issuer
- certs.add(new JcaX509CertificateHolder(params.getIssuerCertificate()));
-
- // eventuale certificato di firma del responder OCSP autorizzato puntualmente
- if (params.getSignerCertificate() != null) {
- certs.add(new JcaX509CertificateHolder(params.getSignerCertificate()));
- }
- } catch (Exception e) {
- throw new UtilsException("OCSP Response signature unverifiable; read certs failed: "+e.getMessage(),e);
- }
-
- // Ottengo il certificato utilizzato per firmare la risposta
- X509Certificate signingCert = readSigningCertByResponderId(log, certs, basicOcspResponse);
- if (signingCert == null) {
- throw new UtilsException("OCSP Response signature unverifiable: signing cert not found");
- }
- // Valido il certificato
- verifySigningCert(log, prefix, signingCert, params, date);
-
- // Verifico la risposta rispetto al certificato
- try {
- log.debug(prefix+"Verify OCSP Response ...");
- JcaContentVerifierProviderBuilder builder = new JcaContentVerifierProviderBuilder();
- builder.setProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
- ContentVerifierProvider contentVerifier = builder.build(signingCert.getPublicKey());
- boolean valid = basicOcspResponse.isSignatureValid(contentVerifier);
- if(!valid) {
- throw new UtilsException("invalid signature");
- }
- } catch(Exception t) {
- throw new UtilsException("Verifying OCSP Response's signature failed: "+t.getMessage(),t);
- }
- // Verifico validità risposta rispetto a nonce o data
- verifyNonceOrDates(log, prefix, params, requestSigned, basicOcspResponse, date);
- }
-
- private static X509Certificate readSigningCertByResponderId(LoggerBuffer log, List<X509CertificateHolder> certs, BasicOCSPResp basicOcspResponse) throws UtilsException {
-
- if(basicOcspResponse.getResponderId()==null) {
- throw new UtilsException("OSPC Response does not contain responder id");
- }
- ResponderID responderId = basicOcspResponse.getResponderId().toASN1Primitive();
- if(responderId==null) {
- throw new UtilsException("OSPC Response does not contain responder id (asn1)");
- }
-
- List<Throwable> listThrowable = new ArrayList<>();
-
- if (certs!=null && !certs.isEmpty()) {
- X500Name responderName = responderId.getName();
- byte[] responderKey = responderId.getKeyHash();
- if (responderName != null) {
- for (X509CertificateHolder certHolder : certs) {
- try {
- JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
- converter.setProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
- X509Certificate certCheck = converter.getCertificate(certHolder);
- X500Name nameCheck = new X500Name(certCheck.getSubjectX500Principal().getName());
- if (responderName.equals(nameCheck)) {
- return certCheck;
- }
- } catch (Exception t) {
- log.debug("check (responderName) failed: "+t.getMessage(),t);
- listThrowable.add(t);
- }
- }
- } else if (responderKey != null) {
- SubjectKeyIdentifier responderSubjectKey = new SubjectKeyIdentifier(responderKey);
- for (X509CertificateHolder certHolder : certs) {
- try {
- JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
- converter.setProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
- X509Certificate certCheck = converter.getCertificate(certHolder);
-
- // verifico per subject key identifier
- SubjectKeyIdentifier subjectKeyIdentifierCheck = null;
- if (certHolder.getExtensions() != null) {
- subjectKeyIdentifierCheck = SubjectKeyIdentifier.fromExtensions(certHolder.getExtensions());
- }
- if (subjectKeyIdentifierCheck != null && responderSubjectKey.equals(subjectKeyIdentifierCheck)) {
- return certCheck;
- }
- // verifico per chiave pubblica
- subjectKeyIdentifierCheck = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(certCheck.getPublicKey());
- if (responderSubjectKey.equals(subjectKeyIdentifierCheck)) {
- return certCheck;
- }
- } catch (Exception t) {
- log.debug("check (responderKey) failed: "+t.getMessage(),t);
- listThrowable.add(t);
- }
- }
- }
- }
-
- if(!listThrowable.isEmpty()) {
- if(listThrowable.size()==1) {
- Throwable t = listThrowable.get(0);
- throw new UtilsException("OCSP Response signature unverifiable: signing cert not found; "+t.getMessage(),t);
- }
- else {
- UtilsMultiException multi = new UtilsMultiException("OCSP Response signature unverifiable: signing cert not found; multiple exception",listThrowable.toArray(new Throwable[1]));
- throw new UtilsException(multi.getMessage(),multi);
- }
- }
-
- return null;
- }
-
- private static void verifySigningCert(LoggerBuffer log, String prefix, X509Certificate signingCert, OCSPRequestParams params, Date date) throws UtilsException {
-
- // Un responder OCSP può firmare le risposte in 3 modi (https://www.rfc-editor.org/rfc/rfc6960#section-2.6):
- // 1) La risposta viene firmata utilizzando lo stesso certificato della CA che ha emesso i certificati che si controlla.
- // In questo caso, nella risposta non viene ritornato alcun certificato.
- // Questo caso non richiede altri controlli al di fuori delle normali verifiche previste nella connessione TLS o gestione della sicurezza messaggio da cui il certificato proviene.
- // 2) La risposta viene firmata utilizzando un altro certificato firmato dalla stessa CA che ha emesso i certificati che si controlla.
- // In questo caso nella risposta viene ritornato il certificato di firma utilizzato.
- // Deve essere controllato che il certificato abbia una 'Extended Key Usage Extension' impostata a 'id-kp-OCSPSigning' così che possa essere considerato affidabile per questo scopo.
- // Il controllo è fondamentale per prevenire attacchi "man in the middle" (siamo in http durante la comunicazione con OCSP)
- // dove la risposta intercettata viene alterata firmandola con un altro certificato rilasciato sempre dalla CA, ma non adibito a firmare risposte OCSP.
- // 3) La risposta viene firmata usando un altro certificato che non è in relazione con il certificato che si sta controllando.
- // Deve quindi essere verificato ulteriormente il certificato ritornato dal responder per assicurarsi che sia "trusted"
-
- if(signingCert==null) {
- throw new UtilsException("Signing certificate not found");
- }
- if (signingCert.equals(params.getIssuerCertificate())) {
- /** System.out.println("*** [Case 1] OCSP response is signed by the certificate's Issuing CA ***"); */
- // Non sono richiesti altri controlli
-
- log.debug(prefix+"[Case 1] OCSP response is signed by the certificate's Issuing CA");
-
- } else if (params.getSignerCertificate() != null && signingCert.equals(params.getSignerCertificate())) {
-
- /** System.out.println("*** [Case 3] OCSP response is signed by an authorized responder certificate manually configured***"); */
- // Essendo un certificato fornito manualmente non servono altri controlli
- // NOTA: essendo fornito puntualmente, è compito di chi lo configura assicurarsi che abbia l'extension key usage corretto o ne accetta il fatto che non le abbia
- log.debug(prefix+"[Case 3] OCSP response is signed by an authorized responder certificate manually configured: "+signingCert.getSubjectDN());
-
- } else {
- /** System.out.println("*** [Case 2 e 3] OCSP response is signed by an responder certificate readed in ocsp response ***"); */
- // Estratto da https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.2
- // - OCSP signing delegation SHALL be designated by the inclusion of id-kp-OCSPSigning in an extended key usage certificate extension included in the OCSP response signer's certificate.
- // This certificate MUST be issued directly by the CA that is identified in the request.
-
- boolean responderCertificateManuallyAuthorized = false;
- X509Certificate differentIssuerResponderCertificateCA = null;
- KeyStore differentIssuerResponderCertificateTrustStore = null;
-
- if (!signingCert.getIssuerX500Principal().equals(params.getIssuerCertificate().getSubjectX500Principal())) {
- // Case 3: La risposta viene firmata usando un altro certificato che non è in relazione con il certificato che si sta controllando.
-
- log.debug(prefix+"[Case 3] OCSP response is signed by an responder certificate readed in ocsp response (different CA '"+signingCert.getIssuerDN()+"'): "+signingCert.getSubjectDN());
-
- if(params.getSignerTrustStore()!=null) {
- X509Certificate tmp = (X509Certificate) params.getSignerTrustStore().getCertificateBySubject(signingCert.getSubjectX500Principal());
- if(tmp!=null && tmp.equals(signingCert)) {
- responderCertificateManuallyAuthorized = true; // autorizzato puntualmente il certificato
- }
- else {
- differentIssuerResponderCertificateCA = (X509Certificate) params.getSignerTrustStore().getCertificateBySubject(signingCert.getIssuerX500Principal());
- }
-
- differentIssuerResponderCertificateTrustStore = params.getSignerTrustStore();
- }
-
- if(!responderCertificateManuallyAuthorized && differentIssuerResponderCertificateCA==null) {
- throw new UtilsException("Signing certificate is not authorized to sign OCSP responses: unauthorized different issuer certificate '"+signingCert.getIssuerDN()+"'");
- }
- }
- else {
- log.debug(prefix+"[Case 2] OCSP response is signed by an responder certificate readed in ocsp response (same CA): "+signingCert.getSubjectDN());
- }
-
- // Controllo Extended Key Usage
-
- CertificateInfo certificateInfo = new CertificateInfo(signingCert, "signingCert");
- List<ExtendedKeyUsage> requiredExtendedKeyUsages = params.getConfig().getExtendedKeyUsageRequired(); // consente di disabilitare il controllo per ambienti di test
- if(requiredExtendedKeyUsages!=null && !requiredExtendedKeyUsages.isEmpty()) {
- for (ExtendedKeyUsage extendedKeyUsage : requiredExtendedKeyUsages) {
- boolean hasExtendedKeyUsage = false;
- try {
- log.debug(prefix+"Check ExtendedKeyUsage '"+extendedKeyUsage+"' ...");
- hasExtendedKeyUsage = certificateInfo.hasExtendedKeyUsage(extendedKeyUsage);
- }catch(Exception t) {
- throw new UtilsException("Signing certificate not valid for signing OCSP responses: extended key usage '"+extendedKeyUsage+"' not found; "+t.getMessage(),t);
- }
- if(!hasExtendedKeyUsage) {
- throw new UtilsException("Signing certificate not valid for signing OCSP responses: extended key usage '"+extendedKeyUsage+"' not found");
- }
- }
- }
- else {
- log.debug(prefix+"Check ExtendedKeyUsage disable");
- }
-
- // Verifica del certificato di firma
-
- // https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.2.1
-
- // Revocation Checking of an Authorized Responder
- // Since an authorized OCSP responder provides status information for one or more CAs, OCSP clients need to know how to check that an Authorized Responder's certificate has not been revoked.
- // CAs may choose to deal with this problem in one of three ways:
- //
- // 1) A CA may specify that an OCSP client can trust a responder for the lifetime of the responder's certificate.
- // The CA does so by including the extension id-pkix-ocsp-nocheck.
- // This SHOULD be a non-critical extension. The value of the extension SHALL be NULL.
- // CAs issuing such a certificate should realize that a compromise of the responder's key is as serious as the compromise of a CA key used to sign CRLs,
- // at least for the validity period of this certificate.
- // CAs may choose to issue this type of certificate with a very short lifetime and renew it frequently.
- /** Identificativo: id-pkix-ocsp-nocheck OBJECT IDENTIFIER ::= { id-pkix-ocsp 5 } */
- //
- // 2) A CA may specify how the responder's certificate is to be checked for revocation.
- // This can be done by using CRL Distribution Points if the check should be done using CRLs,
- // or by using Authority Information Access if the check should be done in some other way.
- // Details for specifying either of these two mechanisms are available in [RFC5280].
- //
- // 3) A CA may choose not to specify any method of revocation checking for the responder's certificate,
- // in which case it would be up to the OCSP client's local security policy to decide whether that certificate should be checked for revocation or not.
-
- org.openspcoop2.utils.certificate.Extensions exts = null;
- try {
- exts = certificateInfo.getExtensions();
- }catch(Exception t) {
- log.debug("Extension read failed: "+t.getMessage(),t);
- }
- boolean ocspNoCheck = exts!=null && exts.hasExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck);
-
- log.debug(prefix+"ocsp_nocheck:"+ocspNoCheck);
-
- // Caso 1 e 3 vengono gestiti in ugual maniera
- // Il caso 2 viene gestito con CRL, mentre non si gestisce un eventuale loop verso un altro servizio OCSP
-
- CRLParams crlParams = null;
- if(!ocspNoCheck &&
- params.getConfig().isCrlSigningCertCheck()) {
- log.debug(prefix+"(SigningCert:"+signingCert.getSubjectDN()+") Build CRL params...");
- KeyStore trustStoreConfig = differentIssuerResponderCertificateTrustStore!=null ? differentIssuerResponderCertificateTrustStore : params.getIssuerTrustStore();
- try {
- crlParams = CRLParams.build(log, signingCert, null, trustStoreConfig, params.getConfig(), params.getReader());
- }catch(Exception t) {
- throw new UtilsException(t.getMessage(),t);
- }
- }
-
- if(crlParams==null || crlParams.getCrlCertstore()==null) { // altrimenti la validita' viene verificata insieme alle CRL)
- // Controllo che non sia scaduto
- try {
- log.debug(prefix+" (SigningCert:"+signingCert.getSubjectDN()+") Check valid...");
- certificateInfo.checkValid(date);
- }catch(CertificateNotYetValidException t) {
- throw new UtilsException("Signing certificate not yet valid: "+t.getMessage(),t);
- }catch(CertificateExpiredException t) {
- throw new UtilsException("Signing certificate expired: "+t.getMessage(),t);
- }catch(Exception t) {
- throw new UtilsException("Signing certificate not valid: "+t.getMessage(),t);
- }
- }
-
- String ca = "n.d.";
- try {
- if(responderCertificateManuallyAuthorized) {
- // non devo verificarlo, è stato inserito nel truststore dedicato il certificato del responder puntualmente
- }
- else if(differentIssuerResponderCertificateCA!=null) {
- if(differentIssuerResponderCertificateCA.getSubjectDN()!=null) {
- ca = differentIssuerResponderCertificateCA.getSubjectDN().toString();
- }
- log.debug(prefix+"(SigningCert:"+signingCert.getSubjectDN()+") verify against ca '"+ca+"'...");
- certificateInfo.verify(differentIssuerResponderCertificateCA);
- }
- else {
- if(params.getIssuerCertificate().getSubjectDN()!=null) {
- ca = params.getIssuerCertificate().getSubjectDN().toString();
- }
- log.debug(prefix+"(SigningCert:"+signingCert.getSubjectDN()+") verify against ca '"+ca+"'...");
- certificateInfo.verify(params.getIssuerCertificate());
- }
- } catch (Exception t) {
- throw new UtilsException("Signing certificate not valid (CA: "+ca+"): "+t.getMessage(),t);
- }
-
- if(crlParams!=null && crlParams.getCrlCertstore()!=null) {
- try {
- log.debug(prefix+"(SigningCert:"+signingCert.getSubjectDN()+") CRL check...");
- certificateInfo.checkValid(crlParams.getCrlCertstore(), crlParams.getCrlTrustStore(), date);
- }catch(Exception t) {
- throw new UtilsException("Signing certificate not valid (CRL): "+t.getMessage(),t);
- }
- }
- }
-
- }
-
- private static void verifyNonceOrDates(LoggerBuffer log, String prefix, OCSPRequestParams params, OCSPRequestSigned requestSigned, BasicOCSPResp basicOcspResponse, Date date) throws UtilsException {
- // https://www.rfc-editor.org/rfc/rfc6960#section-4.4.1
-
- // The nonce cryptographically binds a request and a response to prevent replay attacks.
- // The nonce is included as one of the requestExtensions in requests, while in responses it would be included as one of the responseExtensions.
- // In both the request and the response, the nonce will be identified by the object identifier id-pkix-ocsp-nonce, while the extnValue is the value of the nonce.
- byte[] requestNonce = requestSigned.nounce!=null ? requestSigned.nounce.toByteArray() : null;
- Extension responseNonce = basicOcspResponse.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
- if (responseNonce != null && requestNonce != null) {
- log.debug(prefix+"Verify nonce request-response...");
- if(!Arrays.equals(requestNonce, responseNonce.getExtnValue().getOctets())) {
- throw new UtilsException("OCSP Response not valid: nonces do not match");
- }
- }
- else {
- log.debug(prefix+"Verify nonce dates...");
-
- // https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.1
-
- // Responses can contain four times -- thisUpdate, nextUpdate, producedAt, and revocationTime.
- // The semantics of these fields are defined in Section 2.4. The format for GeneralizedTime is as specified in Section 4.1.2.5.2 of [RFC5280].
- // The thisUpdate and nextUpdate fields define a recommended validity interval. This interval corresponds to the {thisUpdate, nextUpdate} interval in CRLs.
- // Responses whose nextUpdate value is earlier than the local system time value SHOULD be considered unreliable.
- // Responses whose thisUpdate time is later than the local system time SHOULD be considered unreliable.
- // If nextUpdate is not set, the responder is indicating that newer revocation information is available all the time.
-
- // The semantics of these fields are:
- // thisUpdate The most recent time at which the status being indicated is known by the responder to have been correct.
- // nextUpdate The time at or before which newer information will be available about the status of the certificate.
- // producedAt The time at which the OCSP responder signed this response.
- // revocationTime The time at which the certificate was revoked or placed on hold.
-
- long current = date.getTime();
- int toleranceMilliseconds = params.getConfig().getResponseCheckDateToleranceMilliseconds();
- Date rightInterval = new Date(current + toleranceMilliseconds);
- Date leftInterval = new Date(current - toleranceMilliseconds);
- SingleResp[] resp = basicOcspResponse.getResponses();
- if(resp!=null && resp.length>0) {
- // una risposta ci deve essere, se non c'è è già stata sollevata una eccezione in precedenza
- for (SingleResp singleResp : resp) {
-
- // Responses whose thisUpdate time is later than the local system time SHOULD be considered unreliable.
- if(rightInterval.before(singleResp.getThisUpdate())){
- throw new UtilsException("OCSP Response is unreliable: this update time '"+DateUtils.getSimpleDateFormatMs().format(singleResp.getThisUpdate())+"' is later than the local system time");
- }
-
- // Responses whose nextUpdate value is earlier than the local system time value SHOULD be considered unreliable.
- // If nextUpdate is not set, the responder is indicating that newer revocation information is available all the time.
- Date dateCheck = singleResp.getNextUpdate() != null ? singleResp.getNextUpdate() : singleResp.getThisUpdate();
- if(leftInterval.after(dateCheck)) {
- throw new UtilsException("OCSP Response is unreliable: next update time '"+DateUtils.getSimpleDateFormatMs().format(dateCheck)+"' is earlier than the local system time");
- }
- }
- }
- }
-
- }
-
- private static CertificateStatus checkCRLEngine(LoggerBuffer log, OCSPRequestParams params, String crlInput, Date date, String prefix) throws UtilsException {
-
- log.debug(prefix+"Build CRL request ...");
-
- CRLParams crlParams = null;
- try {
- crlParams = CRLParams.build(log, params.getCertificate(), crlInput, params.getIssuerTrustStore(), params.getConfig(), params.getReader());
- }catch(Exception t) {
- throw new UtilsException(t.getMessage(),t);
- }
-
- CertificateInfo certificateInfo = new CertificateInfo(params.getCertificate(), "certificateCrlCheck");
-
- if(!certificateInfo.isSelfSigned()) {
- log.debug(prefix+"Verify against CA ...");
-
- String ca = "n.d.";
- try {
- if(params.getIssuerCertificate().getSubjectDN()!=null) {
- ca = params.getIssuerCertificate().getSubjectDN().toString();
- }
- certificateInfo.verify(params.getIssuerCertificate());
- } catch (Exception t) {
- CertificateStatus cs = CertificateStatus.REVOKED(CRLReason.UNSPECIFIED, null);
- String eMessage = t.getMessage();
- String msgError = "Certificate not valid (CA: "+ca+"): "+eMessage;
- log.error(msgError, t);
- cs.setDetails(msgError);
- return cs;
- }
- }
- else {
- log.debug(prefix+"Certificate self-signed");
- }
-
- CertificateStatus status = CertificateStatus.GOOD();
-
- try {
- if(crlParams.getCrlCertstore()!=null) {
-
- log.debug(prefix+"Verify CRL ...");
-
- certificateInfo.checkValid(crlParams.getCrlCertstore(), crlParams.getCrlTrustStore(), date);
- }
- else {
- log.debug(prefix+"CRL undefined");
-
- status = CertificateStatus.CRL_NOT_FOUND();
- }
- }catch(Exception t) {
- CertificateStatus cs = CertificateStatus.REVOKED(CRLReason.UNSPECIFIED, null);
- String eMessage = t.getMessage();
- if(Utilities.existsInnerException(t, java.security.cert.CertificateExpiredException.class)) {
- Throwable inner = Utilities.getInnerException(t, java.security.cert.CertificateExpiredException.class);
- if(inner!=null && inner.getMessage()!=null) {
- eMessage = inner.getMessage();
- }
- }
- else if (Utilities.existsInnerException(t, java.security.cert.CertificateRevokedException.class)) {
- Throwable inner = Utilities.getInnerException(t, java.security.cert.CertificateRevokedException.class);
- if(inner instanceof java.security.cert.CertificateRevokedException) {
- java.security.cert.CertificateRevokedException cre = (java.security.cert.CertificateRevokedException) inner;
- if(cre.getRevocationReason()!=null) {
- cs = CertificateStatus.REVOKED(cre.getRevocationReason(), cre.getRevocationDate());
- }
- }
- }
- String msgError = "Certificate not valid (CRL): "+eMessage;
- log.error(msgError, t);
- cs.setDetails(msgError);
- return cs;
- }
-
- return status;
- }
- }
- class OCSPRequestSigned {
- OCSPReq request;
- BigInteger nounce;
- JcaCertificateID certificateID;
- }