/* WolfCryptPKIXCertPathValidator.java * * Copyright (C) 2006-2025 wolfSSL Inc. * * This file is part of wolfSSL. * * wolfSSL is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * wolfSSL 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ package com.wolfssl.provider.jce; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Collection; import java.util.ArrayList; import java.security.InvalidAlgorithmParameterException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.cert.TrustAnchor; import java.security.cert.CertPathValidatorSpi; import java.security.cert.CertPathValidatorResult; import java.security.cert.CertPath; import java.security.cert.CertPathChecker; import java.security.cert.CertPathParameters; import java.security.cert.CertPathValidatorException; import java.security.cert.CertSelector; import java.security.cert.CertStore; import java.security.cert.CertStoreException; import java.security.cert.X509CertSelector; import java.security.cert.PKIXParameters; import java.security.cert.PKIXCertPathChecker; import java.security.cert.PKIXCertPathValidatorResult; import java.security.cert.CRL; import java.security.cert.X509CRL; import java.security.cert.X509CRLSelector; import javax.security.auth.x500.X500Principal; import com.wolfssl.wolfcrypt.Fips; import com.wolfssl.wolfcrypt.WolfCrypt; import com.wolfssl.wolfcrypt.WolfSSLCertManager; import com.wolfssl.wolfcrypt.WolfCryptException; /** * wolfJCE implementation of CertPathValidator for PKIX (X.509) * * This implementation supports most of CertPathValidator, but not the * following items. If needed, please contact support@wolfssl.com * with details of required support. * * A. Certificate policies, and this related setters/getters. As such, * validation will not return PolicyNode in CertPathValidatorResult * B. Overriding current date for validation (PKIXParameters.setDate()) * C. getRevocationChecker() throws UnsupportedOperationException. * Internal revocation is done with CRL if * PKIXParameters.isRevocationEnabled() is true and appropriate CRLs * have been loaded into CertStore Set */ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi { /** * Create new WolfCryptPKIXCertPathValidator object. */ public WolfCryptPKIXCertPathValidator() { log("created new WolfCryptPKIXCertPathValidator"); } /** * Check CertPathParameters matches our requirements. * 1. Not null * 2. Is an instance of PKIXParameters * * @throws InvalidAlgorithmParameterException if null or not an instance * of PKIXParameters */ private void sanitizeCertPathParameters(CertPathParameters params) throws InvalidAlgorithmParameterException { log("sanitizing CertPathParameters"); if (params == null) { throw new InvalidAlgorithmParameterException( "CertPathParameters is null"); } /* Check params is of type PKIXParameters */ if (!(params instanceof PKIXParameters)) { throw new InvalidAlgorithmParameterException( "params not of type PKIXParameters"); } } /** * Check CertPath matches our requirements. * 1. CertPath.getType() is "X.509" * 2. CertPath.getEncoding() contains "PkiPath" * * @throws InvalidAlgorithmParametersException if type is not X.509 * @throws CertPathValidatorException if PkiPath encoding is not supported */ private void sanitizeCertPath(CertPath path) throws InvalidAlgorithmParameterException, CertPathValidatorException { boolean pkiPathEncodingSupported = false; Iterator supportedCertEncodings = null; log("sanitizing CertPath"); /* Verify CertPath type is X.509 */ if (!path.getType().equals("X.509")) { throw new InvalidAlgorithmParameterException( "PKIX CertPathValidator only supports X.509"); } /* Check that PkiPath encoding is supported, which is an * ASN.1 DER encoded sequence of the cert */ supportedCertEncodings = path.getEncodings(); while (supportedCertEncodings.hasNext()) { if (supportedCertEncodings.next().equals("PkiPath")) { pkiPathEncodingSupported = true; } } if (!pkiPathEncodingSupported) { throw new CertPathValidatorException( "PkiPath CertPath encoding not supported but required"); } } private void checkTargetCertConstraints(X509Certificate cert, int certIdx, CertPath path, PKIXParameters params) throws CertPathValidatorException { CertSelector selector = null; X509CertSelector x509Selector = null; if (cert == null || params == null) { throw new CertPathValidatorException( "X509Certificate in chain or PKIXParameters is null"); } /* Only check leaf/peer certificate against constraints */ if (certIdx != 0) { return; } /* Use CertSelector to check target cert */ selector = params.getTargetCertConstraints(); if (selector != null) { log("checking target cert constraints against CertSelector"); if (!(selector instanceof X509CertSelector)) { throw new CertPathValidatorException( "CertSelector not of type X509CertSelector"); } x509Selector = (X509CertSelector)selector; if (!x509Selector.match(cert)) { throw new CertPathValidatorException( "Target certificate did not pass CertConstraints check"); } } else { log("no cert constraints in params, not checking CertSelector"); } } private void disallowCertPolicyUse(PKIXParameters params) throws CertPathValidatorException { if (params == null) { throw new CertPathValidatorException( "PKIXParameters is null when checking for cert policies"); } if (!params.getInitialPolicies().isEmpty()) { throw new CertPathValidatorException( "Certificate policies not supported by wolfJCE " + "CertPathValidator, PKIXParameters.getInitialPolicies() is " + "not empty"); } /* Ignored, but log for debugging */ log("PKIXParameters.getPolicyQualifiersRejected(): " + params.getPolicyQualifiersRejected()); log("PKIXParameters.isPolicyMappingInhibited(): " + params.isPolicyMappingInhibited()); /* Should the any policy OID be processed if it is included in * a certificate? Default is false, don't allow enablement since * not supported here yet */ if (params.isAnyPolicyInhibited()) { throw new CertPathValidatorException( "Certificate policies not supported by wolfJCE " + "CertPathValidator. PKIXParameters.setAnyPolicyInhibited() " + "must be set to false (default)"); } /* If true an acceptable policy needs to be explicitly identified in * every certificate. Default is false, don't allow enablement since * not supported here yet */ if (params.isExplicitPolicyRequired()) { throw new CertPathValidatorException( "Certificate policies not supported by wolfJCE " + "CertPathValidator. PKIXParameters.setExplicitPolicy" + "Required() must be set to false (default)"); } } /** * Check X509Certificate against constraints or settings inside * PKIXParameters. * * @param cert certificate to check * @param certIdx index of certificate, used when throwing exception * @param path CertPath used when throwing exception * @param params parameters used to get constraints from * * @throws CertPathValidatorException if checks on certificate fail */ private void sanitizeX509Certificate(X509Certificate cert, int certIdx, CertPath path, PKIXParameters params) throws CertPathValidatorException { if (cert == null || params == null) { throw new CertPathValidatorException( "X509Certificate in chain or PKIXParameters is null"); } /* Check target cert constraints, if set in parameters */ checkTargetCertConstraints(cert, certIdx, path, params); /* Certificate policies are not currently supported by this * CertPathValidator implementation, throw exceptions when * user tries to use them. */ disallowCertPolicyUse(params); } /** * Call all PKIXCertPathCheckers that have been registered into * PKIXParameters. This allows users to do additional verification * steps on certificates if needed. * * @param cert certificate to be checked * @param params parameters from which to get PKIXCertPathChecker list * * @throws CertPathValidatorException if a checker fails validation on * the given Certificate */ private void callCertPathCheckers(X509Certificate cert, PKIXParameters params) throws CertPathValidatorException { int i = 0; List pathCheckers = null; if (cert == null || params == null) { throw new CertPathValidatorException( "X509Certificate in chain or PKIXParameters is null"); } pathCheckers = params.getCertPathCheckers(); if (pathCheckers == null) { /* Spec says this cannot be null */ throw new CertPathValidatorException( "PKIXParameters.getCertPathCheckers() should not return null"); } if (pathCheckers.isEmpty()) { return; } for (i = 0; i < pathCheckers.size(); i++) { log("calling CertPathChecker: " + pathCheckers.get(i)); /* Throws CertPathValidatorException on error */ pathCheckers.get(i).check((Certificate)cert); } } /** * Load TrustAnchors from PKIXParameters into WolfSSLCertManager as * trusted CA certificates. * * @param params PKIXParameters from which to get TrustAnchor Set * @param cm WolfSSLCertManager to load TrustAnchors into as trusted roots * * @throws CertPathValidatorException on failure to load trust anchors */ private void loadTrustAnchorsIntoCertManager( PKIXParameters params, WolfSSLCertManager cm) throws CertPathValidatorException { Set trustAnchors = null; Iterator trustIterator = null; log("loading TrustAnchors into native WolfSSLCertManager"); if (params == null || cm == null) { throw new CertPathValidatorException( "PKIXParameters or WolfSSLCertManager are null when loading " + "TrustAnchors"); } /* Load trust anchors into CertManager from PKIXParameters */ trustAnchors = params.getTrustAnchors(); if (trustAnchors == null || trustAnchors.isEmpty()) { throw new CertPathValidatorException( "No TrustAnchors in PKIXParameters"); } /* Iterate through TrustAnchors, load as CAs into CertManager */ trustIterator = trustAnchors.iterator(); while (trustIterator.hasNext()) { TrustAnchor anchor = trustIterator.next(); X509Certificate anchorCert = anchor.getTrustedCert(); if (anchorCert != null) { try { cm.CertManagerLoadCA(anchorCert); log("loaded TrustAnchor: " + anchorCert.getSubjectX500Principal().getName()); } catch (WolfCryptException e) { throw new CertPathValidatorException(e); } } } } /** * Verify X509Certificate chain from top down, ending with peer/leaf * cert last. * */ private void verifyCertChain(CertPath path, PKIXParameters params, List certs, WolfSSLCertManager cm) throws CertPathValidatorException { int i = 0; X509Certificate cert = null; if (path == null || params == null || certs == null || cm == null) { throw new CertPathValidatorException( "Input args to verifyCertChain are null"); } log("verifying certificate chain (chain size: " + certs.size() + ")"); /* Process certs from List in reverse order (top to peer) */ for (i = certs.size()-1; i >= 0; i--) { cert = certs.get(i); try { /* Try to verify cert */ cm.CertManagerVerify(cert); log("verified chain [" + i + "]: " + cert.getSubjectX500Principal().getName()); } catch (WolfCryptException e) { log("failed verification chain [" + i + "]: " + cert.getSubjectX500Principal().getName()); throw new CertPathValidatorException( "Failed verification on certificate", e, path, i); } /* Verified successfully. If this is a CA and we have more * certs, load this as trusted (intermediate) */ if (i > 0 && cert.getBasicConstraints() >= 0) { try { cm.CertManagerLoadCA(cert); log("chain [" + i + "] is intermediate, loading as root"); } catch (WolfCryptException e) { log("chain [" + i + "] is CA, but failed to load as " + "trusted root, not loading"); } } } } /** * Search TrustAnchors in PKIXParameters for one that verifies the provided * X509Certificate. * * @param params PKIXParameters to get TrustAnchors from * @param cert X509Certificate for which to find signer cert * * @return TrustAnchor that signs provided cert * * @throws CertPathValidatorException if the search for TrustAnchor fails */ public TrustAnchor findTrustAnchor(PKIXParameters params, X509Certificate cert) throws CertPathValidatorException { Set trustAnchors = null; Iterator trustIterator = null; TrustAnchor anchorFound = null; X500Principal issuer = null; WolfSSLCertManager cm = null; if (params == null || cert == null) { throw new CertPathValidatorException( "Input parameters are null to findTrustAnchor"); } /* Issuer name we need to match */ issuer = cert.getIssuerX500Principal(); if (issuer == null) { throw new CertPathValidatorException( "Unable to get expected issuer name"); } /* Get all TrustAnchors in PKIXParameters */ trustAnchors = params.getTrustAnchors(); if (trustAnchors == null || trustAnchors.isEmpty()) { throw new CertPathValidatorException( "No TrustAnchors in PKIXParameters"); } try { cm = new WolfSSLCertManager(); } catch (WolfCryptException e) { throw new CertPathValidatorException( "Failed to create native WolfSSLCertManager"); } /* Iterate through TrustAnchors and check for match */ trustIterator = trustAnchors.iterator(); while (trustIterator.hasNext()) { TrustAnchor anchor = trustIterator.next(); X509Certificate anchorCert = anchor.getTrustedCert(); if (anchorCert == null) { /* Skip to next */ continue; } if (!anchorCert.getSubjectX500Principal().equals(issuer)) { /* Isser name doesn't match, skip to next */ continue; } try { /* Unload any CAs in CertManager */ cm.CertManagerUnloadCAs(); } catch (WolfCryptException e) { cm.free(); throw new CertPathValidatorException( "Unable to unload CAs from native WolfSSLCertManager"); } try { /* Load anchor as CA */ cm.CertManagerLoadCA(anchorCert); } catch (WolfCryptException e) { /* error loading CA, skip to next */ continue; } try { /* Try to verify cert, mark found if successful */ cm.CertManagerVerify(cert); anchorFound = anchor; } catch (WolfCryptException e) { /* Does not verify, skip to next */ continue; } } /* Free native WolfSSLCertManager resources */ cm.free(); return anchorFound; } /** * Check if revocation has been enabled in PKIXParameters, and if so * find and load any CRLs in params.getCertStores(). * * @param params parameters used to check if revocation is enabled * and if so load any CRLs available * @param cm WolfSSLCertManager to load CRLs into * @param targetCert peer/leaf cert used to find matching CRL * * @throws CertPathValidatorException if error is encountered during * revocation checking or CRL loading */ private void checkRevocationEnabledAndLoadCRLs( PKIXParameters params, WolfSSLCertManager cm, X509Certificate targetCert) throws CertPathValidatorException { int i = 0; int loadedCount = 0; List stores = null; Collection crls = null; if (params == null || cm == null) { throw new CertPathValidatorException( "PKIXParameters or WolfSSLCertManager is null"); } if (params.isRevocationEnabled()) { log("revocation enabled in PKIXParameters, checking " + "for CRLs to load"); if (!WolfCrypt.CrlEnabled()) { throw new CertPathValidatorException( "Revocation enabled in PKIXParameters but native " + "wolfCrypt CRL not compiled in"); } /* Enable CRL in native WolfSSLCertManager */ cm.CertManagerEnableCRL(WolfCrypt.WOLFSSL_CRL_CHECK); log("CRL support enabled in native WolfSSLCertManager"); stores = params.getCertStores(); if (stores == null || stores.isEmpty()) { log("no CertStores in PKIXParameters to load CRLs"); return; } /* Create CRL selector to help match target X509Certificate */ X509CRLSelector selector = new X509CRLSelector(); selector.setCertificateChecking(targetCert); try { /* Find and load any matching CRLs */ for (i = 0; i < stores.size(); i++) { crls = stores.get(i).getCRLs(selector); for (CRL crl: crls) { if (crl instanceof X509CRL) { cm.CertManagerLoadCRL((X509CRL)crl); loadedCount++; } } } } catch (CertStoreException e) { throw new CertPathValidatorException(e); } log("loaded " + loadedCount + " CRLs into WolfSSLCertManager"); } else { log("revocation not enabled in PKIXParameters"); } } /** * Validates the specified certification path using the provided * algorithm parameter set. * * General validation process follows: * 1. Sanitize CertPathParameters * a. Verify not null and instanceof PKIXParameters * 2. Sanitize CertPath * a. CertPath.getType() is "X.509" * b. CertPath.getEncoding() contains "PkiPath" * 3. If wolfCrypt FIPS, verify params.getSigProvider() is wolfJCE * 4. Sanitize Certificate objects in CertPath chain * a. Check target certificate constraints meet target cert * b. Check cert policies are not used (not supported) * 5. Call any registered CertPathCheckers * 6. Load TrustAnchors into WolfSSLCertManager * 7. Enable CRL if requested, load CRLs from getCertStores() * 8. Verify X.509 certificate chain * 9. Find top-most TrustAnchor for return object * * @param certPath the CertPath to be validated. CertPath entries are * ordered from leaf/peer up the chain to CA/root last. * The certificate representing the last/final TrustAnchor * should not be part of the CertPath. * @param params the algorithm parameters to be used for validation * * @return the result of the validation * * @throws CertPathValidatorException if the CertPath does not validate * @throws InvalidAlgorithmParameterException if the parameters or type * specified are unsupported or inappropriate for this * CertPathValidator implementation. */ @Override public CertPathValidatorResult engineValidate( CertPath certPath, CertPathParameters params) throws CertPathValidatorException, InvalidAlgorithmParameterException { int i = 0; PKIXParameters pkixParams = null; List certs = null; WolfSSLCertManager cm = null; TrustAnchor trustAnchor = null; log("entered engineValidate(), FIPS enabled: " + Fips.enabled); sanitizeCertPathParameters(params); sanitizeCertPath(certPath); pkixParams = (PKIXParameters)params; /* If we are in FIPS mode, verify wolfJCE is the Signature provider * to help maintain FIPS compliance */ if (Fips.enabled && pkixParams.getSigProvider() != "wolfJCE") { if (pkixParams.getSigProvider() == null) { /* Preferred Signature provider not set, set to wolfJCE */ pkixParams.setSigProvider("wolfJCE"); } else { throw new CertPathValidatorException( "CertPathParameters Signature Provider must be wolfJCE " + "when using wolfCrypt FIPS: " + pkixParams.getSigProvider()); } } /* Use wolfSSL CertManager to facilitate chain verification */ try { cm = new WolfSSLCertManager(); } catch (WolfCryptException e) { throw new CertPathValidatorException( "Failed to create native WolfSSLCertManager"); } try { if (pkixParams.getDate() != null) { /* TODO: If pkixParams.getDate() is not null, we should * use that time for verification instead of current time. * Will need to wrap/register/use native time callback * with wc_SetTimeCb() */ throw new CertPathValidatorException( "Overriding date not supported with wolfJCE " + "CertPathValidator implementation yet"); } /* Get List of Certificate objects in CertPath, sanity check * that they are X509Certificate instances. */ certs = new ArrayList<>(); for (Certificate cert : certPath.getCertificates()) { if (cert instanceof X509Certificate) { certs.add((X509Certificate) cert); } } if (certs.size() == 0) { throw new CertPathValidatorException( "No Certificate objects in CertPath"); } /* Sanity checks on certs from PKIXParameters constraints */ for (i = 0; i < certs.size(); i++) { sanitizeX509Certificate(certs.get(i), i, certPath, pkixParams); callCertPathCheckers(certs.get(i), pkixParams); } /* Load trust anchors into CertManager from PKIXParameters */ loadTrustAnchorsIntoCertManager(pkixParams, cm); /* Enable CRL if PKIXParameters.isRevocationEnabled(), load * any CRLs found in PKIXParameters.getCertStores(). Needs to * happen after trust anchors are loaded, since native wolfSSL * will try to find/verify CRL against trusted roots on load */ checkRevocationEnabledAndLoadCRLs(pkixParams, cm, certs.get(0)); /* Verify cert chain */ verifyCertChain(certPath, pkixParams, certs, cm); /* Cert chain has been verified, find TrustAnchor to return * in PKIXCertPathValidatorResult */ trustAnchor = findTrustAnchor( pkixParams, certs.get(certs.size() - 1)); } finally { /* Free native WolfSSLCertManager resources */ cm.free(); } /* PolicyNode not returned, since certificate policies are not * yet supported */ return new PKIXCertPathValidatorResult(trustAnchor, null, certs.get(0).getPublicKey()); } /** * Returns a CertPathChecker that this implementation uses to check the * revocation status of certificates. * * Not currently implemented in wolfJCE. * * @return a CertPathChecker object that this implementation uses to * check the revocation status of certificates. * @throws UnsupportedOperationException if this method is not implemented */ @Override public CertPathChecker engineGetRevocationChecker() throws UnsupportedOperationException { throw new UnsupportedOperationException( "getRevocationChecker() not supported by wolfJCE"); } /** * Internal log function, called when debug is enabled. * * @param msg Log message to be printed */ private void log(String msg) { WolfCryptDebug.print("[CertPathValidator] " + msg); } }