Skip to content

platform-verifier cannot verify iroh's own default relays on Windows (trailing-dot hostnames) #4448

Description

@yukimemi

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: NotValidForName

Every 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:

  1. Nothing here — wait for rustls-platform-verifier to strip the root
    label, then bump.
  2. Normalise the server name where the relay URL becomes a ServerName,
    so the trailing dot never reaches the verifier.
  3. 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-verifier 0.7.0
  • Reproduced against the default presets::N0 relay map

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      ❗ Blocked

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions