Mutual TLS and Proof-of-Possession Access Tokens – Part 1: Setup

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…

Pure Kestrel

Kestrel allows for both the PKI and self-signed methods – that’s good. But again, only app-wide MTLS settings are supported.

IIS

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.

Nginx

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:

server_name  mtls.identityserver.io;
ssl_verify_client optional_no_ca;

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.

This entry was posted in ASP.NET Core, IdentityServer, OAuth, OpenID Connect. Bookmark the permalink.

8 Responses to Mutual TLS and Proof-of-Possession Access Tokens – Part 1: Setup

  1. driedas says:

    Hey Dominick,
    actually, on azure app services you can specify a list of certificate exclusion paths, however the matching algorithm is extremely simple, it doesn’t support scenarios like having the root of the site not require a certificate but only a specific sub-endpoint require one. If you specify ‘/’ as the exclusion path, there is no longer a way to enable it on a single endpoint lower in the hierarchy only (e. g. /connect/token/mtls).
    However, a setup like having TLS set to required globally, and having the non TLS endpoints excluded (/connect/token, /.well-known/openid-configuration) works (obviously, on a host dedicated to hosting identity server only). Also, I didn’t encounter an issue with self signed certificates, we are using those exclusively (no hassle with a PKI infrastructure required)

  2. Thank you very much for this post Dominick.
    According to this “wish-list” it is planned for Application Gateway, https://feedback.azure.com/forums/217313-networking/suggestions/9379902-allow-mutual-ssl-auth-on-application-gateway. But that was said over a year ago.

    Regards Hans

  3. Thanks driedas! Updated the post.

  4. Vishal Rastogi says:

    Great post. But is it fine to not do the certificate chain validation when doing the ssl handshake and do it at the time of verifying the certificate against the client? Would this conformatant to open banking standards as well?

  5. jkaffenberger says:

    Hey Dominick, wanted to let you know I was able to defer mTLS negotiation to an ASP.NET Core proxy built with Microsoft’s new YARP package (https://github.com/microsoft/reverse-proxy). I only needed to write a small amount of code to extract the client certificate from the request, encode it, and forward it in the X-SSL-CERT header.

    No more cludgy certificate selector in my OAuth server!

Leave a comment