Improper Following of a Certificate's Chain of Trust

Description

Improper Following of a Certificate's Chain of Trust is a vulnerability that occurs when a product does not follow, or incorrectly follows, the chain of trust for a certificate back to a trusted root certificate. Trust derived from certificates depends on validating the entire chain from the end-entity certificate through intermediate certificates to a reputable root certificate authority. When only the immediate certificate is checked or validation shortcuts are taken, genuine trust cannot be established. Common failures include accepting self-signed certificates in the chain (except at root level), skipping intermediate certificate validation, ignoring missing Basic Constraints or critical extensions, and trusting compromised or improperly authorized root certificates.

Risk

Improper chain of trust validation fundamentally undermines certificate-based security. Attackers can create self-signed certificates or obtain certificates from any CA (including compromised ones) and use them in man-in-the-middle attacks. DNS poisoning combined with acceptance of self-signed certificates allows attackers to redirect users to malicious servers that appear trusted. If intermediate certificates are not validated, attackers can chain their malicious certificate to any trusted root they can reference. Missing Basic Constraints validation allows end-entity certificates to improperly act as CA certificates, signing arbitrary certificates that applications then trust. The risk is amplified because these failures typically occur silently - users believe their connections are secure while they are actually vulnerable to interception.

Solution

Incorporate proper certificate chain validation from the design phase. Fully understand and implement all chain-of-trust verification checks including: validating each certificate in the chain is signed by the next, verifying all certificates are within their validity period, checking Basic Constraints extensions to ensure only CA certificates can sign other certificates, verifying that end-entity certificates are not acting as CAs, checking certificate revocation status for all certificates in the chain, and ensuring the chain terminates at a trusted root certificate. When using certificate pinning, validate the complete chain before pinning. Use well-tested TLS libraries that implement proper chain validation and ensure you're calling the validation functions correctly.

Common Consequences

ImpactDetails
Non-RepudiationScope: Non-Repudiation

Exploitation of this flaw enables hiding malicious activities behind apparently trusted sources. Data appears to originate from trusted entities when it may have been intercepted and modified by attackers.
Access Control, Integrity, ConfidentialityScope: Access Control, Integrity, Confidentiality, Availability

Attackers can perform privileged actions as trusted entities, gain unauthorized access to sensitive data, and compromise the integrity of communications.

Example Code

Vulnerable Code (C/OpenSSL)

The following examples demonstrate improper chain of trust validation:

// Vulnerable: Accepting self-signed certificates in chain
#include <openssl/ssl.h>
#include <openssl/x509.h>

int vulnerable_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
    int err = X509_STORE_CTX_get_error(ctx);

    // Vulnerable: Accepting self-signed certificates
    if (err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) {
        return 1;  // Allow self-signed - DANGEROUS!
    }

    // Vulnerable: Ignoring depth errors
    if (err == X509_V_ERR_CERT_CHAIN_TOO_LONG) {
        return 1;  // Skip chain length validation
    }

    return preverify_ok;
}

int vulnerable_check_cert(SSL *ssl, const char *host) {
    X509 *cert = SSL_get_peer_certificate(ssl);
    long foo;

    if (cert && host) {
        foo = SSL_get_verify_result(ssl);
    }

    // Vulnerable: Accepting self-signed or verification errors
    if ((X509_V_OK == foo) ||
        (X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN == foo)) {
        // Certificate "accepted" - but chain not validated!
        return 1;
    }

    return 0;
}
# Vulnerable: Python ignoring chain validation errors
import ssl
import socket

def vulnerable_connect(hostname, port):
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

    # Vulnerable: Loading additional untrusted CAs
    context.load_verify_locations('/path/to/untrusted/ca-bundle.pem')

    # Vulnerable: Custom verify callback that ignores errors
    # (Python doesn't directly expose this, but equivalent behavior)

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    ssl_sock = context.wrap_socket(sock, server_hostname=hostname)

    try:
        ssl_sock.connect((hostname, port))
    except ssl.SSLCertVerificationError as e:
        # Vulnerable: Ignoring verification errors and proceeding
        if "self signed" in str(e).lower():
            print("Warning: Self-signed certificate (ignored)")
            # Continues anyway!

    return ssl_sock
// Vulnerable: Java ignoring chain validation
import javax.net.ssl.*;
import java.security.cert.*;

public class VulnerableChainValidator implements X509TrustManager {

    private final X509TrustManager defaultTm;

    public VulnerableChainValidator() throws Exception {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init((KeyStore) null);
        defaultTm = (X509TrustManager) tmf.getTrustManagers()[0];
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType)
            throws CertificateException {
        try {
            defaultTm.checkServerTrusted(chain, authType);
        } catch (CertificateException e) {
            // Vulnerable: Ignoring certain chain errors
            if (e.getMessage().contains("self-signed") ||
                e.getMessage().contains("path building")) {
                // Accept anyway!
                return;
            }
            throw e;
        }
    }

    // ... other methods
}

Fixed Code (C/OpenSSL)

// Fixed: Proper certificate chain validation
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

SSL_CTX* secure_create_context() {
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());

    // Enable full verification
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

    // Load only trusted root CAs
    SSL_CTX_set_default_verify_paths(ctx);

    // Set proper verification depth
    SSL_CTX_set_verify_depth(ctx, 4);

    // Enable CRL checking
    X509_STORE *store = SSL_CTX_get_cert_store(ctx);
    X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
                                X509_V_FLAG_CRL_CHECK_ALL);

    return ctx;
}

// Fixed: Strict verification callback
int strict_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
    if (!preverify_ok) {
        int err = X509_STORE_CTX_get_error(ctx);
        int depth = X509_STORE_CTX_get_error_depth(ctx);
        X509 *cert = X509_STORE_CTX_get_current_cert(ctx);

        char subject[256];
        X509_NAME_oneline(X509_get_subject_name(cert), subject, sizeof(subject));

        fprintf(stderr, "Certificate error at depth %d:\n", depth);
        fprintf(stderr, "  Subject: %s\n", subject);
        fprintf(stderr, "  Error: %s\n", X509_verify_cert_error_string(err));

        // Fixed: Reject ALL validation failures
        return 0;
    }

    // Additional checks even if preverify passed
    X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
    int depth = X509_STORE_CTX_get_error_depth(ctx);

    // Check Basic Constraints for intermediate certs
    if (depth > 0) {
        BASIC_CONSTRAINTS *bc = X509_get_ext_d2i(cert, NID_basic_constraints,
                                                  NULL, NULL);
        if (bc == NULL || !bc->ca) {
            fprintf(stderr, "Intermediate cert is not a CA\n");
            BASIC_CONSTRAINTS_free(bc);
            return 0;
        }
        BASIC_CONSTRAINTS_free(bc);
    }

    return 1;
}

int secure_check_cert(SSL *ssl, const char *expected_host) {
    // Get certificate
    X509 *cert = SSL_get_peer_certificate(ssl);
    if (cert == NULL) {
        fprintf(stderr, "No certificate presented\n");
        return 0;
    }

    // Fixed: Check verification result - NO exceptions
    long verify_result = SSL_get_verify_result(ssl);
    if (verify_result != X509_V_OK) {
        fprintf(stderr, "Certificate verification failed: %s\n",
                X509_verify_cert_error_string(verify_result));
        X509_free(cert);
        return 0;
    }

    // Fixed: Verify hostname
    if (X509_check_host(cert, expected_host, strlen(expected_host),
                        0, NULL) != 1) {
        fprintf(stderr, "Hostname verification failed\n");
        X509_free(cert);
        return 0;
    }

    X509_free(cert);
    return 1;  // Fully validated
}
# Fixed: Python with strict chain validation
import ssl
import socket
import certifi

def secure_connect(hostname, port):
    # Use default context with strict validation
    context = ssl.create_default_context(cafile=certifi.where())

    # Ensure all validation is enabled
    context.check_hostname = True
    context.verify_mode = ssl.CERT_REQUIRED

    # Don't load additional untrusted CAs
    # context.load_verify_locations() only for additional trusted CAs

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    ssl_sock = context.wrap_socket(sock, server_hostname=hostname)

    try:
        ssl_sock.connect((hostname, port))
    except ssl.SSLCertVerificationError as e:
        # Fixed: Never ignore verification errors
        print(f"Certificate verification failed: {e}")
        raise  # Propagate the error, don't suppress

    return ssl_sock

The fix ensures complete chain validation with no exceptions for self-signed certificates, validates Basic Constraints, checks hostname, and rejects all verification failures.


Exploited in the Wild

Self-Signed Certificate Acceptance (Various Applications, Ongoing)

CVE-2008-4989 documented acceptance of self-signed certificate chains, enabling man-in-the-middle attacks where attackers presented self-signed certificates that applications incorrectly trusted.

Untrusted CA in Chain (Android Apps, 2016)

CVE-2016-2402 documented bypass via untrusted CA certificates in the chain, where applications failed to validate that all CAs in the chain were trusted.

Missing Basic Constraints Validation (Browsers, Historical)

CVE-2002-0970 and CVE-2002-0862 documented web browsers that failed to validate Basic Constraints extensions, allowing end-entity certificates to act as CAs and sign arbitrary certificates.


Tools to Test/Exploit

  • testssl.sh — Command line tool for checking SSL/TLS configurations including chain validation.

  • mitmproxy — HTTPS proxy for testing certificate chain validation.

  • openssl s_client — Tool for testing SSL/TLS connections and certificate chains.


CVE Examples

  • CVE-2016-2402 — Bypass via untrusted CA in certificate chain.

  • CVE-2008-4989 — Self-signed certificate chain acceptance.

  • CVE-2012-5821 — Incorrect TLS function usage preventing CA verification.

  • CVE-2009-3046 — Missing revocation checks on intermediate certificates.

  • CVE-2002-0970 — Missing Basic Constraints validation.


References

  1. MITRE Corporation. "CWE-296: Improper Following of a Certificate's Chain of Trust." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/296.html

  2. RFC 5280. "Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile." https://tools.ietf.org/html/rfc5280

  3. OWASP Foundation. "Transport Layer Protection Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html