sfackler / rust-native-tls

Apache License 2.0
467 stars 196 forks source link

programmatic control over certificate validation #108

Open izolyomi opened 5 years ago

izolyomi commented 5 years ago

I'm trying to use native-tls in a non-HTTP scenario with self-signed certificates building a mutually authenticated peer to peer connection. All I want is essentially a Diffie-Hellman key exchange and an encrypted channel. During the handshake I'd like to avoid the usual PKI trust chain and let peers decide programatically if they trust the certificate of the other party.

To achieve this, the usual way is to provide a hook for a callback function where certificate of the peer can be validated. In Java you can set a custom TrustManager while initializing your SslContext. Also, crate rustls nicely supports with ServerCertVerifier::verify_server_cert(). I'd like to have the same programatic control over certificate validation using native-tls + tokio-tls without blocking, but unfortunately this library doesn't seem to support this so far.

Would it be hard to add such a callback feature also to native-tls?

sfackler commented 5 years ago

It would be possible to make a callback function, but creating APIs that expose all of the information about the certificate chain that implementations might want would be pretty painful I think. What would be easier is to have higher level verification methods - for example it seems like your use case would be served by certificate pinning, right?

izolyomi commented 5 years ago

I think our use case is different from pinning, we'd like to allow any IP/host for any certificate. In our P2P use case we initiate a connection with an expected public key and we'd like to make sure we're handshaking with that specific public key regardless of network details of the peer.

sfackler commented 5 years ago

That's what certificate pinning is - you pin to a single (or set of) specific identity.

izolyomi commented 5 years ago

Sorry, seems I'm not too familiar with this terminology yet. Thank you for clarifying, pinning is what we'd need then. Note: we don't know the certificate in advance, only the public key, I hope this is not limiting a possible solution. Ok, would pinning also be technically hard to be included?

wigy-opensource-developer commented 5 years ago

In many cases we do not even know the public key itself, just its hash (github.com/multiformats/multihash) in a p2p network connection. So I can imagine a callback which we parametrise with a hash before the handshake and then below the hood the handshake checks if the public key is hashed to that value.

sfackler commented 5 years ago

Yeah that's definitely implementable.

elbaro commented 5 years ago

Example:

fn callback(cert) -> Result<(), Error> {
    match default_verifier(&cert) {
        Ok(()) => Ok(()),
        Err(InvalidCert) => Err(InvalidCert),
        Err(NotTrusted) => {
            let fingerprint = hash(&cert);

            let keys = read("~/.ssh/known_hosts");
            if keys.contain(&fingerprint)  { return Ok(()); }

            println!("Do you trust this entity? [Y/n]");
            println!("Fingerprint: {}", &fingerprint);
            let response = prompt_yn();
            if response { Ok(()) } else { Err(NotTrusted) }
        }
    }
}

If exposing an entire cert is difficult, can you provide a hash of cert to callbacks as a starter?

WhyNotHugo commented 6 months ago

I'd also find this useful. My use case is configuring the fingerprint of a trusted certificate, and I'd want to validate if a remote certificate matches the fingerprint or not.