Docs › Security
At a glance — constant-time tag and token comparison everywhere; ECDSA/Ed25519/X25519 inputs validated against the known attack classes (zero scalars, low-order points, non-canonical encodings); certificate chains anchored, CA-bit checked, DER fully bounds-checked; QUIC anti-amplification and two-level flow control enforced in the event loop; no malloc, no printf, no libc anywhere. Two things are deliberately left to you: certificate time/hostname checks and request-rate limiting.
The security properties wired enforces, organized by subsystem. Each item
states a concrete guarantee and, where useful, the RFC clause and the source
that implements it. This is a description of what the code checks — not a threat
model or an operator hardening guide.
- In scope (this SDK enforces it): cryptographic primitive correctness, certificate chain and signature validation, key agreement input validation, and QUIC/HTTP3 core resource and parser safety.
- Out of scope (the caller / upper layer must supply it): certificate validity-time and hostname checks (the SDK provides the APIs but the clock and SNI come from the application), HTTP/3 request-rate limiting, and Retry-token time-to-live. These are noted below as caller responsibilities.
- AEAD tag comparison is constant-time (XOR-OR accumulation, no early return);
used for every tag check —
common/bytes/util/ct.h,gcm.c,chacha/aead.c. - Truncated authentication tags are rejected: the tag length is fixed at 16 bytes and a minimum packet length is enforced before decryption.
- AEAD nonces are unique: the packet number is monotonic per number space and
the nonce is
IV XOR PN(RFC 9001 5.3). - Raw hashes are never used as a MAC; all MACs go through HMAC with correct ipad/opad, closing length-extension (RFC 2104 / FIPS 198-1).
- HMAC keys longer than the block size are folded with SHA-256 first (FIPS 198-1).
- Poly1305 clamps
rand uses a one-time key derived at counter 0 (RFC 8439).
- ECDSA rejects
r = 0ands = 0and requires both scalars in[1, n), closing the "Psychic Signature" class (CVE-2022-21449) —ecdsa_verify.c. - ECDSA signatures the SDK produces are low-S normalized (
s <= n/2, matching BoringSSL / RFC 6979 practice) —p256sign/sign.c. Verification accepts standard high-S signatures, as RFC/FIPS verification rules require. - Ed25519 rejects non-canonical scalars (
S >= L) —ed25519_sign.c. - Public keys used in signature verification are checked to lie on the curve and
are rejected if they are the point at infinity —
ecdsa_verify.c,p256_point.c. - ECDSA nonces are deterministic via HMAC-DRBG with range-checked candidate
generation (RFC 6979) —
rfc6979.c. - RSA PKCS#1 v1.5 verification builds the expected encoded message and compares it in constant time; RSA-PSS follows RFC 8017 9.1.2 with a correct MGF1.
- The X25519 shared secret is rejected when it is all-zero, which a low-order
peer point produces (RFC 7748 6.1); this prevents a non-contributory key
exchange —
x25519.c. - The rejection propagates through every ECDHE call site so a low-order peer key aborts the handshake instead of deriving keys from a zero secret — the crypto stream, endpoint agreement, the server handshake, and the sdrv flight.
- The scalar is clamped per RFC 7748 (clear low 3 bits, set bit 254, clear bit 255).
- Field elements reduce fully mod
p = 2^255 - 19, including the boundary case where the internal value equalsp(it reduces to 0, not to 19) — this is what lets a low-order point produce a true all-zero secret.
- Each link in the chain is verified: the child's issuer name equals the
parent's subject name and the parent's key signs the child; the tail is
anchored to a registered trust root —
pathvalidate.c,chainverify.c. - Every issuer (each intermediate parent and the trust-anchor root) must assert
basicConstraints cA = TRUE(RFC 5280 6.1.4); a non-CA certificate cannot be used as an issuer to forge downstream certificates —pathvalidate.cviaquic_x509_is_ca. The leaf is not required to be a CA. - A certificate's inner
tbsCertificate.signatureAlgorithmmust equal its outersignatureAlgorithmOID (RFC 5280 4.1.1.2); a mismatch is rejected as malformed —chainverify.c. - The DER parser bounds every length (only
0x81/0x82long forms, nested lengths range-checked) so malformed encodings cannot over-read or exhaust the stack —der.c,derseq.c,derval.c. - Caller responsibility: certificate validity-time (
quic_x509_validity_ok) and hostname/SAN matching (quic_x509_san_matches) are implemented but require the current time and the expected hostname from the application; the SDK does not call them itself.
- Anti-amplification is enforced in the core event loop: a server does not send
more than the allowed multiple of received bytes before address validation
(RFC 9000 8.1) —
antiamp.c,evloop.c. - Retry tokens and stateless-reset tokens are HMAC-derived and compared in
constant time —
retrytoken.c,sreset.c. - Flow control rejects data past MAX_DATA / MAX_STREAM_DATA / MAX_STREAMS with
the correct transport error —
flow.c,stream_flow.c,streams.c. - QPACK decoding rejects output overflow, guards Huffman/integer decoding
against overrun, and bounds the dynamic table —
huffman.c,integer.c. - Version downgrade is detected (chosen version vs. server-reported) and
connection IDs are generated from the RNG —
version/downgrade.c,cidgen.c. - varint and frame parsers bound every read against the remaining buffer.
- Caller responsibility: HTTP/3 request-rate limiting (Rapid Reset, CVE-2023-44487) and Retry-token time-to-live are application-layer concerns; the SDK provides the mechanism (RST handling, HMAC integrity) but not the policy.
src/links no libc: the freestanding-ffreestanding -nostdlibbuild is the proof. Nomalloc/free(all buffers are fixed-size), noprintf/scanffamily, nogetenv/system/exec, nosignalhandlers, and no libc string functions — each verified absent from production sources.
Next: Syscalls — every kernel call the SDK makes. (all docs)