2020 is the year where I want proof-of-possession tokens to become reality. Mutual TLS seems to be the only feasible way to do that today. So here’s another post about it….
This is a two-part post. In this post we will have a look at server-setup to support MTLS, and in the next part we will have a look at ASP.NET and IdentityServer configuration to add support for proof-of-possession access tokens.
The MTLS spec defines two big features:
- Strong client authentication using a TLS X.509 client certificate
- Binding access tokens to a TLS X.509 client certificate
Both features can be used either together – or individually.
The spec also allows for two types of client certificates:
- PKI Mutual-TLS Method: This method assumes an a-priori established trust root and chain-trust certificate validation
- Self-Signed Certificate Mutual-TLS Method: This method allows establishing trust with a specific certificate, e.g. via the thumbprint
Another technicality is how you are planning to host your MTLS endpoints. Since client certificates get negotiated at connection time, you cannot simply “MTLS enable” your complete web application, since this will have side-effects on other endpoints (e.g. your UI) that don’t need client certificates. This might result in the browser showing “certificate picker” dialogs, which is not desirable.
IIS, e.g. allows to configure the MTLS requirement on a path basis, e.g. /connect/mtls/*. This causes a re-negotiation of the connection, which (I was told) is not optimal, and also not a thing anymore in HTTP/2 going forward. Other servers like Nginx encourage hosting the MTLS endpoints on a different (sub) domain, which seems cleaner to me.
After a long discussion on the OAuth mailing list, the spec added an additional metadata entry called mtls_endpoint_aliases, which allows to point to arbitrary addresses to accommodate different hosting styles.
Over Christmas holidays I was working on the MTLS updates for IdentityServer and tried to setup a test system. This was a more complicated than I thought, so I had to do some additional research which infrastructure has the best support. Here are my results.
Azure App Service
I only had a quick look, but from that, my conclusion was that this only allows to configure MTLS application-wide, and also only supports the PKI MTLS method. This is a show stopper unfortunately.
Update: I got some feedback that this is a bit more flexible than I thought – but not much. Apparently Azure allows to configure exclusion paths for the MTLS config – this sounds like a configuration nightmare to be honest – but is doable I guess. This seems to be the only docs though…
Kestrel allows for both the PKI and self-signed methods – that’s good. But again, only app-wide MTLS settings are supported.
IIS does support scoping the MTLS endpoints to certain paths – but as mentioned above, this does not seem to be favoured approach anymore. The other problem is, that it seems that IIS only supports the PKI MTLS method, or IOW you always need to establish trust to a CA upfront at the OS level – that’s not what I wanted. I had several conversations with Microsoft people about that, and no-one had a solution to that problem. If you know how that would work, I am interested in updating this document.
I never worked with Nginx before, so this was kind of my last resort option. But man, I was shocked how easy it was to get started – and lo and behold, Nginx ticked all the boxes!
With Nginx you would typically setup two sites (or servers in Nginx speak) pointing to the same ASP.NET Core host, e.g. https://identityserver.io as the main site, and https://mtls.identityserver.io to isolate your MTLS endpoints.
For the general ASP.NET Core setup for Nginx see here.
For the MTLS endpoints, you set-up an additional server that has the following config entries:
In this particular case, I accept all MTLS certificates (that’s the no_ca option). You can also configure trusted CAs at the Nginx level.
You then need to configure Nginx to pass the client certificate to your ASP.NET Core application via a header:
proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
This will send the certificate as a URL-encoded string on the X-SSL-CERT header, which you then pickup in ASP.NET Core using the certificate forwarding middleware.
I am NOT an Nginx expert – if anyone has some comments on this setup, please let me know!
Hope this helps – in the next post I will have a closer look at the required ASP.NET Core and IdentityServer configuration.