ScreenshotNeo

BlogEngineering

TLS Trust Anchors: How Certificate Authorities Are Negotiated

TLS can advertise acceptable CA names for certificate selection, but trust anchors remain a local policy decision in each verifier.

By the ScreenshotNeo team1 October 20269 min read

Short answer: TLS normally does not negotiate, install, or replace the certificate authorities you trust. In TLS 1.3, the certificate_authorities extension carries acceptable CA distinguished names to help the other endpoint choose a certificate. The verifier still uses its own local trust store and validation policy. RFC 5280 describes the key rule plainly: “The selection of one or more trusted CAs is a local decision.”

This distinction matters when diagnosing mutual TLS failures, configuring enterprise roots, or explaining why two applications on the same device accept different certificates. The handshake can communicate a certificate-selection constraint; it cannot silently change the verifier’s trust anchors.

What is a TLS trust anchor?

A trust anchor is trusted input supplied to certification-path validation. Usually it identifies a trusted CA name and public key, along with constraints that apply to that key. The self-signed root certificate is not necessarily treated as an ordinary certificate in the validated chain.

During validation, the implementation builds a prospective path from a target certificate through its issuers to an acceptable trust anchor. It checks issuer and subject relationships, signatures, validity periods, key usage, basic constraints, revocation information where configured, and any application-specific restrictions.

The trust anchor comes from local policy. It may be installed by an operating system, an application, enterprise management, or a user. RFC 6024 notes that a device can have multiple stores and that those stores need not be synchronized. A browser, command-line tool, language runtime, and database client can therefore make different decisions about the same server certificate.

What the certificate_authorities extension does

The current TLS 1.3 specification, RFC 9846, defines certificate_authorities as a list of acceptable CA distinguished names encoded in DER. The names can identify a trust anchor or a subordinate CA. The receiving endpoint uses the list to guide certificate selection.

Where it appears What it helps select What it does not do
ClientHello A certificate the server presents, where the server implementation uses the advertised names Install roots in the server’s trust store
CertificateRequest A client certificate chain for mutual TLS Make a client certificate trusted automatically

For mutual TLS, the server’s CertificateRequest can include acceptable CA names. The client should choose a certificate whose chain contains a certificate issued by one of those CAs when the list is present. This is a selection constraint or hint within the handshake. The server still validates the resulting chain against its own configured anchors and policy.

CA names are also separate from TLS 1.3’s signature_algorithms and optional signature_algorithms_cert extensions. The signature extensions describe usable signature schemes; the CA list describes issuer names that should guide certificate choice.

Handshake sequence: selection versus validation

  1. The endpoint builds a list of certificates it can use from its local configuration and key material.
  2. During the handshake, an endpoint may send acceptable CA names in ClientHello or CertificateRequest.
  3. The peer filters its candidate certificates using those names, signature-algorithm compatibility, key usage, validity, and local selection rules.
  4. The peer sends a certificate chain and proves possession of the private key.
  5. The receiving implementation validates that chain against its own trust anchors and policy.
  6. If no acceptable path exists, the handshake fails even if the peer sent a CA name that appears familiar.

A CA distinguished name in the extension is therefore not a global registry entry and not a grant of trust. It is protocol-context information used to improve certificate selection.

How to inspect CA negotiation yourself

Inspect a server’s advertised chain with OpenSSL

openssl s_client \
  -connect example.com:443 \
  -servername example.com \
  -showcerts \
  -verify_return_error \
  -CAfile /etc/ssl/certs/ca-certificates.crt

The command prints the certificates sent by the server and the local verification result. It does not show a universal trust store because the -CAfile argument selects the trust input for this invocation.

Request client authentication with OpenSSL

To observe a mutual TLS exchange, run a test server with a CA file and client-certificate requirement:

openssl s_server \
  -accept 8443 \
  -cert server.crt \
  -key server.key \
  -Verify 1 \
  -CAfile client-ca.pem \
  -www

The server’s certificate request can contain acceptable issuer names derived from its client CA configuration. Use a client certificate issued by that CA, then connect:

openssl s_client \
  -connect 127.0.0.1:8443 \
  -cert client.crt \
  -key client.key \
  -CAfile server-ca.pem \
  -state -msg

-msg is verbose and useful in a lab. Do not enable it in production logs if private metadata must remain confidential.

Python: inspect and configure trust locally

import socket
import ssl

host = "example.com"
context = ssl.create_default_context()

with socket.create_connection((host, 443), timeout=10) as raw:
    with context.wrap_socket(raw, server_hostname=host) as tls:
        print("TLS version:", tls.version())
        print("Cipher:", tls.cipher())
        print("Peer subject:", tls.getpeercert().get("subject"))

Python’s create_default_context() loads the trust configuration used by that Python/OpenSSL build. To use an application-specific CA, pass a file explicitly:

context = ssl.create_default_context(cafile="/path/to/company-root.pem")

That changes this Python context only. It does not add the root to the operating system or to other applications.

Node.js: configure a CA for one HTTPS client

import https from "node:https";

const request = https.get(
  "https://example.com",
  { ca: "-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\\n" },
  (response) => {
    console.log("status", response.statusCode);
    response.resume();
  }
);

request.on("error", console.error);

The ca option supplies trust material to this Node.js TLS context. In a real program, read a PEM file with fs.readFileSync and protect its distribution. Avoid setting rejectUnauthorized: false as a workaround: it disables certificate verification instead of fixing trust configuration.

cURL: select a CA bundle

curl --cacert /path/to/company-root.pem https://example.com/

Without --cacert, cURL uses the CA bundle selected by its build and environment. curl -k skips verification and is appropriate only for controlled troubleshooting.

Why trust differs between applications

Trust stores are implementation inputs, not necessarily a single device-wide database. Common sources include:

  • Operating-system roots used by native networking libraries.
  • Application bundles, such as a browser’s managed roots.
  • Language-runtime stores, including Java, Python/OpenSSL, and Node.js configurations.
  • Enterprise or mobile-device management profiles.
  • User-installed roots for development, interception proxies, or private PKI.

Before comparing results, identify the exact client, runtime, process environment, CA-file variables, proxy, and hostname. A certificate accepted by one application may fail in another because the applications use different anchors, constraints, or revocation settings.

PKI trust negotiation is a different process

“Negotiated trust” can also describe governance between participating PKIs. Policy authorities may agree on which certification policies, names, constraints, and roots their organizations will recognize. RFC 6024 discusses this trust-anchor management work as an administrative process that can take substantial time.

That governance is separate from the per-connection TLS extension. A policy agreement can result in a root being installed or authorized locally; the TLS handshake then operates with the resulting local policy.

Configuration checklist

  • Identify which process is doing validation.
  • Record the trust store or CA file that process actually loads.
  • Check the server and client certificate chains, including intermediates.
  • Check hostname verification separately from chain validation.
  • For mutual TLS, confirm that the client chain contains an issuer named by the server’s request.
  • Compare signature_algorithms with the certificate key and signature.
  • Check validity dates, key usage, basic constraints, and required policy OIDs.
  • Confirm that a proxy or TLS terminator is not presenting a different certificate.
  • After changing a store, restart long-lived processes that cache TLS contexts.

Troubleshooting common failures

Symptom Likely cause Fix
“unable to get local issuer certificate” The issuer or root is absent from the active store, or the server omitted an intermediate. Install the intended root in the correct application store and configure the server to send intermediates.
“certificate unknown” during mutual TLS The server cannot build a path from the client certificate to one of its anchors. Use a client chain from an accepted CA and configure the server’s client CA bundle.
Client sends no certificate The client has no certificate matching the names, key usage, or signature schemes in CertificateRequest. Inspect the request, choose a matching certificate, and verify its private key is available.
Works in a browser but fails in a script The applications use different trust stores, intermediates, proxy paths, or hostname rules. Inspect the script’s CA configuration and capture the certificate actually presented to that process.
“hostname mismatch” The chain may be trusted, but the certificate’s SAN does not match the requested host. Use the correct DNS name or issue a certificate containing the required SAN. Do not disable hostname verification.
“certificate has expired” The leaf or an intermediate is outside its validity period, or the system clock is wrong. Renew or replace the certificate and correct clock synchronization.
Handshake fails after adding a root The root was added to a store that this process does not use, or the chain violates application constraints. Trace the process-specific store and inspect policy, EKU, name constraints, and algorithm support.
TLS 1.3 server ignores CA names CA names guide selection; implementations can apply their own selection logic. Configure the server’s certificate selection explicitly and verify algorithm compatibility.

Performance, reliability, and security considerations

Performance

Certificate-path building can involve several candidate chains, signature checks, policy processing, and revocation lookups. Keep the server chain complete so clients do not need unreliable discovery of missing intermediates. Reuse TLS sessions where appropriate, and avoid sending unnecessary certificates.

Reliability

Manage roots and intermediates as deployable configuration. Pinning a private root in one service does not update every other service. Test certificate rotation before the old certificate expires, and include clients built with each supported runtime.

Security

Use the smallest trust set that satisfies the application. A broad public-root store can authorize more issuers than a private service requires. Keep private keys separate from trust-anchor files, restrict file permissions, and audit enterprise-installed roots. Never use verification-disable flags as a permanent fix.

Or skip the browser setup

If the goal is collecting clean screenshots of TLS documentation, dashboards, or certificate-status pages, ScreenshotNeo provides a single website screenshot API call. It accepts cookie banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also provides an MCP server for Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools.

See the ScreenshotNeo API documentation for all options. The same request works from cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

There are 1,000 free screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Create a free ScreenshotNeo account.

FAQ

Does a CA name in CertificateRequest make my client certificate trusted?

No. It helps the client select a certificate. The server still validates the chain against its local anchors and policy.

Can a TLS server tell a client which root certificates to install?

No. It can send CA names for certificate selection, but installing or authorizing roots is outside the handshake.

Are trust anchors always self-signed root certificates?

No. A trust anchor can be represented by trusted issuer and public-key information and may correspond to a subordinate CA with applicable constraints.

Why does the same certificate work with cURL but fail in Python?

The two programs may load different CA bundles, environment settings, proxies, hostname rules, or runtime policies. Compare their active configuration rather than assuming one device-wide store.

Is certificate_authorities the same as signature_algorithms?

No. CA names guide issuer-based certificate selection. Signature-algorithm extensions advertise supported signing schemes.

Primary references