platform-verifier cannot verify iroh's own default relays on Windows (trailing-dot hostnames)
Summary
On Windows, enabling the platform-verifier feature and building an
endpoint with CaTlsConfig::system() makes every default relay fail
TLS with NotValidForName — including relays that verify fine with the
default embedded roots.
The cause is that iroh's default relay hostnames are absolute FQDNs, with
the root label's trailing dot (iroh-1.0.3/src/defaults.rs:27):
pub const NA_EAST_RELAY_HOSTNAME: &str = "use1-1.relay.n0.iroh.link.";rustls-platform-verifier passes that name to CryptoAPI verbatim, which
compares aps1-1.relay.n0.iroh.link. against the certificate's dot-less
SAN and returns CERT_E_INVALID_NAME. webpki drops the root label during
name matching and never sees a problem.
I filed the underlying bug as
rustls/rustls-platform-verifier#240 — it reproduces with plain
example.com. and no iroh involved. Opening this separately because the
combination is what makes the feature unusable in practice, and iroh is
where the two halves meet.
Repro
let endpoint = Endpoint::builder(iroh::endpoint::presets::N0)
.ca_tls_config(CaTlsConfig::system())
.alpns(vec![b"my/alpn/1".to_vec()])
.bind()
.await?;with iroh = { version = "1.0.3", features = ["platform-verifier"] }, on
Windows:
ERROR endpoint:actor:reportgen-actor:run-probe{proto=Https relay=https://aps1-1.relay.n0.iroh.link./}:
failed to verify TLS certificate: invalid peer certificate: NotValidForName
WARN endpoint:actor:reportgen-actor:run-probe{proto=Https relay=https://aps1-1.relay.n0.iroh.link./}:
probe failed: Failed to run HTTPS probe: HTTP request failed:
error sending request for url (https://aps1-1.relay.n0.iroh.link./ping):
client error (Connect): invalid peer certificate: NotValidForNameEvery region, on a loop. Dropping back to the default
CaTlsConfig::embedded() makes the same relays connect immediately, so
it is not the certificates and not the network.
Why this matters more than the feature being niche
platform-verifier is exactly what a machine behind a TLS-inspecting
proxy (Cato SASE, Zscaler, any corporate MITM appliance) needs: the
interception CA is in the OS store, and webpki-roots reads neither
SSL_CERT_FILE nor SSL_CERT_DIR, so there is no way to widen trust
from outside a shipped binary. Those users are on Windows more often than
not, and Windows is where the feature does not work.
CaTlsConfig is otherwise well-shaped for this — the escape hatch below
only exists because with_extra_roots is there.
Workaround, for anyone who finds this
Take the roots from the OS but leave the name matching to webpki:
let found = rustls_native_certs::load_native_certs();
let ca = CaTlsConfig::embedded().with_extra_roots(found.certs);rustls-native-certs reads the same stores platform-verifier would
(CryptoAPI on Windows, Keychain on macOS, the OS bundle on Linux), and
honours SSL_CERT_FILE / SSL_CERT_DIR as a bonus. Keeping embedded()
underneath rather than custom_roots also means a host with no CA bundle
at all still binds instead of failing at bind() with
InvalidCaRootConfig.
Possible fixes on this side
Whichever you prefer, and none of them are urgent if the upstream fix lands:
- Nothing here — wait for
rustls-platform-verifierto strip the root label, then bump. - Normalise the server name where the relay URL becomes a
ServerName, so the trailing dot never reaches the verifier. - Document on
CaTlsConfig::system()that it is currently incompatible with the default relay map on Windows. Right now the feature reads as the supported answer for this problem, and following it makes things worse than doing nothing.
Environment
- Windows 11 Pro 26200
- iroh 1.0.3,
rustls-platform-verifier0.7.0 - Reproduced against the default
presets::N0relay map
Source: n0-computer/iroh