ureq icon indicating copy to clipboard operation
ureq copied to clipboard

seeking some thoughts on how to generically return peer Certificate

Open mcr opened this issue 3 years ago • 7 comments

rtls has

pub fn peer_certificates(&self) -> Option<&[Certificate]>

and provides a Certificate definition.

ntls has:

pub fn peer_certificate(&self) -> Result<Option<Certificate>>

and mbedtls has:

pub fn peer_cert(&self) -> Result<Option<&MbedtlsList<Certificate>>>

(with three different definitions for Certificate!)

I'm thinking that it would make sense to add this as a trait for ReadWrite. But, the return value just can't work sensibly at this point, so I am looking for some other way.

https://docs.rs/mbedtls-sys-auto/2.26.1/mbedtls_sys/struct.ssl_session.html#structfield.peer_cert https://docs.rs/rustls/latest/rustls/struct.ClientConnection.html#method.peer_certificates https://docs.rs/native-tls/0.2.8/native_tls/struct.TlsStream.html#method.peer_certificate https://docs.rs/native-tls/0.2.8/native_tls/struct.Certificate.html

mcr avatar Jan 31 '22 18:01 mcr

So if I understand this correctly. You want to extract the server certificate from the established TLS connection? And just because I'm curious, is this to do certificate pinning?

I think currently in ureq this wouldn't be possible. There are two reasons:

  1. The ReadWrite trait is used as a trait object, which hides the concrete types. To get the concrete type back, one needs to have access to that concrete type, and for rustls that's isolated away in the private type RustlsStream.
  2. Stuff like Stream (which in turn abstracts ReadWrite), is not accessible from the public API.

If the use case is something like certificate pinning, I think the easiest way would be to do an intial manual connection using rustls/native-tls or mbedtls to extract the cert.

algesten avatar Jan 31 '22 20:01 algesten

So if I understand this correctly. You want to extract the server certificate from the established TLS connection? And just because I'm curious, is this to do certificate pinning?

To very belatedly reply...

I think currently in ureq this wouldn't be possible. (reasons)

Yes, I have now been down this rabbit hole.

I'm implementing RFC8995. (Yes, I'm a primary author). I've previously implemented in Ruby. Yes, we what we do when we construct the voucher request is a form of certificate pinning. The client witnesses the server certificate, is unable to check it (it might be self-signed), and then sends a signed request through the server to a third party that vouches for that server. So the witness and signed request should occur on the same HTTP/1.1 connection.

At this point, I'm writing code that is living inside the ureq crate. I have some of it working, but of late I have mbedtls crate issues that are not yet resolved. Once I've finished, I will return to ureq and suggest some interfaces/traits that will help me. I think that ultimately, ureq needs to split up "making connections" from "processing HTTP" into to workspaces.

mcr avatar Mar 24 '22 19:03 mcr

Sounds good! Looking forward to suggestions.

lolgesten avatar Mar 25 '22 07:03 lolgesten

I think it would be hard to provide a Certificate object in a way that abstracts over native-tls, rustls, and mbedtls, since they each have their own internal way of representing a certificate.

Also, I suspect offering a peer_certificate accessor on ReadWrite would encourage bad practices like manually implementing certificate verification post-connection rather than pre-connection.

It sounds like the most generically useful thing ureq could add to allow you to solve your problem would be:

  • Provide a way for user code to "take over" a connection from a Response object, so it can read and write on that connection indefinitely (this is useful for some other use cases as well).
  • Provide a way to "unwrap" a dyn ReadWrite into the concrete underlying type. Right now that's not possible, but judging from this blog post, we could add an as_any to the ReadWrite trait (breaking change, unless we make it optional), and user code could call that, then downcast the resulting Any to the appropriate concrete type.

jsha avatar Mar 27 '22 06:03 jsha

I think it would be hard to provide a Certificate object in a way that abstracts over native-tls, rustls, and mbedtls, since they each have their own internal way of representing a certificate.

I haven't looked at the representations. Would it not be possible to get like PEM bytes out as a unified front?

algesten avatar Mar 27 '22 11:03 algesten

yes, PEM (or DER) would certainly work. For things that want to pin the server cert (TOFU), that's actually enough: you don't even have to evaluate the bytes, as long as you are just doing memcpy. (I"d rather DER than PEM actually)

mcr avatar Mar 28 '22 22:03 mcr

hi :wave: I've been eyeing on this issue since February - and started toying with a branch today: https://github.com/pldubouilh/ureq/commit/ca77cd7aaa71dd925213cf398337dd481061f1c4

It uses a generic callback through agent, à la middleware, that's executed upon the connect, before we actually perform the request. The signature of the cb is fn cb(cert: Option<Vec<u8>>) -> Result<(), Error> where cert is a generic byte array that holds the cert, and returning an error will stop the processing of the request.

I only implemented rustls - and it returns the peer_certificate that's DER-encoded X.509.

the commit is still fairly messy and there's a few more things to do, but would something in this vein be suitable ? if so I can finish up the work and open a PR. cheers !

[edit] I moved the signature to fn cb(cert: Option<Vec<Vec<u8>>>) -> Result<(), Error> so that we return the whole chain in binary format directly.

pldubouilh avatar Apr 02 '22 18:04 pldubouilh