Buckle Up Your mTLS With OAuth 2.0 Client Authentication and Certificate-Bound Access

Introduction

Application security is a persistent hot topic in the technology industry. It is quite common to use mTLS in a business-to-business application, where security is incredibly important, and which uses the zero trust security model. mTLS is also popular with microservices and service mesh to ensure that sensitive resources are not accessible to unauthorised services in the network. mTLS is a transport layer authentication protocol.

Working and integrating with highly secured applications I discovered about an additional layer of security on top of mTLS that would make the application truly following zero trust security model. Extending mTLS with OAuth 2.0 Client Authentication improves security for all requests.

In this article I will be explaining:

  • Some of the basics of the mTLS and OAuth 2.0 protocol
  • What are the potential drawbacks with mTLS
  • How OAuth 2.0 Client Authentication and Certificate-Bound Access helps improve security.

After explaining the topics above I will be illustrating how OAuth 2.0 Client Authentication works with mTLS.

Prerequisite knowledge of these topics is not required, but is helpful when you are reading this article:

Introduction to mTLS and OAuth 2.0

mTLS

Mutual Transport Layer Security (mTLS) is a transport layer encryption protocol that authenticates both the client and server in a client-server connection, unlike TLS where only the server is authenticated. It not only authenticates the request but also encrypts the communication so that no third party can intercept the request and read the information from the request.

In a TLS request the server has a certificate, private key, and a public key but the client does not. In case of mTLS both client and server have a certificate and the public/private key. Like the server, the client would also present its TLS certificate and the server would verify the certificate to grants the access.

OAuth 2.0

OAuth 2.0 is an authorization framework that enables third party applications to obtain delegated access to protected resources. OAuth 2.0 protocol has three entities involved:

  1. Authorization server - responsible to authorize the request and grant access token
  2. Resource server - owns and can provide the protected resource
  3. Client - application that wants to obtain the protected resource

The client application communicates with the authorization server to get an access token. The Authorization server validates the request and provides the token. The client can now use this token to request a protected resource from the resource server. The resource server can validate the token locally or in some case requests the authorization server to determine the state of the token and validate the request. Once validated the client is granted with the resource.

Why use OAuth 2.0 with mTLS

mTLS is a highly secure transport layer protocol that protects from the attacks like request smugglingbrute force attacksphishing attacks, and credential stuffing. Since it only provides a transport layer authentication, all the requests sent over the channel is then implicitly granted access assuming the origination is from the authentic client. This opens the possibility for the attackers to inject requests pretending to be from a legitimate client.

In case of a misconfigured server where the server is not configured properly to require a client certificate, the client will not be asked for a certificate and the request will be served silently unauthenticated.

IETF RFC describes OAuth 2.0 client authentication and certificate-bound access using mutual TLS. Mutual-TLS certificate-bound access tokens ensure that only the party in possession of the private key corresponding to the certificate can utilize the token to access the associated resources. Binding an access token to the client's certificate prevents the use of stolen access tokens or replay of access tokens by unauthorized parties.

The specification describes two distinct complementary mechanisms:

  • Mutual-TLS client authentication - To utilize TLS for OAuth 2.0 client authentication, the TLS connection between the client and the authorization server MUST have been established or re-established with mutual-TLS X.509 certificate authentication. The client must include "client_id" parameter while requesting the authorization server. This enables the server to identify the client independent of the content of the certificate.
  • Mutual-TLS certificate-bound access tokens - Mutual TLS client authentication requires you to create a connection using mTLS, to access the token from the authorization server. This only ensures that the access to the auth token is verified using mTLS. Which is, just mTLS and has the same issue that we talked earlier about not having the auth for each request. The second mechanism to use mTLS with certificate bound access token is what we can do to protect from the attacks. The mTLS client authentication can be used together with certificate bound access token to complement it.

Mutual-TLS certificate-bound access tokens

In this section we will look deeper into how mTLS certificate-bound access token works. When a client requests a token from the authorization server using mutual TLS, the server can bind the issued access token to the client certificate. The certificate binding is possible either by embedding the certificate hash as a part of the issued token or through token introspection. The access token can be used at the application layer passed as a HTTP header by the client. This makes sure that each request is protected.

JWT Certificate Thumbprint Confirmation Method

If the access token is represented as a JSON Web Token, this method can be used to associate the token with the certificate. The JWT would include base64url encoded SHA-256 hash of the X.509 Certificate. The JWT contains cnf confirmation method claim. It has x5t#S256 as a confirmation method member that contains the value of the base64url-encoded SHA-256 hash of the X.509 Certificate. The base64url-encoded value must omit all trailing pad '=' characters and must not include any line breaks, whitespace, or other additional characters. An example token would look like this:

{

  "iss": "https://server.example.com",

  "sub": "ty.webb@example.com",

  "exp": 1661605509,

  "nbf": 1661609109,

  "cnf":{

    "x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"

  }

}

JWT token example take from rfc8705

Let us also look at some sample code on how you can generate the token using Golang.

Note: The code below is just for guidance and is not a working example

  1. Parse the certificate provided by the server:
  import (

    "crypto/sha256"

    "crypto/x509"

  )
  // You can read the certificate from a file and decode it
  var cert = "/cert.crt"
  r, _ := ioutil.ReadFile(cert)
  block, _ := pem.Decode(r)

  parsedCert, err := x509.ParseCertificate(block.Bytes)

Note: I skipped error handling above, as it is a sample code. Do not forget to handle error in your production code

  1. Generate the x5t#S256 as base64url encoded SHA-256 of the certificate
  newSha := sha256.New()

  newSha.Write(parsedCert.Raw)

  encoded := hex.EncodeToString(newSha.Sum(nil))

  data, err := hex.DecodeString(encoded)

  encodedx5t := base64.URLEncoding.EncodeToString(data)

  encodedx5t = strings.TrimRight(encodedx5t, "=")

  fmt.Println(encodedx5t)
  1. Generate and sign the token
  token, _ := jwt.NewBuilder().

  Issuer("issuer").

  IssuedAt(time.Now()).

  Expiration(time.Now().Add(900 * time.Second)).

  NotBefore(time.Now().Add(800 * time.Second))

  Subject("subject").

  Audience([]string{"audience"}).

  Claim("cnf", "{\"x5t#S256\":\"<encodedx5t>\"}").

  Build()

  signed, _ := jwt.Sign(token, jwt.WithKey(jwa.RS256, "<privateKey>"))

Confirmation Method for Token Introspection

OAuth 2.0 token Introspection method enables the protected resource to query and authorization server about the state of the access token. It also helps provide metadata about the token which includes the base64url encoded SHA-256 hash of the certificate. The encoded hash is provided as a response to the protected resources in the same format as the JWT thumbprint uses. The encoded certificate is present in the cnf claim in the x5t#S256 member structure. The protected resource will be able to compare the client certificate used for mTLS to the certificate hash in the response and rejects the request if they do not match.

A response from the authorization server when requesting the state of the access token would look like this:

HTTP/1.1 200 OK

Content-Type: application/json

{

  "active": true,

  "iss": "https://server.example.com",

  "sub": "ty.webb@example.com",

  "exp": 1661605509,

  "nbf": 1661609109,

  "cnf":{

    "x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"

  }

}

Conclusions

mTLS is a transport layer protocol that authenticates both client and server. OAuth 2.0 is an authorization framework to delegate access to resources. OAuth 2.0 Client Authentication and certificate bound access is a rigorous way to secure your mTLS connection. mTLS is a transport layer protocol and does not protect the application layer. OAuth 2.0 Client Authentication and certificate bound access solves the problem by making sure that only the party in possession of the private key corresponding to the certificate can utilize the token to access the associated resources. Either by using JWT certificate thumbprint or by using confirmation method for token introspection you can validate if the cnf confirmation method claim matches with the client certificate used for mTLS to secure the application layer.

Written by

github-icongithub-icon
Milap Neupane Senior Software Engineer - Cross Currency team

Milap Neupane is a Senior Software Engineer at Form3. He likes working with various technologies like Golang, Ruby/Rails, Node.js and cloud-native technologies. He is a technophilic who loves tech community and contributes to blogs, conferences and meetup's.