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.
On this page
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:
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:
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:
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.
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== 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 refusedBoth 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 freeThe 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