Skip to content

Support more than one TLS cert - #651

Draft
michalhosna wants to merge 5 commits into
mainfrom
mh/tls-multi-cert-sni
Draft

Support more than one TLS cert#651
michalhosna wants to merge 5 commits into
mainfrom
mh/tls-multi-cert-sni

Conversation

@michalhosna

@michalhosna michalhosna commented Aug 27, 2026

Copy link
Copy Markdown
Member

Draft, up for visibility, more my passes to come. Currently, very non-human comments and code everywhere.

Fixes #45

Missing picoquic support, see #562


This change is Reviewable

TempYamlFile and TempFile were separate copies of the same unique-path
construction, one per header that needed a temp path. TempDir.h holds the
one copy plus the TempDir the cert-directory tests need.

CertTestUtils.h carries the self-signed cert builder that PKCS#12 bundles
and PEM pairs both start from; the header notes why folly's
TemporaryDirectory/TemporaryFile are not usable here.
MoqxRelayServer and MoqxQmuxRelayServer each carried their own copy of the
proxygen sample's context setup, and the mvfst copy had grown a third
variant for PKCS#12 material. The three drifted independently; a knob added
to one silently did not apply to the other listener type.

makeCertManager is also the seam an alternative cert source plugs into
(HSM/KMS): implement fizz::server::CertManager and hand it to the
CertManager overload. The rationale for keeping selection in-process is on
the declaration.
BREAKING: a listener setting insecure: true together with cert_file,
key_file, or pkcs12_file now fails config load. It previously warned and
served the compiled-in development certificate, so a deployment that
believed it had real TLS only found out from production traffic.

The two scripts that render cert paths from env diverge, because what a
set path means differs. scripts/moqx-run.sh synthesizes one from DOMAIN
whether or not the operator asked for it, so --insecure clears it.
docker/entrypoint.sh defaults it to empty, so a value there is one the
operator set: it exits rather than making the silent swap the config now
refuses. entrypoint.sh canonicalizes MOQX_INSECURE first: YAML accepts
yes/on/1 as true, and the shell tests around it compare against the
literal, which also left the picoquic listener enabled under a spelling
the config then rejected.

A hand-written config setting both needs the cert source removed.
A relay fronting several hostnames could only present one certificate, so
every name had to share a SAN list and every renewal touched all of them.
tls.fizz.cert_dir serves a directory of <base>.pem/<base>.key pairs and
picks one per connection from the client's SNI; cert_file/key_file or a
PKCS#12 bundle, if also set, stay on as the fallback.

Certificates and keys are read on the scanning thread, never on a
handshake. fizz's cert-selection hook is synchronous, so a deferred read
would land on the connection's IO thread and one slow cert_dir mount would
stall every handshake sharing it. Startup is strict and a rescan is not:
the reasoning for each retention rule is in docs/config.md.

insecure: true rejects fizz.cert_dir like any other cert source, and warns
for the rest of the block: ignoring a cert source serves something weaker
than the operator configured, ignoring a reload interval does not.

ListenerTlsConfig carries the fizz-only options so admin TLS, which shares
TlsConfig, cannot pick up SNI behavior by construction. picoquic does not
use fizz and rejects the block.

Listeners resolving to the same cert source share one SniCertManager: one
scan, one rescan thread, one set of loaded keys.
Each process picked a random ticket seed at startup, so TLS resumption
died on restart and never worked across relay instances behind one VIP.
Pointing every relay at the same seeds file fixes both: the first seed
encrypts, every listed seed still decrypts, so a rotation is a prepended
line and a restart.

The format is moqx's own, not nginx's or HAProxy's. Seeds are validated
per line at config load rather than left to fizz, which rejects a set with
any sub-32-byte member wholesale and would otherwise leave the ticket
cipher silently secretless — no tickets issued, no resumption, no error.

The decoded seeds are secret material: the /config dump carries only how
many are loaded, and the file buffer is wiped after parsing.
@michalhosna michalhosna changed the title Support more then one TLS cert Support more than one TLS cert Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TLS cert loading / managment

1 participant