Mutual TLS Authentication

When a TLS connection is set up, it is possible to authenticate both ends of the communication channel: the server as well as the client. When both parties authenticate it is called a mutual TLS authenticated connection. Being the transport layer that the Curity Identity Server builds its application layer access security features on, a mutual TLS authenticated connection has properties that can be re-used for application level security.

The process of issuing tokens can benefit from mutual TLS authentication in multiple ways. First of all, a client that is configured in Curity Identity Server is required to authenticate to different endpoints of the server, for example to the token-endpoint. If a client is able to present valid credentials (i.e. a valid X509-certificate) as part of setting up a mutual TLS authenticated connection to the server, Curity Identity Server can then take over and verify whether the same credentials can also authenticate the configured client of the token profile.

On top of authenticating the client at the token endpoint, Curity Identity Server can also issue a token that is actually bound to the certificate that the client used to authenticate the token request. Once the client uses the same client certificate to authenticate to a resource server when it presents the token there, that particular resource server can verify that the same client certificate was used when the token was requested as when the request to the resource server was made. In other words: the client can prove to the resource server that the token being used was issued to it, as the token is bound to the same client certificate that is used for the TLS connection between the client and the resource server.

This last property is very powerful, because it prevents a leaked token from being used by a party that is not in possession of that private key, as such a party would not be able to setup a mutually authenticated TLS connection. This type of authentication is based on what is called Proof-Of-Possession: the client can proof that it has access to the private key, but the proof doesn’t require the client to hand over the private key itself.

To ensure that different entities in this protocol can understand each other, the use of mutual TLS authentication is being standardized. At the time of writing, there is a stable draft specification, which is expected to be finalized without many changes. Curity Identity Server implements this draft specification.

TLS termination

A TLS connection is set up from a source, terminated at its destination and protects the connection between these two entities. While this sounds pretty obvious, the most common deployment of a security server is not one where the client makes a connection directly to the server. In other words, the TLS connection that the client initiates is not always terminated at Curity Identity Server, but instead it is terminated by one or more intermediate systems (e.g. loadbalancers, reverse proxies, firewalls, etc.) and verified before it is being forwarded to Curity.

../_images/mutual-tls-by-proxy.png

Fig. 122 TLS connection terminated by an intermediate

In order to support mutual TLS authentication in these situations, Curity can be configured to receive client certificate information through HTTP request headers instead of directly from the TLS connection. This requires a cooperation between the reverse proxy and Curity that needs to be carefully designed. Further details can be found in the Generic Reverse Proxy Server Setup section.

The alternative to mutual TLS by proxy is direct TLS, where the TLS connection is terminated by Curity. As this exposes Curity directly to the client, it must be considered very carefully to use this in any other environment than for development or testing.

../_images/mutual-tls.png

Fig. 123 TLS connection terminated by Curity

Binding certificates to tokens

Once a token request is based on a mutual TLS authenticated connection, the token will automatically be bound to the client certificate. This is achieved by including the x5t#s256 thumbprint of the client certificate as a confirmation claim to the Access Token and, if this is also issued, the Refresh Token.

Listing 214 Claims of an Access Token from introspection
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
    "sub": "mutual_tls_client",
    "purpose": "access_token",
    "iss": "https://localhost:8443/dev/oauth/anonymous",
    "active": true,
    "token_type": "bearer",
    "client_id": "mutual_tls_client",
    "aud": "mutual_tls_client",
    "nbf": 1542896887,
    "scope": "",
    "cnf": {
        "x5t#S256": "FjeHcvJwiHXlr8dgnP7UvLQ7dLLMTe_3SgMYMuEpekc"
    },
    "exp": 1542897187,
    "delegationId": "d0d2bc6b-55f9-4412-83bc-a42b992e15c1",
    "iat": 1542896887
}

The purpose of this binding is for the receiver of the token to be able to verify that it is the same client that presents the token, as the client that the token was issued to. To make this work, the same client certificate must be used to request a token at the token endpoint, as to request a protected resource from a resource server.

Please note that this also applies to using the refresh token: when a client uses the Refresh Token to request a new Access Token, the request to the token-endpoint must be authenticated using the same client certificate that was used when that Refresh Token was issued. Also note that because the tokens are bound to the thumbprint of the certificate, using a newly issued certificate with the same Subject DN, will not be accepted, as this new certificate will have a different x5t#s256 thumbprint.

Trusted certificates

A mutual TLS authenticated connection is based on the property that each end of the connection is capable of proving to be in possession of the private key that belongs to the presented certificate. For client authentication, this means that a client certificate identifies the client. A certificate exposes a number of attributes, among which are the issuing party (i.e. the Certificate Authority) and the subject (i.e. the Subject Distinguished Name).

A client in Curity can be configured for mutual TLS authentication, and by doing so there are two options available to indicate how a client certificate can be trusted as a credential to authenticate the client:

  1. Trust by PKI
  2. Trust by pinned certificate

Trust by PKI

Configuring trust by PKI means configuring a Trusted CA (i.e. the issuer) and a Client DN (i.e. the subject) that must match the presented client certificate. The Trusted CA is a reference to a certificate as it is stored in the client-ssl-truststores section of the crypto facility. This issuer certificate must be the actual CA that is stored as issuer in the client certificates. In other words: there are no intermediaries accepted between the presented client certificate and the issuer’s certificate, which means that a certificate chain from the client certificate up to a trusted issuer will be ignored when validating the client certificate.

The Client DN is the Distinguished Name of the presented client certificate. The admin of Curity lets you upload the certificate to extract the value of the Subject DN from it, but a DN that conforms to RFC 2253 is accepted.

Note

Both fields must be configured, except in the case where the client authenticates a user or client when requesting an Initial Token (i.e. an Access Token with the dcr scope). This is a special case, where the actual Client DN might be unknown, but the Trusted CA is known. In this case, the configuration allows you to leave the value for Client DN empty.

Trust by a pinned certificate

In case there is no PKI to assert trust, you can assign individual certificates to a client and by doing so, limit the client to one specific certificate that it can use to authenticate through mutual TLS. This client certificate must be added to the client-ssl-truststores section of the crypto facility, and can be referenced from the client configuration.

DN comparison

When a client provides a certificate, and the DN of the certificate is matched with the configured value, by default the matching is done to either match the DN’s RDNs (both DNs are made up of the same RDNs and the order of the RDNs in each DN is the same), or match the DN’s RDNs in reverse order (both DNs are made up of the same RDNs and the order of the RDNs in each DN is exactly the reverse).

This second match condition is based on OpenSSL’s way of presenting a DN in its text output (i.e. when running OpenSSL like openssl x509 -text -noout <certificate>). This means that the following DNs will result in a positive match:

provided DN configured DN
CN=client,OU=example.com CN=client,OU=example.com
CN=client,OU=example.com OU=example.com,CN=client

When the latter case should not result in a positive match, the server can be started in a strict DN comparison mode. To do so, the Java runtime property se.curity:identity-server:strict-dn-comparison must be set to true when starting the server.

Note

openssl can be instructed to reverse the DN in its output by using the -nameopt dn_rev option, to conform to RFC’s (see the nameopt documentation of OpenSSL). While this works with standard OpenSSL, it does not work with OpenSSL that comes bundled with Mac OSX. To use the nameopt flag with OpenSSL on OSX, you must use a non-Apple version of OpenSSL, e.g. install it through homebrew or build it yourself.

Configuring Mutual TLS

Mutual TLS configuration consists of

1a. (in case of proxy terminated mutual TLS) how to communicate between a reverse proxy and Curity on each profile, or 2b. (in case of direct terminated mutal TLS) how TLS termination should behave on each Server, and 2. trusted certificate credentials of a client

Proxy terminated Mutual TLS

Configuring mutual TLS terminated by a reverse proxy is done per profile. This setup relies on the reverse proxy having verified the presented client certificate as being issued by a trusted CA, as well as that the status of the client certificate being valid (e.g. OCSP and CRL verification is done by the reverse proxy).

Caution

When Curity gets a certificate from a proxy, it will NOT check its status. Instead, Curity relies on the proxy for making these checks (for example through OCSP or a CRL) as part of terminating the TLS connection. Make sure that the proxy takes this responsibility!

The certificate that is accepted, is sent over in a request header to Curity over a connection that can be protected by basic Proxy-Authorization authentication. If neither a User ID nor a Password is configured, the proxy will not be authenticated by Curity.

These settings are configured as part of the Token Service’s Client Authentication configuration.

../_images/mutual-tls-config-profile.png

Fig. 124 Configuration of Mutual TLS settings on a profile

See the Generic Reverse Proxy Server Setup section for more concrete guidance on setting up proxies from specific vendors.

Direct terminated Mutual TLS

It takes three settings to enable mutual TLS terminated by Curity.

First, open the Server configuration through the System -> Deployments settings of the admin. Ensure the server is configured with the https protocol, and open the Advanced settings at the bottom of the configuration screen.

../_images/mutual-tls-config-server.png

Fig. 125 Configuration of TLS termination on a Server

By default the server will be able to accept client authentication on the regular https-port. It is however possible to add a separate port for mutual TLS connections, and leave the main listening port unaffected by any of the mutual TLS functionality.

To configure which client certificates are acceptable, all trusted CA certificates must be whitelisted. These are configured in the Client Trust Stores section of the Crypto Facility. In case no entries are configured, then all the configured client trust stores are valid. Note that all presented certificates must be directly signed by one of the trusted client trust stores, a certificate chain that is sent by the client, that contains certificates between the client certificate and a trusted CA certificate, is ignored when validating trust. This configures the server’s listener capabilities, which is the first part of configuring Direct terminated mutual TLS.

Second, enable client authentication on each endpoint that requires mutual TLS authentication. To do so, open the Profile’s Endpoints settings (from the menu on the left).

../_images/mutual-tls-config-endpoint.png

Fig. 126 Configuration client authentication of endpoints

The Client Authentication setting controls Mutual TLS client authentication, with three options:

  1. Disallow, meaning that no mutual TLS client authentication may be done,
  2. Allow, meaning that mutual TLS client authentication is optional, and
  3. Required, meaning that the endpoint may not be called without mutual TLS client authentication

Make sure to always verify the configuration of Client Authentication on the endpoints of each profile when you enable direct terminated TLS in Curity.

Third, you must enable Mutual TLS as a valid authentication method for Clients of a Token Service. To do so, open the Token Service’s General settings, and toggle the Mutual TLS slider to enable it.

../_images/mutual-tls-config-profile-direct.png

Fig. 127 Configuration of enabling direct terminated Mutual TLS on a Token Service

Note

Note that it is not possible to enable both Mutual TLS and Mutual TLS by Proxy. Make sure Mutual TLS by Proxy is toggled off if you need direct terminated Mutual TLS.

Configuring trust

Once direct Mutual TLS or Mutual TLS by proxy is enabled as a client authentication method, it becomes configurable for a client. When configuring a client’s Authentication Methods, the options mutual-tls and mutual-tls-by-proxy are available.

../_images/mutual-tls-config-client.png

Fig. 128 Configuring trust for a client

Select either the Trust by PKI to set the values for Client DN and Trusted CA, or Trust by a pinned client certificate to set the Client Certificate that the client must use to authenticate from the Client Trust Stores.

See the Trusted certificates section for more on these settings.

Reverse Proxy Server Setup

Generic Reverse Proxy Server Setup

It is always recommended to operate the Curity Identity Server in a secure environment. To create such an enclave that exposes services to the Internet, a reverse proxy is typically set up in front of the Curity Identity Server as a facade. By using a reverse proxy in a Demilitarized Zone (DMZ), private keys and credentials used by the Curity Identity Server can be kept in a private network.

In addition to TLS offloading, mutual TLS client authentication can also be offloaded to the reverse proxy server. In this case, the reverse proxy will perform TLS handshake and validate client certificate. After validation, the client certificate is passed to the Curity Identity Server in a HTTP header (header names in the Curity Identity Server configuration and reverse proxy configuration should match).

To accommodate different types of proxy servers, the Curity Identity Server supports several formats for client certificates:
  • Base64-encoded DER format (default for HAProxy);
  • PEM format with newlines encoded as spaces (default for Apache HTTPD);
  • URL-encoded PEM format (with space encoded as %20, default for NGINX, or +).

Caution

Please keep in mind that, if mutual TLS client authentication is performed on a reverse proxy, the Curity Identity Server will treat all forwarded requests as trusted and perform only limited client authentication on its own. Accidentally turning of client certificate verification on the reverse proxy (or making it optional instead of mandatory) can severely compromise your setup. Also, the Curity Identity Server has no way of telling between HTTP headers from the original request and HTTP headers injected by the reverse proxy server, it is important to filter out headers, that are used for authentication, from incoming HTTP requests.

Another thing to consider, is to use a randomized header name (e.g. X-ClientCertificate-Idsvr-CXVvfqrg) that is kept a secret between the reverse proxy and Curity, so an attacker can not guess the header name that is being used. While it does not replace the need for request header sanitization, it would reduce the risk when request header sanitization somehow doesn’t work as expected.

As an added security measure, the proxy server itself can be configured to perform basic HTTP authentication against the Curity Identity Server. In this case, a Proxy-Authentication HTTP header with corresponding credentials should be injected into requests by the proxy server.

Setting Up NGINX As a Reverse Proxy Server

If you’re running NGINX as a reverse proxy server in front of the Curity Identity Server, your NGINX configuration might look like this:

Listing 215 Example configuration snippet for NGINX before enabling m-TLS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
server {
    listen                  443         ssl;

    ssl_certificate         /path/to/your/ssl/bundle-server.cert.pem;
    ssl_certificate_key     /path/to/your/ssl/server.key.pem;
    ssl_protocols           TLSv1.2 TLSv1.3;

    server_name             server_name id.example.com;

    location / {
        proxy_pass          http://localhost:8443/;
    }
}
To enable mutual TLS client authentication, several things should be done:
  • Enable client certificate validation (lines 8-9).
  • Forward client certificate to the Curity Identity Server (line 15). Header name is assumed to be “X-ClientCertificate-Idsvr”.
  • (Optionally) Add proxy authentication header (line 16).
Listing 216 Example configuration snippet for NGINX after enabling m-TLS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
server {
    listen 443 ssl;

    ssl_certificate         /path/to/your/ssl/bundle-server.cert.pem;
    ssl_certificate_key     /path/to/your/ssl/server.key.pem;
    ssl_protocols           TLSv1.2 TLSv1.3;

    ssl_verify_client       on;
    ssl_client_certificate  /path/to/your/ssl/ca-chain-client.cert.pem;

    server_name             id.example.com;

    location / {
        proxy_pass          http://localhost:8443/;
        proxy_set_header    X-ClientCertificate-Idsvr $ssl_client_escaped_cert;
        proxy_set_header    Proxy-Authorization "Basic dXNlcjpwYXNzd29yZA==";
    }
}

For more advanced mutual TLS configuration options please refer to the NGINX configuration manual.

Setting Up HAProxy As a Reverse Proxy Server

If you’re running HAProxy as a reverse proxy server in front of the Curity Identity Server, your HAProxy configuration might look like this:

Listing 217 Example configuration snippet for HAProxy before enabling m-TLS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
frontend idsvr-frontend
    bind                  0.0.0.0:443     ssl crt /path/to/your/ssl/server.pem
    default_backend       idsvr-backend
    timeout client        50000

backend idsvr-backend
    mode                  https
    timeout connect       5000
    timeout server        50000
    server                idsvr1          localhost:8443 ssl verify none
Steps to enable proxy-based mutual TLS authentication for HAProxy are equivalent to NGINX:
  • Enable client certificate validation (line 2).
  • Forward client certificate to the Curity Identity Server (line 11). Header name is assumed to be “X-ClientCertificate-Idsvr”.
  • (Optionally) Add proxy authentication header (line 12). Authentication token is a base64-encoded “username:password” string.
Listing 218 Example configuration snippet for HAProxy after enabling m-TLS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
frontend idsvr-frontend
    bind                  0.0.0.0:443     ssl crt /path/to/your/ssl/server.pem ca-file /path/to/your/ssl/client-chain.pem verify required
    default_backend       idsvr-backend
    timeout client        50000

backend idsvr-backend
    mode                  https
    timeout connect       5000
    timeout server        50000
    server                idsvr1          localhost:8443                ssl verify none
    http-request          set-header      X-ClientCertificate-Idsvr     %{+Q}[ssl_c_der,base64]
    http-request          set-header      Proxy-Authorization           "Basic dXNlcjpwYXNzd29yZA=="

Setting Up Apache HTTPD 2.x As a Reverse Proxy

If you’re running HTTPD as a reverse proxy server in front of the Curity Identity Server, your configuration might look like this:

Listing 219 Example configuration snippet for Apache HTTPD 2 before enabling m-TLS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Listen 443

<VirtualHost *:443>
    ServerName              id.example.com

    SSLEngine               on
    SSLProxyEngine          on
    SSLProxyVerify          require

    SSLCertificateFile      "/path/to/your/ssl/cert.pem"
    SSLCertificateKeyFile   "/path/to/your/ssl/key.pem"

    # ID Server location
    ProxyPass               "/" "https://localhost:8443/"
    ProxyPassReverse        "/" "https://localhost:8443/"
</VirtualHost>
Steps to enable proxy-based mutual TLS authentication for Apache HTTPD are equivalent to NGINX or HAProxy:
  • Enable client certificate validation (lines 10-11 and 19-20).
  • Forward client certificate to the Curity Identity Server (lines 14 and 28). Header name is assumed to be “X-ClientCertificate-Idsvr”.
  • (Optionally) Add proxy authentication header (line 30). Authentication token is a base64-encoded “username:password” string.
Listing 220 Example configuration snippet for Apache HTTPD 2 after enabling m-TLS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Listen 443

<VirtualHost *:443>
    ServerName              id.example.com

    SSLEngine               on
    SSLProxyEngine          on
    SSLProxyVerify          require

    # Mandatory client certificate verification
    SSLVerifyClient         require

    # Export additional environment variables (SSL_CLIENT_CERT among others)
    SSLOptions              +ExportCertData

    SSLCertificateFile      "/path/to/your/ssl/cert.pem"
    SSLCertificateKeyFile   "/path/to/your/ssl/key.pem"

    # CA certificate bundle to verify client certs against.
    SSLCACertificateFile    "/path/to/your/ssl/ca-cert-chain.pem"


    # ID Server location
    ProxyPass               "/" "https://localhost:8443/"
    ProxyPassReverse        "/" "https://localhost:8443/"

    # Forward client certificate
    RequestHeader set       X-ClientCertificate-Idsvr %{SSL_CLIENT_CERT}e
    # Proxy authentication
    RequestHeader set       Proxy-Authorization "Basic dXNlcjpwYXNzd29yZA=="
</VirtualHost>

Non-Templatized Dynamic Client Registration using Mutual TLS

It is possible for non-templatized clients to register using a DN of a certificate. That DN should be sent on the registration request while calling the Dynamic Client Registration (DCR) endpoint. The field that should be sent in the request is tls_client_auth_subject_dn and it should be a valid subject name. For more information on what is considered a valid subject name feel free to consult the relevant RFC 4514. Mutual TLS specific client-authentication-method settings are only available when mutual-tls is enabled on the oauth profile, and the profile’s settings of mutual-tls or mutual-tls-by-proxy will imply how they are used by the DCR’s settings. The provided DN is accepted without further verification upon registration, and when the client is authenticating using a client certificate, the provided certificate will have to be trusted by a configured trusted-ca. In other words, in order to authenticate the client, the CA of the certificate presented has to be in the list of trusted CAs provided in the configuration.

More information about Mutual TLS Client Authentication and Certificate-Bound Access Tokens can be found in the relevant draft RFC (Section 2.1.2).