# How to Use testssl.sh for Certificate Chain Validation and CA Trust Checking

> Learn to validate certificate chains and check CA trust with testssl.sh. This tool verifies server certificates against major trust stores using OpenSSL and supports custom CAs.

- Repository: [Dirk Wetter/testssl.sh](https://github.com/drwetter/testssl.sh)
- Tags: how-to-guide
- Published: 2026-03-01

---

**testssl.sh validates X.509 certificate chains by retrieving server-presented certificates, building an intermediate bundle, and verifying the complete chain against five built-in CA trust stores (Mozilla, Microsoft, Apple, Java, Linux) using OpenSSL, with support for custom root CAs via the `--add-ca` option or `ADDTL_CA_FILES` environment variable.**

The [`drwetter/testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/drwetter/testssl.sh) repository provides a comprehensive SSL/TLS testing framework that automates certificate chain validation and CA trust verification. Unlike basic OpenSSL commands, testssl.sh systematically checks your server's certificate chain against multiple trust stores and identifies specific validation failures such as incomplete chains or untrusted roots. This guide explains the underlying implementation and practical commands for effective certificate chain validation.

## How testssl.sh Validates Certificate Chains

### Retrieving the Server Certificate Chain

The validation process begins in [`utils/checkcert.sh`](https://github.com/drwetter/testssl.sh/blob/main/utils/checkcert.sh), which connects to the target server and extracts the complete certificate chain. The script uses `openssl s_client` to capture every certificate the server presents, storing them as individual `level*.crt` files (e.g., `level0.crt` for the leaf, `level1.crt` for the intermediate) according to the extraction logic at lines 239-242【**utils/checkcert.sh#L239‑L242**】.

These collected certificates are concatenated into a single `intermediatecerts.pem` file within the temporary directory. This intermediate bundle represents every certificate the server provided aside from the leaf certificate itself, which is stored separately as `HOSTCERT`.

### Verifying Against Trust Stores

The core validation logic resides in [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh) between lines 8008 and 8030【**testssl.sh#L8008‑L8030**】. The script iterates over five built-in CA bundles located in the `etc/` directory—representing Mozilla, Microsoft, Apple, Java, and Linux trust stores—and executes OpenSSL verification for each.

The verification command follows this structure:

```bash
openssl verify -purpose sslserver \
    -CAfile <(cat ${ADDTL_CA_FILES//,/ } "$bundle_fname") \
    -untrusted $TEMPDIR/intermediatecerts.pem \
    $HOSTCERT

```

If the server presents a complete chain, `openssl verify` returns exit code `0` and the store is marked as trusted. When validation fails, the script captures the error code through `verify_retcode_helper` and translates it into human-readable messages such as "chain incomplete" or "self-signed in chain"【**testssl.sh#L8059‑L8085**】.

## Command-Line Options for Custom CA Trust Checking

testssl.sh provides flexible mechanisms to inject custom root certificates into the validation process, essential for testing internal applications with private PKI infrastructures.

### Using the --add-ca Flag

To validate chains against a custom root CA, use the `--add-ca` option documented in the man page at lines 157-159【**doc/testssl.1.md#L157‑L159**】:

```bash
./testssl.sh --add-ca /path/to/corporate-root.pem internal.example.com:443

```

The specified PEM file is concatenated with each built-in trust store during verification, effectively extending the trust anchor without modifying system stores.

### Environment Variable Configuration

For multiple custom CAs or repeated testing sessions, set the `ADDTL_CA_FILES` environment variable with comma-separated paths:

```bash
export ADDTL_CA_FILES="/opt/certs/root1.pem,/opt/certs/root2.pem"
./testssl.sh --add-ca "$ADDTL_CA_FILES" mail.internal:443

```

You can also override the default CA bundle location using `CA_BUNDLES_PATH` to point to alternative trust store directories【**testssl.sh#L8002‑L8007**】.

## Understanding Certificate Chain Validation Output

The grading logic between lines 8059 and 8085 assigns severity ratings based on validation results【**testssl.sh#L8059‑L8085**】. A grade of **B** indicates chain construction problems (such as missing intermediates), while **T** flags other trust issues.

Typical output appears as:

```

[+] Trust via hostname + chain of trust against supplied certificates
    OK: Mozilla Linux
    NOT ok: Microsoft (chain incomplete)

```

When "OK" appears, the certificate chain successfully validates against that specific trust store. "NOT ok" entries include specific error codes that help diagnose whether the server failed to send intermediate certificates or if the root CA is absent from that particular store.

## Summary

- **testssl.sh** automates certificate chain validation by extracting server certificates via [`utils/checkcert.sh`](https://github.com/drwetter/testssl.sh/blob/main/utils/checkcert.sh) and verifying them against five major trust stores in `etc/*.pem`.
- The verification engine at `testssl.sh#L8008‑L8030` uses `openssl verify` with the `-untrusted` flag to process intermediate certificates and checks against bundled Mozilla, Microsoft, Apple, Java, and Linux CA files.
- Custom root CAs integrate seamlessly through the `--add-ca` command-line option or `ADDTL_CA_FILES` environment variable, documented in [`doc/testssl.1.md`](https://github.com/drwetter/testssl.sh/blob/main/doc/testssl.1.md).
- Validation failures are graded (**B** for incomplete chains, **T** for trust issues) with specific error codes extracted via `verify_retcode_helper` between lines 8059-8085.
- For targeted testing, use `./testssl.sh -c` to display only certificate chain and trust validation results.

## Frequently Asked Questions

### How do I test a server that uses an internal or self-signed CA?

Use the `--add-ca` flag to specify your private root certificate file. testssl.sh concatenates this file with each built-in trust store during verification, allowing you to validate chains that rely on corporate or self-signed roots without modifying system certificate stores【**doc/testssl.1.md#L157‑L159**】.

### What does a grade "B" mean in certificate chain validation?

A **B** grade indicates a chain construction problem, most commonly an incomplete chain where the server fails to send necessary intermediate certificates. The script detects this via OpenSSL verify error codes processed in the grading logic at lines 8059-8085, distinguishing it from **T** grades that indicate broader trust issues【**testssl.sh#L8059‑L8085**】.

### Can I limit output to only certificate chain validation results?

Yes. Run testssl.sh with the `-c` flag to restrict output specifically to certificate chain and trust-store validation. This mode suppresses other TLS/SSL tests and provides focused results showing which of the five built-in stores (Mozilla, Microsoft, Apple, Java, Linux) successfully validate your server's chain.

### Where does testssl.sh store intermediate certificates during validation?

During execution, the script stores extracted intermediate certificates in `$TEMPDIR/intermediatecerts.pem` (where `$TEMPDIR` is a temporary directory created at runtime). The [`utils/checkcert.sh`](https://github.com/drwetter/testssl.sh/blob/main/utils/checkcert.sh) component creates this file by concatenating individual `level*.crt` files extracted from the server's chain【**utils/checkcert.sh#L239‑L242**】.