Communicating result of `verify_client_cert` with application
Checklist
- [x] I've searched the issue tracker for similar requests
Is your feature request related to a problem? Please describe.
We have a ClientCertVerifier instance for which we would like to extract the result of the call to verify_client_cert. More specifically, we need to know if any Error occurred during cert verification, so that we can decide whether to proceed with the connection later on.
Currently, we do this by setting up a new ClientCertVerifier for each connection, and making it so this verifier shares a piece of state with some other code that makes these decisions (it relies upon other state to do this). This means we need to set up a new ServerConfig to pass into the Acceptor API for each connection, rather than keeping a handful of pre-built configs around.
Describe the solution you'd like
I was wondering if there's a better way to do this; either an API I'm missing, or a way to inspect specifically the result of client verification. This issue's comment seems to describe a similar situation, and hints at the fact that there is currently no better solution (?).
Alternatively, I considered having a single ClientCertVerifier and creating a cache with client cert verification results, but the verify_client_cert API does not provide me with a way to uniquely identify the current handshake such that I can retrieve the state for the resulting stream elsewhere.
More specifically, we need to know if any Error occurred during cert verification, so that we can decide whether to proceed with the connection later on.
Can you more clearly describe why this is problematic for your use case? It seems like building separate ServerConfigs per Acceptor is a decent pattern to support this. You can also use the peer_certificates() API to inspect the client's certificates after the connection is established.
Thanks for the pointer, I'll take a look at that API. We might be able to use it to postpone cert verification in cases where we need external input to decide what to do with the result of verification.
To my original point;
Can you more clearly describe why this is problematic for your use case? It seems like building separate ServerConfigs per Acceptor is a decent pattern to support this. You can also use the peer_certificates() API to inspect the client's certificates after the connection is established.
It's not problematic per se, I wanted to make sure, first and foremost, that this is the intended solution.
It does feel a bit wasteful, because apart from the ClientCertVerifier, all of the fields of our dynamically created ServerConfigs are identical, and the builder interface does not allow setting some of them before setting the ClientCertVerifier. Additionally, we need to set up shared state to communicate the verification result for each incoming connection, rather than managing the shared state centrally, or being able to just attach a piece of non-shared state to each connection we create.
Currently, we do this by setting up a new
ClientCertVerifierfor each connection, and making it so this verifier shares a piece of state with some other code that makes these decisions (it relies upon other state to do this). This means we need to set up a newServerConfigto pass into theAcceptorAPI for each connection, rather than keeping a handful of pre-built configs around.
For what it's worth, this is the approach that rustls-openssl-compat takes to solve the same problem.
I have a, maybe, more concrete list of requirements that seems to match the above problem description. In short, I'm trying to have an application implement an authentication style of .ssh/authorized_keys (multiple differing sets of allowed credentials, over the same incoming socket, bounded by per-cert allowed actions). On the face this seems rather simple within the theoretical TLS flow but the trait makes it complex.
-
When accepting a client certificate for connection establishment, the requirement lists that we should validate against a list of effectively permitted client certificates varies by the SNI and alpn; not only on whether the certificate is consistent with the PKI as a whole. The current trait situation feels comparable to as-if
ServerCertVerifier::verify_server_certdid not receive theserver_nameargument. -
Partially addressed with
peer_certificatesbut it fits the original reqest: Later on, the application layer is adjudicating over some request permissibility based on the client authorization tied to the connection. Aspects of the exact certificate may play a role here that are outside the scope of verifying the chain itself, e.g. inspecting application-specific additional claims within the certificate that had been used and this necessarily happens at an unspecified time outside the connection establishment sequence. With the current architecture any client data that should be associated to the connection must be extracted into theClientCertVerifieritself so that it is still around.
In terms of possible changes, maybe it could be the role of what is currently ResolvesServerCert to configure more connection-specific state (such as a concrete, separate ClientCertVerifier instance) rather than merely filling the CertifiedKey chain.
Have you looked at the Acceptor API? That let's you provide a ServerConfig based on the same ClientHello type that is used as input for ResolvesServerCert::resolve().
I'm using it through quinn so unfortunately, no. Maybe that should be escalated upstream since its Incoming does not expose it in this way. Having a sans-IO state machine is however in general a design decision that should address many of the issues raised here, so thanks for making me aware of it.
Right -- that would be https://github.com/quinn-rs/quinn/issues/2024.