Closed paulhilliar closed 1 week ago
When an SSL handshake fails the HTTP request has not been sent yet. We have no information about the scheme, host/port, path, query unless the HTTP request has been sent and fully parsed.
The client cert validation occurs deep within the JVM, we have no way to get involved in that process. From a Java I/O point of view, the connection failed to be established when the cert fails.
Thanks. Would it be possible by not validating the client certificate up front but doing it later (manually triggered) in a filter?
Certificate validation is done in the JVM's SSLEngine, we cannot validate it later in our own code. It is either a valid certificate and the connection proceeds, followed by the HTTP request. Or the certificate is invalid and the connection fails to be established.
@paulhilliar an alternative could be to install a TrustManager
that accepts all certificates, even if they are invalid.
Then you wait for the request.
Then you perform certificate validation, but to be honest I have not tried and I have no idea if it would be possible to retrieve the cryptographic material using the JDK APIs to perform the validation later.
It may require some work on your part to figure this out.
Just curious, what was the rewrite_by_lua
code doing, exactly?
@sbordet thanks for the suggestion. I've made this work in a standalone project. I'll tidy it up and publish it shortly in case it helps others. Overview:
No need for SslHandshakeListener
Set SslContextFactory.Server.setWantClientAuth(true); (grabs client cert if present)
Set SslContextFactory.Server.setNeedClientAuth(false); (if no client cert then establish a connection anyway)
Override the trust manager with a 'trust anything' trust manager (if client cert then trust it no matter what)
SSL Session will be negotiated and any client cert present will be 'trusted' enough to make a SSL connection
Use SecureRequestCustomizer so that the SSL session ends up in the request
Handler gets the SSL session from the request and (if appropriate for that URL) validates it using the 'real' trust manager. If there is no client cert or an untrusted client cert then it gets rejected at this point with 401/connection:close response header.
You can argue that it's a security hole to allow the client to establish an insecure connection at all but, given that different URLs will have different MTLS enforcement (in our product at least), it's unavoidable I think.
@paulhilliar sounds a reasonable approach.
We won't do this in Jetty, but if it works for you, then great.
If you want to publish a solution, please add a comment with the link to the solution, so that others may benefit from your work.
Thanks!
Thanks for the help here. There's no expectation to do this in Jetty but it's nice to figure out a way to bend the rules to get this done.
I tidied up the Jetty client mtls example project in case it helps others: https://github.com/paulhilliar/jetty-client-mtls-play-area
P.S. @sbordet you asked what is happening in rewrite_by_lua - it seems that ngx.var.uri is available at that point. We then subsequently, from within ssl_certificate_by_lua, call ngx.ssl.verify_client
It's difficult for me to tell because, well, it's OpenResty/Lua and I wasn't around when this bit was written but my best guess is that the client verification it postponed (like we have been trying to do here) until we explicitly call it
Jetty Version 12.0.13
Jetty Environment ee10
Java Version 21
Question We have Jetty set up with SslContextFactory.Server.setWantClientAuth(true) in order to get Jetty to ask for client certificates.
But when the SSL validation fails, we need to alert the customer differently depending on which URL was being attempted.
Using connector.addBean(new SslHandshakeListener() { .... is fine to give me the callback that the client cert was rejected, but it doesn't give me the request that was being attempted.
I'm assuming this is because you need to negotiate a connection before the actual request can be made?
So is it possible to either
I realise the thing I'm asking for here isn't the cleanest architecturally. We are migrating from an OpenResty-based solution to Jetty and we manage this in OpenResty/Nginx by hooking into rewrite_by_lua (https://openresty-reference.readthedocs.io/en/latest/Directives/#rewrite_by_lua).
Any help or advice would be much appreciated. If it helps then I could put it into a replication project but there didn't seem much point given that I'm not even sure this is possible in Jetty.