All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itextpdf.signatures.OCSPVerifier Maven / Gradle / Ivy

There is a newer version: 9.0.0
Show newest version
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2024 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    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 .
 */
package com.itextpdf.signatures;

import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
import com.itextpdf.commons.bouncycastle.cert.ocsp.AbstractOCSPException;
import com.itextpdf.commons.bouncycastle.cert.ocsp.IBasicOCSPResp;
import com.itextpdf.commons.bouncycastle.cert.ocsp.ICertificateStatus;
import com.itextpdf.commons.bouncycastle.cert.ocsp.IRevokedStatus;
import com.itextpdf.commons.bouncycastle.cert.ocsp.ISingleResp;
import com.itextpdf.commons.bouncycastle.operator.AbstractOperatorCreationException;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.signatures.logs.SignLogMessageConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.CRL;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * Class that allows you to verify a certificate against
 * one or more OCSP responses.
 *
 * @deprecated starting from 8.0.5.
 * {@link com.itextpdf.signatures.validation.v1.OCSPValidator} should be used instead.
 */
@Deprecated
public class OCSPVerifier extends RootStoreVerifier {

    private static final IBouncyCastleFactory BOUNCY_CASTLE_FACTORY = BouncyCastleFactoryCreator.getFactory();

    /**
     * The Logger instance
     */
    protected static final Logger LOGGER = LoggerFactory.getLogger(OCSPVerifier.class);

    protected final static String id_kp_OCSPSigning = "1.3.6.1.5.5.7.3.9";

    /**
     * The list of {@link IBasicOCSPResp} OCSP response wrappers.
     */
    protected List ocsps;

    /**
     * Ocsp client to check OCSP Authorized Responder's revocation data.
     */
    private IOcspClient ocspClient;
    /**
     * Ocsp client to check OCSP Authorized Responder's revocation data.
     */
    private ICrlClient crlClient;

    /**
     * Creates an OCSPVerifier instance.
     *
     * @param verifier the next verifier in the chain
     * @param ocsps    a list of {@link IBasicOCSPResp} OCSP response wrappers for the certificate verification
     */
    public OCSPVerifier(CertificateVerifier verifier, List ocsps) {
        super(verifier);
        this.ocsps = ocsps;
    }

    /**
     * Sets OCSP client to provide OCSP responses for verifying of the OCSP signer's certificate (an Authorized
     * Responder). Also, should be used in case responder's certificate doesn't have any method of revocation checking.
     *
     * 

* See RFC6960 4.2.2.2.1. Revocation Checking of an Authorized Responder. * *

* Optional. Default one is {@link OcspClientBouncyCastle}. * * @param ocspClient {@link IOcspClient} to provide an Authorized Responder revocation data. */ public void setOcspClient(IOcspClient ocspClient) { this.ocspClient = ocspClient; } /** * Sets CRL client to provide CRL responses for verifying of the OCSP signer's certificate (an Authorized Responder) * that also should be used in case responder's certificate doesn't have any method of revocation checking. * *

* See RFC6960 4.2.2.2.1. Revocation Checking of an Authorized Responder. * *

* Optional. Default one is {@link CrlClientOnline}. * * @param crlClient {@link ICrlClient} to provide an Authorized Responder revocation data. */ public void setCrlClient(ICrlClient crlClient) { this.crlClient = crlClient; } /** * Verifies if a valid OCSP response is found for the certificate. * If this method returns false, it doesn't mean the certificate isn't valid. * It means we couldn't verify it against any OCSP response that was available. * * @param signCert the certificate that needs to be checked * @param issuerCert issuer of the certificate to be checked * @param signDate the date the certificate needs to be valid * * @return a list of VerificationOK objects. * The list will be empty if the certificate couldn't be verified. * * @see com.itextpdf.signatures.RootStoreVerifier#verify(java.security.cert.X509Certificate, * java.security.cert.X509Certificate, java.util.Date) */ public List verify(X509Certificate signCert, X509Certificate issuerCert, Date signDate) throws GeneralSecurityException { List result = new ArrayList<>(); int validOCSPsFound = 0; // First check in the list of OCSP responses that was provided. if (ocsps != null) { for (IBasicOCSPResp ocspResp : ocsps) { if (verify(ocspResp, signCert, issuerCert, signDate)) { validOCSPsFound++; } } } // Then check online if allowed. boolean online = false; int validOCSPsFoundOnline = 0; if (onlineCheckingAllowed && verify(getOcspResponse(signCert, issuerCert), signCert, issuerCert, signDate)) { validOCSPsFound++; validOCSPsFoundOnline++; online = true; } // Show how many valid OCSP responses were found. LOGGER.info("Valid OCSPs found: " + validOCSPsFound); if (validOCSPsFound > 0) { result.add(new VerificationOK(signCert, this.getClass(), "Valid OCSPs Found: " + validOCSPsFound + (online ? (" (" + validOCSPsFoundOnline + " online)") : ""))); } // Verify using the previous verifier in the chain (if any). if (verifier != null) { result.addAll(verifier.verify(signCert, issuerCert, signDate)); } return result; } /** * Verifies a certificate against a single OCSP response. * * @param ocspResp {@link IBasicOCSPResp} the OCSP response wrapper for a certificate verification * @param signCert the certificate that needs to be checked * @param issuerCert the certificate that issued signCert – immediate parent. This certificate is considered * trusted and valid by this method. * @param signDate sign date (or the date the certificate needs to be valid) * * @return {@code true} in case check is successful, false otherwise. * * @throws GeneralSecurityException if OCSP response verification cannot be done or failed. */ public boolean verify(IBasicOCSPResp ocspResp, X509Certificate signCert, X509Certificate issuerCert, Date signDate) throws GeneralSecurityException { if (ocspResp == null) { return false; } // Getting the responses. ISingleResp[] resp = ocspResp.getResponses(); for (ISingleResp iSingleResp : resp) { // SingleResp contains the basic information of the status of the certificate identified by the certID. // Check if the serial numbers of the signCert and certID corresponds: if (!signCert.getSerialNumber().equals(iSingleResp.getCertID().getSerialNumber())) { continue; } // Check if the issuer of the certID and signCert matches, i.e. check that issuerNameHash and issuerKeyHash // fields of the certID is the hash of the issuer's name and public key: try { if (issuerCert == null) { issuerCert = signCert; } if (!SignUtils.checkIfIssuersMatch(iSingleResp.getCertID(), issuerCert)) { LOGGER.info("OCSP: Issuers doesn't match."); continue; } } catch (IOException e) { throw new GeneralSecurityException(e.getMessage()); } catch (AbstractOCSPException | AbstractOperatorCreationException e) { continue; } // So, since the issuer name and serial number identify a unique certificate, we found the single response // for the signCert. // OCSP response can be created after the signing, so we won't compare signDate with thisUpdate. // If nextUpdate is not set, the responder is indicating that newer revocation information // is available all the time. if (iSingleResp.getNextUpdate() != null && signDate.after(iSingleResp.getNextUpdate())) { LOGGER.info(MessageFormatUtil.format("OCSP is no longer valid: {0} after {1}", signDate, iSingleResp.getNextUpdate())); continue; } // Check the status of the certificate: ICertificateStatus status = iSingleResp.getCertStatus(); IRevokedStatus revokedStatus = BOUNCY_CASTLE_FACTORY.createRevokedStatus(status); boolean isStatusGood = BOUNCY_CASTLE_FACTORY.createCertificateStatus().getGood().equals(status); if (isStatusGood || (revokedStatus != null && signDate.before(revokedStatus.getRevocationTime()))) { // Check if the OCSP response was genuine. isValidResponse(ocspResp, issuerCert, signDate); if (!isStatusGood) { LOGGER.warn(MessageFormatUtil.format(SignLogMessageConstant.VALID_CERTIFICATE_IS_REVOKED, revokedStatus.getRevocationTime())); } return true; } } return false; } /** * Verifies if an OCSP response is genuine. * If it doesn't verify against the issuer certificate and response's certificates, it may verify * using a trusted anchor or cert. * * @param ocspResp {@link IBasicOCSPResp} the OCSP response wrapper * @param issuerCert the issuer certificate. This certificate is considered trusted and valid by this method. * @param signDate sign date for backwards compatibility * * @throws GeneralSecurityException if OCSP response verification cannot be done or failed. */ public void isValidResponse(IBasicOCSPResp ocspResp, X509Certificate issuerCert, Date signDate) throws GeneralSecurityException { // OCSP response might be signed by the issuer certificate or // the Authorized OCSP responder certificate containing the id-kp-OCSPSigning extended key usage extension. X509Certificate responderCert = null; // First check if the issuer certificate signed the response since it is expected to be the most common case: if (isSignatureValid(ocspResp, issuerCert)) { responderCert = issuerCert; } // If the issuer certificate didn't sign the ocsp response, look for authorized ocsp responses // from the properties or from the certificate chain received with response. if (responderCert == null) { if (ocspResp.getCerts().length > 0) { // Look for the existence of an Authorized OCSP responder inside the cert chain in the ocsp response. Iterable certs = SignUtils.getCertsFromOcspResponse(ocspResp); for (X509Certificate cert : certs) { try { List keyPurposes = cert.getExtendedKeyUsage(); if (keyPurposes != null && keyPurposes.contains(id_kp_OCSPSigning) && isSignatureValid(ocspResp, cert)) { responderCert = cert; break; } } catch (CertificateParsingException ignored) { } } // Certificate signing the ocsp response is not found in ocsp response's certificate chain received // and is not signed by the issuer certificate. // RFC 6960 4.2.1. ASN.1 Specification of the OCSP Response: "The responder MAY include certificates in // the certs field of BasicOCSPResponse that help the OCSP client verify the responder's signature. // If no certificates are included, then certs SHOULD be absent". if (responderCert == null) { throw new VerificationException(issuerCert, "OCSP response could not be verified"); } // RFC 6960 4.2.2.2. Authorized Responders: // "Systems relying on OCSP responses MUST recognize a delegation certificate as being issued // by the CA that issued the certificate in question only if the delegation certificate and the // certificate being checked for revocation were signed by the same key." // and "This certificate MUST be issued directly by the CA that is identified in the request". responderCert.verify(issuerCert.getPublicKey()); // Check if the lifetime of the certificate is valid. Responder cert could be created after the signing. responderCert.checkValidity(ocspResp.getProducedAt()); // Validating ocsp signer's certificate (responderCert). // See RFC6960 4.2.2.2.1. Revocation Checking of an Authorized Responder. // 1. Check if responders certificate has id-pkix-ocsp-nocheck extension, in which case we do not // validate (perform revocation check on) ocsp certs for the lifetime of the responder certificate. if (SignUtils.getExtensionValueByOid(responderCert, BOUNCY_CASTLE_FACTORY.createOCSPObjectIdentifiers() .getIdPkixOcspNoCheck().getId()) != null) { return; } // 2.1. Try to check responderCert for revocation using provided responder OCSP/CRL clients. if (ocspClient != null) { IBasicOCSPResp responderOcspResp = null; byte[] basicOcspRespBytes = ocspClient.getEncoded(responderCert, issuerCert, null); if (basicOcspRespBytes != null) { try { responderOcspResp = BOUNCY_CASTLE_FACTORY.createBasicOCSPResp( BOUNCY_CASTLE_FACTORY.createBasicOCSPResponse(BOUNCY_CASTLE_FACTORY.createASN1Primitive( basicOcspRespBytes))); } catch (IOException ignored) { } } if (verifyOcsp(responderOcspResp, responderCert, issuerCert, ocspResp.getProducedAt())) { return; } } if (crlClient != null && checkCrlResponses(crlClient, responderCert, issuerCert, ocspResp.getProducedAt())) { return; } // 2.2. Try to check responderCert for revocation using Authority Information Access for OCSP responses // or CRL Distribution Points for CRL responses using default clients. IBasicOCSPResp responderOcspResp = new OcspClientBouncyCastle() .getBasicOCSPResp(responderCert, issuerCert, null); if (verifyOcsp(responderOcspResp, responderCert, issuerCert, ocspResp.getProducedAt())) { return; } if (checkCrlResponses(new CrlClientOnline(), responderCert, issuerCert, ocspResp.getProducedAt())) { return; } // 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". throw new VerificationException(responderCert, "Authorized OCSP responder certificate revocation status cannot be checked"); } else { // Certificate chain is not present in the response received. // Try to verify using rootStore according to RFC 6960 2.2. Response: // "The key used to sign the response MUST belong to one of the following: // - ... // - a Trusted Responder whose public key is trusted by the requester; // - ..." if (rootStore != null) { try { for (X509Certificate anchor : SignUtils.getCertificates(rootStore)) { if (isSignatureValid(ocspResp, anchor)) { // Certificate from the root store is considered trusted and valid by this method. responderCert = anchor; break; } } } catch (Exception ignored) { // Ignore. } } if (responderCert == null) { throw new VerificationException(issuerCert, "OCSP response could not be verified: it does not contain certificate chain and response is not signed by issuer certificate or any from the root store."); } } } // Check if the lifetime of the certificate is valid. responderCert.checkValidity(ocspResp.getProducedAt()); } /** * Checks if an OCSP response is genuine. * * @param ocspResp {@link IBasicOCSPResp} the OCSP response wrapper * @param responderCert the responder certificate * * @return true if the OCSP response verifies against the responder certificate. */ public boolean isSignatureValid(IBasicOCSPResp ocspResp, Certificate responderCert) { try { return SignUtils.isSignatureValid(ocspResp, responderCert, BOUNCY_CASTLE_FACTORY.getProviderName()); } catch (Exception e) { return false; } } /** * Gets an OCSP response online and returns it without further checking. * * @param signCert the signing certificate * @param issuerCert the issuer certificate * * @return {@link IBasicOCSPResp} an OCSP response wrapper. */ public IBasicOCSPResp getOcspResponse(X509Certificate signCert, X509Certificate issuerCert) { if (signCert == null && issuerCert == null) { return null; } OcspClientBouncyCastle ocsp = new OcspClientBouncyCastle(); return ocsp.getBasicOCSPResp(signCert, issuerCert, null); } private boolean verifyOcsp(IBasicOCSPResp ocspResp, X509Certificate certificate, X509Certificate issuerCert, Date signDate) throws GeneralSecurityException { if (ocspResp == null) { // Unable to verify. return false; } return this.verify(ocspResp, certificate, issuerCert, signDate); } private boolean checkCrlResponses(ICrlClient client, X509Certificate responderCert, X509Certificate issuerCert, Date signDate) throws GeneralSecurityException { Collection crlBytesCollection = client.getEncoded(responderCert, null); for (byte[] crlBytes : crlBytesCollection) { CRL crl = SignUtils.parseCrlFromStream(new ByteArrayInputStream(crlBytes)); if (verifyCrl(crl, responderCert, issuerCert, signDate)) { return true; } } return false; } private boolean verifyCrl(CRL crl, X509Certificate certificate, X509Certificate issuerCert, Date signDate) throws GeneralSecurityException { if (crl instanceof X509CRL) { CRLVerifier crlVerifier = new CRLVerifier(null, null); crlVerifier.setRootStore(rootStore); crlVerifier.setOnlineCheckingAllowed(onlineCheckingAllowed); return crlVerifier.verify((X509CRL) crl, certificate, issuerCert, signDate); } // Unable to verify. return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy