Post-quantum and TLS

CNSA 2.0 for web servers, the secure way

A contract says "CNSA 2.0", a scanner says "A+", and those are two different exams. CNSA 2.0 asks for one key exchange, one cipher and a short list of signatures, and it does not care what your grade was.

The short answer

Serve CNSA 2.0 clients on a dedicated TLS 1.3 endpoint that accepts only ML-KEM-1024 key exchange and TLS_AES_256_GCM_SHA384. In nginx on OpenSSL 3.5 set ssl_ecdh_curve MLKEM1024 and the Ciphersuites command; in Envoy use the CNSA2_202603 compliance policy. Keep browsers on a separate hybrid endpoint, since they do not offer ML-KEM-1024.

Updated Houssam Hammoudi, CTOTested with nginx 1.28.3 on OpenSSL 3.5.8, Envoy 1.39.1

On this page
  1. What goes wrong
  2. What the docs say
  3. The secure configuration
  4. Prove it
  5. Mistakes people make
  6. Checklist

What goes wrong

CNSA 2.0 is NSA's algorithm suite for US national security systems. For key establishment it names one algorithm at one size: ML-KEM-1024. For encryption, AES with 256-bit keys. For hashing, SHA-384 or SHA-512. For signatures, ML-DSA-87, with a transition period for existing algorithms.

A typical "modern" TLS config fails that test in three places at once. It offers X25519 or the X25519MLKEM768 hybrid, which are not ML-KEM-1024. It accepts TLS_AES_128_GCM_SHA256 and ChaCha20. And it serves an RSA-2048 or P-256 certificate.

The other failure is putting CNSA rules on the public site. No browser offers ML-KEM-1024 today, so a CNSA-only listener refuses every browser. The secure setup is a separate endpoint for the clients that need CNSA 2.0.

What the docs say

ML-KEM-1024 for all classification levels.

Source: NSA, The Commercial National Security Algorithm Suite 2.0 and Quantum Computing FAQ

All appropriate system components should be configured to prefer CNSA 2.0 algorithms. As products mature, those components should be configured to accept only CNSA 2.0 algorithms.

Source: NSA, The Commercial National Security Algorithm Suite 2.0 and Quantum Computing FAQ

Any cipher suite other than TLS_AES_256_GCM_SHA384 offered by the client MUST NOT be accepted by a CNSA TLS server establishing a CNSA-compliant connection.

Source: IETF draft, CNSA 2.0 TLS profile (draft-becker-cnsa2-tls-profile)

Note: this setting aids with compliance with CNSA requirements but does not guarantee it.

Source: Envoy docs, TlsParameters.CompliancePolicy CNSA2_202603

NSA's documents name algorithms, not server settings. The nginx and Envoy docs name settings, not CNSA. The mapping between them is this page.

The secure configuration

nginx on OpenSSL 3.5 or newer, a dedicated server block:

conf
server {
    listen 443 ssl;
    server_name cnsa.example.com;

    # ECDSA P-384 certificate signed with SHA-384. Publicly trusted ML-DSA
    # certificates do not exist yet; see the ML-DSA page for private PKI.
    ssl_certificate     /etc/nginx/tls/cnsa.example.com.crt;
    ssl_certificate_key /etc/nginx/tls/cnsa.example.com.key;

    ssl_protocols TLSv1.3;                                  # TLS 1.3 only
    ssl_ecdh_curve MLKEM1024;                               # ML-KEM-1024 only
    ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384;   # AES-256-GCM only
    ssl_conf_command SignatureAlgorithms ECDSA+SHA384;      # P-384 / SHA-384 handshake signatures
}

Envoy (1.39 or newer), the listener's TLS context:

yaml
transport_socket:
  name: envoy.transport_sockets.tls
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
    common_tls_context:
      tls_params:
        # BoringSSL's CNSA 2.0 policy: TLS 1.3 only, AES-256-GCM only,
        # ML-KEM-1024 only, ECDSA P-384/SHA-384 or RSA/SHA-384 signatures.
        # Applied last; it overrides other tls_params.
        compliance_policies: [CNSA2_202603]
      tls_certificates:
      - certificate_chain: {filename: /etc/envoy/tls/cnsa.example.com.crt}
        private_key: {filename: /etc/envoy/tls/cnsa.example.com.key}

Envoy Gateway v1.9 does not expose compliance_policies in ClientTrafficPolicy. On Envoy Gateway you can set minVersion: "1.3" and ecdhCurves: [MLKEM1024], but not the TLS 1.3 cipher restriction; use an EnvoyPatchPolicy or a standalone Envoy for a strict CNSA endpoint.

Certificate, ECDSA P-384 with SHA-384:

bash
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-384 -sha384 -nodes \
  -keyout cnsa.example.com.key -out cnsa.example.com.csr -subj "/CN=cnsa.example.com"

Prove it

The test in secure-tests/cnsa-2-0-web-servers/run.sh runs both configurations and probes each with four client offers from OpenSSL 3.5.8.

bash
openssl s_client -connect cnsa.example.com:443 -groups MLKEM1024 -ciphersuites TLS_AES_256_GCM_SHA384
openssl s_client -connect cnsa.example.com:443 -groups X25519MLKEM768:X25519
openssl s_client -connect cnsa.example.com:443 -groups MLKEM1024 -ciphersuites TLS_AES_128_GCM_SHA256
openssl s_client -connect cnsa.example.com:443 -groups MLKEM1024 -tls1_2
text
== nginx 1.28.3, OpenSSL 3.5
  MLKEM1024 + AES-256-GCM (CNSA 2.0)     TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384; Negotiated TLS1.3 group: MLKEM1024
  browser-like: X25519MLKEM768:X25519    handshake refused
  MLKEM1024 + AES-128-GCM only           handshake refused
  MLKEM1024, TLS 1.2 only                handshake refused

== Envoy 1.39.1, compliance_policies: [CNSA2_202603]
  MLKEM1024 + AES-256-GCM (CNSA 2.0)     TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384; Negotiated TLS1.3 group: MLKEM1024
  browser-like: X25519MLKEM768:X25519    handshake refused
  MLKEM1024 + AES-128-GCM only           handshake refused
  MLKEM1024, TLS 1.2 only                handshake refused

Both servers accept the CNSA 2.0 handshake and refuse the three others. The second row is why this endpoint must not be your public website.

Mistakes people make

Calling the hybrid "CNSA 2.0"

X25519MLKEM768 is a sound choice for browsers. It is ML-KEM-768 at NIST level 3, not ML-KEM-1024. It does not meet CNSA 2.0.

Restricting ciphers with the TLS 1.2 setting

ssl_ciphers in nginx and cipher_suites in Envoy apply to TLS 1.2 and below. For TLS 1.3 use ssl_conf_command Ciphersuites in nginx and the compliance policy in Envoy.

Putting CNSA rules on the browser endpoint

A CNSA-only listener refuses every browser today. Run a separate hostname or port for CNSA clients and keep the hybrid on the public site.

Treating P-384 certificates as the end state

CNSA 2.0 names ML-DSA-87 for signatures. Publicly trusted ML-DSA certificates do not exist yet, so P-384 with SHA-384 is the transition. In a private PKI you can issue ML-DSA-87 today; see the ML-DSA page.

Believing a green probe equals compliance

The probe shows the protocol settings. Compliance also covers the module (validated cryptography), key management and the client. The Envoy docs say it plainly: the policy aids compliance, it does not guarantee it.

Checklist

  • Use a dedicated hostname or port for CNSA 2.0 clients.
  • Allow TLS 1.3 only.
  • Allow ML-KEM-1024 only for key exchange.
  • Allow TLS_AES_256_GCM_SHA384 only.
  • Serve an ECDSA P-384 certificate signed with SHA-384, or ML-DSA-87 in a private PKI.
  • Restrict handshake signatures to P-384 with SHA-384.
  • Probe with a CNSA offer, a browser offer, an AES-128 offer and TLS 1.2.

One key exchange, one cipher, one curve: CNSA 2.0 is the rare standard that fits on an index card.

H2-CPQE

Learn it on a live range

Post-quantum TLS, in Edge and Post-Quantum Networking: a real host in your browser, and every objective checked on the machine.

Start free

The Dome

Want it run for you?

The Dome puts post-quantum TLS, a WAF that blocks, signed DNS and a zero-trust mesh in front of your application. Tell us what you run.

See the Dome