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

com.itextpdf.signatures.IssuingCertificateRetriever 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.commons.bouncycastle.cert.ocsp.IBasicOCSPResp;
import com.itextpdf.signatures.logs.SignLogMessageConstant;
import com.itextpdf.signatures.validation.v1.TrustedCertificatesStore;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.CRL;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * {@link IIssuingCertificateRetriever} default implementation.
 */
public class IssuingCertificateRetriever implements IIssuingCertificateRetriever {

    private static final Logger LOGGER = LoggerFactory.getLogger(IssuingCertificateRetriever.class);

    private final TrustedCertificatesStore trustedCertificatesStore = new TrustedCertificatesStore();
    private final Map knownCertificates = new HashMap<>();

    /**
     * Creates {@link IssuingCertificateRetriever} instance.
     */
    public IssuingCertificateRetriever() {
        // Empty constructor.
    }

    /**
     * {@inheritDoc}
     *
     * @param chain {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public Certificate[] retrieveMissingCertificates(Certificate[] chain) {
        List fullChain = new ArrayList<>();
        X509Certificate signingCertificate = (X509Certificate) chain[0];
        fullChain.add(signingCertificate);

        int i = 1;
        X509Certificate lastAddedCert = signingCertificate;
        while (!CertificateUtil.isSelfSigned(lastAddedCert)) {
            // Check if there are any missing certificates with isSignedByNext
            if (i < chain.length &&
                    CertificateUtil.isIssuerCertificate(lastAddedCert, (X509Certificate) chain[i])) {
                fullChain.add(chain[i]);
                i++;
            } else {
                // Get missing certificates using AIA Extensions
                String url = CertificateUtil.getIssuerCertURL(lastAddedCert);
                Collection certificatesFromAIA = processCertificatesFromAIA(url);
                if (certificatesFromAIA == null || certificatesFromAIA.isEmpty()) {
                    // Retrieve Issuer from the certificate store
                    Certificate issuer = trustedCertificatesStore
                            .getKnownCertificate(lastAddedCert.getIssuerX500Principal().getName());
                    if (issuer == null) {
                        issuer = knownCertificates.get(lastAddedCert.getIssuerX500Principal().getName());
                        if (issuer == null) {
                            // Unable to retrieve missing certificates
                            while (i < chain.length) {
                                fullChain.add(chain[i]);
                                i++;
                            }
                            return fullChain.toArray(new Certificate[0]);
                        }
                    }
                    fullChain.add(issuer);
                } else {
                    fullChain.addAll(certificatesFromAIA);
                }
            }
            lastAddedCert = (X509Certificate) fullChain.get(fullChain.size() - 1);
        }

        return fullChain.toArray(new Certificate[0]);
    }

    /**
     * Retrieve issuer certificate for the provided certificate.
     *
     * @param certificate {@link Certificate} for which issuer certificate shall be retrieved
     *
     * @return issuer certificate. {@code null} if there is no issuer certificate, or it cannot be retrieved.
     */
    public Certificate retrieveIssuerCertificate(Certificate certificate) {
        Certificate[] certificateChain = retrieveMissingCertificates(new Certificate[]{certificate});
        if (certificateChain.length > 1) {
            return certificateChain[1];
        }
        return null;
    }

    /**
     * Retrieves OCSP responder certificate either from the response certs or
     * trusted store in case responder certificate isn't found in /Certs.
     *
     * @param ocspResp basic OCSP response to get responder certificate for
     *
     * @return retrieved OCSP responder certificate or null in case it wasn't found.
     */
    public Certificate retrieveOCSPResponderCertificate(IBasicOCSPResp ocspResp) {
        // 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 {
                if (CertificateUtil.isSignatureValid(ocspResp, cert)) {
                    return cert;
                }
            } catch (Exception ignored) {
                // Ignore.
            }
        }
        // Certificate chain is not present in the response.
        // Try to verify using trusted store 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;
        // - ..."
        try {
            for (Certificate anchor : trustedCertificatesStore.getAllTrustedCertificates()) {
                if (CertificateUtil.isSignatureValid(ocspResp, anchor)) {
                    // Certificate from the root store is considered trusted and valid by this method.
                    return anchor;
                }
            }
        } catch (Exception ignored) {
            // Ignore.
        }
        return null;
    }

    /**
     * {@inheritDoc}
     *
     * @param crl {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public Certificate[] getCrlIssuerCertificates(CRL crl) {
        // Usually CRLs are signed using CA certificate, so we don’t need to do anything extra and the revocation data
        // is already collected. However, it is possible to sign it with any other certificate.

        // IssuingDistributionPoint extension: https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5
        // Nothing special for the indirect CRLs.

        // AIA Extension
        String url = CertificateUtil.getIssuerCertURL(crl);
        List certificatesFromAIA = (List) processCertificatesFromAIA(url);
        if (certificatesFromAIA == null) {
            // Retrieve Issuer from the certificate store
            Certificate issuer = trustedCertificatesStore
                    .getKnownCertificate(((X509CRL) crl).getIssuerX500Principal().getName());
            if (issuer == null) {
                issuer = knownCertificates.get(((X509CRL) crl).getIssuerX500Principal().getName());
                if (issuer == null) {
                    // Unable to retrieve CRL issuer
                    return new Certificate[0];
                }
            }
            return retrieveMissingCertificates(new Certificate[]{issuer});
        }
        return retrieveMissingCertificates(certificatesFromAIA.toArray(new Certificate[0]));
    }

    /**
     * Sets trusted certificate list to be used as certificates trusted for any possible usage.
     * In case more specific trusted is desired to be configured
     * {@link IssuingCertificateRetriever#getTrustedCertificatesStore()} method is expected to be used.
     *
     * @param certificates certificate list to be used as certificates trusted for any possible usage.
     */
    @Override
    public void setTrustedCertificates(Collection certificates) {
        addTrustedCertificates(certificates);
    }

    /**
     * Add trusted certificates collection to trusted certificates storage.
     *
     * @param certificates certificates {@link Collection} to be added
     */
    public void addTrustedCertificates(Collection certificates) {
        trustedCertificatesStore.addGenerallyTrustedCertificates(certificates);
    }

    /**
     * Add certificates collection to known certificates storage, which is used for issuer certificates retrieval.
     *
     * @param certificates certificates {@link Collection} to be added
     */
    public void addKnownCertificates(Collection certificates) {
        for (Certificate certificate : certificates) {
            knownCertificates.put(((X509Certificate) certificate).getSubjectX500Principal().getName(), certificate);
        }
    }

    /**
     * Gets {@link TrustedCertificatesStore} to be used to provide more complex trusted certificates configuration.
     *
     * @return {@link TrustedCertificatesStore} storage
     */
    public TrustedCertificatesStore getTrustedCertificatesStore() {
        return trustedCertificatesStore;
    }

    /**
     * Check if provided certificate is present in trusted certificates storage.
     *
     * @param certificate {@link Certificate} to be checked
     *
     * @return {@code true} if certificate is present in trusted certificates storage, {@code false} otherwise
     */
    public boolean isCertificateTrusted(Certificate certificate) {
        return trustedCertificatesStore.isCertificateGenerallyTrusted(certificate);
    }

    /**
     * Get CA issuers certificates represented as {@link InputStream}.
     *
     * @param uri {@link URL} URI, which is expected to be used to get issuer certificates from. Usually
     *            CA Issuers value from Authority Information Access (AIA) certificate extension.
     *
     * @return CA issuer certificate (or chain) bytes, represented as {@link InputStream}.
     *
     * @throws IOException if an I/O error occurs.
     */
    protected InputStream getIssuerCertByURI(String uri) throws IOException {
        return SignUtils.getHttpResponse(new URL(uri));
    }

    /**
     * Parses certificates represented as byte array.
     *
     * @param certsData stream which contains one or more X509 certificates.
     *
     * @return a (possibly empty) collection of the certificates read from the given byte array.
     *
     * @throws CertificateException if parsing error occurs.
     */
    protected Collection parseCertificates(InputStream certsData) throws CertificateException {
        return SignUtils.readAllCerts(certsData, null);
    }

    private Collection processCertificatesFromAIA(String url) {
        if (url == null) {
            // We don't have any URIs to the issuer certificates in AuthorityInfoAccess extension
            return null;
        }
        try (InputStream missingCertsData = getIssuerCertByURI(url)) {
            return parseCertificates(missingCertsData);
        } catch (Exception e) {
            LOGGER.warn(SignLogMessageConstant.UNABLE_TO_PARSE_AIA_CERT);
            return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy