You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何扩展JMeter证书验证代码以识别证书过期/吊销等详细问题

How to Diagnose Specific SSL Certificate Issues (Expired, Revoked, etc.) in JMeter Groovy Scripts

Hey Rick, I feel your pain—dealing with vague SSL errors that don't tell you why the cert is bad is a nightmare, especially when it leads to downtime that takes forever to debug. Let's fix that generic "unable to find valid certification path" error and get specific details about exactly what's wrong with the server's certificate.

The root issue with your current code is that the default SSLSocketFactory hides granular certificate validation details behind a generic exception. To get specific errors, we need to create a custom X509TrustManager that explicitly checks for common certificate problems, then use it to build our SSL context. Here's a step-by-step solution:

Step 1: Define the Certificate Checks We Need

We want to catch these specific failure scenarios:

  • Certificate is expired
  • Certificate is not yet valid (issued for future use)
  • Certificate has been revoked (via CRL)
  • Invalid certificate chain (missing intermediate certificates)

Step 2: Full Groovy Code Implementation

Drop this into a JSR223 Sampler in JMeter. This script will connect to your server, validate the certificate chain, and throw actionable, specific exceptions you can use for alerts or debugging:

import javax.net.ssl.*
import java.security.cert.*
import java.security.*
import java.net.URL
import java.util.Arrays

// Custom TrustManager to perform granular certificate validation
class DetailedTrustManager implements X509TrustManager {

    @Override
    void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        // Not required for server authentication checks
    }

    @Override
    void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        // 1. Check each certificate's validity period
        def now = new Date()
        for (X509Certificate cert : chain) {
            try {
                cert.checkValidity(now)
            } catch (CertificateExpiredException e) {
                throw new CertificateException(
                    "CERTIFICATE EXPIRED: ${cert.getSubjectDN()}, Expiry Date: ${cert.getNotAfter()}", e
                )
            } catch (CertificateNotYetValidException e) {
                throw new CertificateException(
                    "CERTIFICATE NOT YET VALID: ${cert.getSubjectDN()}, Valid From: ${cert.getNotBefore()}", e
                )
            }
        }

        // 2. Check if any certificate in the chain is revoked (via CRL)
        checkRevocationStatus(chain)

        // 3. Validate the full certificate chain against trusted roots
        validateCertificateChain(chain)
    }

    @Override
    X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0]
    }

    // Helper method to check revocation status using CRL
    private void checkRevocationStatus(X509Certificate[] chain) throws CertificateException {
        def certFactory = CertificateFactory.getInstance("X.509")
        for (X509Certificate cert : chain) {
            // Extract CRL distribution points from the certificate
            def crlDistPoints = cert.getExtensionValue("2.5.29.31")
            if (crlDistPoints == null) {
                log.warn("No CRL distribution points found for certificate: ${cert.getSubjectDN()}")
                continue
            }

            def crlUrl = parseCrlDistributionPoint(crlDistPoints)
            if (crlUrl == null) continue

            try {
                // Fetch and parse the CRL
                def crl = certFactory.generateCRL(new URL(crlUrl).openStream()) as X509CRL
                if (crl.isRevoked(cert)) {
                    def revocationDate = crl.getRevocationDate(cert)
                    def revocationReason = crl.getRevocationReason(cert) ?: "Unknown"
                    throw new CertificateException(
                        "CERTIFICATE REVOKED: ${cert.getSubjectDN()}, Revocation Date: ${revocationDate}, Reason: ${revocationReason}"
                    )
                }
            } catch (Exception e) {
                throw new CertificateException(
                    "Failed to check revocation status for ${cert.getSubjectDN()}: ${e.message}", e
                )
            }
        }
    }

    // Helper to parse CRL URL from ASN.1 encoded extension data
    private String parseCrlDistributionPoint(byte[] extensionValue) {
        try {
            def inputStream = new ByteArrayInputStream(extensionValue)
            def derReader = new java.io.DerInputStream(inputStream)
            def derValue = derReader.readDerValue()
            def seq = derValue.getSequence()
            def distributionPoint = seq[0]
            def fullName = distributionPoint.getSequence()[0]
            def uriBytes = fullName.getTaggedObject(6).getOctetString()
            return new String(uriBytes, "UTF-8")
        } catch (Exception e) {
            log.error("Failed to parse CRL distribution point: ${e.message}")
            return null
        }
    }

    // Helper to validate the full certificate chain against system trusted roots
    private void validateCertificateChain(X509Certificate[] chain) throws CertificateException {
        def trustStore = KeyStore.getInstance(KeyStore.getDefaultType())
        // Load system default trust store (contains trusted root CA certificates)
        trustStore.load(null, null)

        def params = new PKIXParameters(trustStore)
        params.setRevocationEnabled(false) // We already checked revocation separately
        def validator = CertPathValidator.getInstance("PKIX")

        def certFactory = CertificateFactory.getInstance("X.509")
        def certPath = certFactory.generateCertPath(Arrays.asList(chain))

        try {
            validator.validate(certPath, params)
        } catch (CertPathValidatorException e) {
            throw new CertificateException("INVALID CERTIFICATE CHAIN: ${e.message}", e)
        }
    }
}

// Build SSL context with our custom TrustManager
def sslContext = SSLContext.getInstance("TLS")
sslContext.init(
    null, // No key manager needed for client-side checks
    [new DetailedTrustManager()] as TrustManager[],
    new SecureRandom()
)

// Establish connection and trigger SSL handshake
def socket = sslContext.getSocketFactory().createSocket('example.com', 443)
try {
    socket.startHandshake() // This triggers all certificate validation checks
    log.info("Certificate validation passed successfully!")
} catch (CertificateException e) {
    // Log the detailed error and store it in a JMeter variable for alerts/assertions
    log.error("Certificate Validation FAILED: ${e.message}", e)
    vars.put("CERT_ERROR_DETAILS", e.message)
    // Throw the exception to mark the sampler as failed in JMeter
    throw e
} finally {
    socket.close()
}

Key Features Explained

  • Granular Error Messages: Each failure scenario throws a clear, human-readable exception (e.g., "CERTIFICATE REVOKED: CN=example.com, Revocation Date: 2024-05-20, Reason: KEY_COMPROMISE").
  • CRL Revocation Check: Parses the certificate's CRL distribution point, fetches the latest CRL, and checks if the certificate has been revoked.
  • Chain Validation: Ensures the full certificate chain (root → intermediate → server) is trusted by the system's default root CA store.
  • JMeter Integration: Stores error details in a JMeter variable (CERT_ERROR_DETAILS) that you can use with assertions, alerting plugins, or reporting tools to trigger targeted notifications.

Production Considerations

  1. CRL/OCSP Access: Ensure your JMeter server can reach the CRL distribution URLs (typically HTTP/LDAP endpoints). For more reliable revocation checks, you can enable OCSP validation by configuring Java's security properties.
  2. Performance: Fetching CRLs adds latency. Consider caching CRLs if you run frequent checks.
  3. Custom Trust Stores: If your environment uses a private trust store, modify the validateCertificateChain method to load your custom store instead of the system default.

This script will give you the precise certificate error details you need to quickly diagnose issues like revoked or expired certs, eliminating those long, frustrating downtime debugging sessions.

内容的提问来源于stack exchange,提问作者Rick B

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:18:55