RFC 8705 - OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens.
A protected resource that supports mTLS-bound access tokens MUST verify
that the access token's confirmation claim (RFC 7800 cnf.x5t#S256)
matches the SHA-256 thumbprint of the client certificate presented in
the same TLS connection. This module computes that thumbprint and
recognises the binding shape.
Thumbprint definition
Per RFC 8705 §3.1 the x5t#S256 value is
base64url(SHA-256(DER-encoded certificate)), no paddingwhich is the canonical shape validated by Attesto.Thumbprint.
Why we round-trip through :public_key.pkix_decode_cert/2
compute_thumbprint/1 only digests its input after confirming that the
bytes parse as an X.509 certificate. A caller that fed in a random
binary would otherwise produce a "thumbprint" that no real client
certificate could ever match - silently turning the binding into a
permanent reject, or (if the binary came from an unauthenticated
source) into an attacker-controlled match. Fail closed at the source.
This module is framework-agnostic: no Plug, no database, no application
config. It is a pure function of the certificate bytes. A resource
server composes Attesto.Token.verify/3 with compute_thumbprint/1
applied to the DER bytes its TLS layer surfaces (e.g.
:ssl.peercert/1).
Where the binding may be issued
Whether the listener is even allowed to issue mTLS-bound tokens (the
TLS layer is directly terminated and the peer certificate is genuinely
the client's, rather than a reverse-proxy socket) is a deployment fact
the host application owns. Attesto does not read it from config;
the caller decides whether to pass an mTLS thumbprint to
Attesto.Token.mint/2 at all.
Summary
Functions
Compute the RFC 8705 §3.1 x5t#S256 thumbprint of an X.509 client
certificate from its DER encoding.
Returns true iff the given access-token claims map advertises an
mTLS binding via the RFC 8705 cnf.x5t#S256 confirmation claim.
Tolerates any non-empty string value (full shape validation happens in
Attesto.Token.verify/3).
The expected length, in characters, of a well-formed x5t#S256
thumbprint.
Returns true iff value is a syntactically-valid x5t#S256
thumbprint: the canonical base64url-no-pad encoding of a 32-byte
SHA-256 digest. Delegates to Attesto.Thumbprint.valid?/1.
Types
@type thumbprint() :: String.t()
Functions
@spec compute_thumbprint(binary()) :: {:ok, thumbprint()} | {:error, :invalid_certificate}
Compute the RFC 8705 §3.1 x5t#S256 thumbprint of an X.509 client
certificate from its DER encoding.
Returns {:ok, thumbprint} if the bytes parse as a certificate;
{:error, :invalid_certificate} otherwise. The certificate is NOT
validated against any trust store, expiry, or revocation status - that
is the TLS terminator's responsibility. This function only ensures the
bytes ARE a certificate (so we never emit a thumbprint for arbitrary
attacker-controlled bytes) and computes the digest.
Returns true iff the given access-token claims map advertises an
mTLS binding via the RFC 8705 cnf.x5t#S256 confirmation claim.
Tolerates any non-empty string value (full shape validation happens in
Attesto.Token.verify/3).
@spec thumbprint_length() :: pos_integer()
The expected length, in characters, of a well-formed x5t#S256
thumbprint.
Returns true iff value is a syntactically-valid x5t#S256
thumbprint: the canonical base64url-no-pad encoding of a 32-byte
SHA-256 digest. Delegates to Attesto.Thumbprint.valid?/1.