quinn-rs / quinn

Async-friendly QUIC implementation in Rust
Apache License 2.0
3.57k stars 364 forks source link

Unable to verify Let's Encrypt certificate chain #1799

Closed externl closed 2 months ago

externl commented 2 months ago
rustls = "0.21.10"
quinn = "0.10.2"
webpki-roots = "0.26.1"

Hello, I have a QUIC server, not Quinn, (hosted at hello.icerpc.dev:4062 in case someone wants to try) configured with a Let's Encrypt certificate. I'm unable to establish a TLS connection to this sever with Quinn despite being able to create a successful connection in C# and Go.

connect failure: TransportError(Error { code: Code::crypto(30), frame: None, reason: "invalid peer certificate: UnknownIssuer" })

My client is configured as folllows:

let mut root_store = rustls::RootCertStore::empty();

root_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
    rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
        &*ta.subject,
        &*ta.subject_public_key_info,
        ta.name_constraints.as_deref(),
    )
}));

let mut client_crypto = rustls::ClientConfig::builder()
    .with_safe_defaults()
    .with_root_certificates(root_store)
    .with_no_client_auth();

client_crypto.alpn_protocols = vec![b"icerpc".to_vec()];

let client_config = quinn::ClientConfig::new(Arc::new(client_crypto));

let mut endpoint = quinn::Endpoint::client("0.0.0.0:0".parse().unwrap())
    .expect("unable to create endpoint");

endpoint.set_default_client_config(client_config);

let host_socket_addr = lookup_host("hello.icerpc.dev:4062")
    .await
    .expect("unable to resolve dns")
    .next()
    .expect("no dns records found");

let conn = endpoint
    .connect(host_socket_addr, "hello.icerpc.dev")
    .expect("bad socket address")
    .await
    .expect("connect failure");

I've also tried downloading the Let's Encrypt Root CA and using it specifically, same error.

I implemented a custom verifier and noticed an oddity. The list of intermediates certificates is empty.

struct SkipServerVerification;

impl SkipServerVerification {
    fn new() -> Arc<Self> {
        Arc::new(Self)
    }
}

impl rustls::client::ServerCertVerifier for SkipServerVerification {
    fn verify_server_cert(
        &self,
        _end_entity: &rustls::Certificate,
        _intermediates: &[rustls::Certificate],
        _server_name: &rustls::ServerName,
        _scts: &mut dyn Iterator<Item = &[u8]>,
        _ocsp_response: &[u8],
        _now: std::time::SystemTime,
    ) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
        println!("intermediates: {:?}", _intermediates);
        Ok(rustls::client::ServerCertVerified::assertion())
    }
}

I wrote a similar client in Go and C# and was able to create a verified connection with a valid certificate chain.

https://github.com/quinn-rs/quinn/issues/1203 seems related but there was no detailed resolution.

Just wondering if anyone has some insight to what I might be doing wrong or have misconfigured.

djc commented 2 months ago

Can you reproduce this using a TCP connection using basic rustls (without QUIC/Quinn in the loop)?

externl commented 2 months ago

Thanks @djc, pretty sure I just figured out the issue. It's unrelated to Quinn. Compared my TCP server which uses the same setup. Looks like, for some reason, the QUIC server is not providing the full chain to the client.

This works in Go and C# since the intermediate certificate is contained in their default cert store.

djc commented 2 months ago

Glad to hear you were able to figure it out!

Ralith commented 2 months ago

Using rustls with https://github.com/rustls/rustls-platform-verifier may provide more consistent results; this will be the default in Quinn's next release