You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a new authentication method, webauthn combined with mutual-TLS
certificate-bound access tokens per RFC 8705
("OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access
Tokens"). On successful auth, the client's TLS certificate thumbprint
(x5t#S256, the base64url SHA-256 digest of the DER-encoded cert, per RFC 8705
§3.1) is minted into the token payload. On every subsequent request, the
current mTLS peer certificate's thumbprint must match the one bound into the
token, or the token is rejected — this is the "proof of possession" defense
RFC 8705 adds on top of plain webauthn/passkey auth: even a stolen bearer
token is useless without the private key of the bound client certificate.
This is a token confirmation mechanism, not a replacement for the existing webauthn credential check (crates/webauthn/) — it complements it, the same
way RFC 8705 complements OAuth2 rather than replacing the token issuance flow.
Background: prior art in python-keystone
python-keystone's fernet token formatter has a numbered set of payload
classes (keystone/token/token_formatters.py); payload id 10 is the
OAuth2 access-token payload used for the mTLS-bound OAuth2 flow, and it
carries the certificate thumbprint (cnf.x5t#S256) alongside the standard
scope fields so that validation can re-derive and compare it against the live
TLS session on every request.
Proposed approach in keystone-rs
crates/core-types/src/token.rs / crates/core-types/src/token/payload/:
add a new FernetToken payload variant (or extend an existing scoped
payload, mirroring python-keystone's payload-10 approach) carrying the x5t#S256 thumbprint alongside the usual scope fields.
crates/core-types/src/auth.rs: extend AuthenticationContext (currently WebauthN has no cert-binding variant — see enum AuthenticationContext,
~line 1294) with a cert-bound variant, or add a cert_thumbprint field
usable by the existing WebauthN arm.
Peer certificate extraction: reuse the existing mTLS/x509 peer-cert
plumbing already present for SPIFFE listeners
(crates/keystone/src/server/listener/spiffe_tls.rs, spiffe_common.rs) rather than inventing new TLS termination handling —
same pattern, different trust domain (webauthn users' own client certs
instead of SPIFFE workload certs).
TokenService::validate_to_context_impl
(crates/core/src/token/service.rs): on every validation of a cert-bound
token, recompute the current connection's peer-cert thumbprint and reject
(new dedicated error, mirroring the PluginVersionMismatch / ruleset_version TOCTOU pattern already used elsewhere in this codebase)
if it doesn't match the thumbprint minted into the token.
Policy/OPA input must not carry the raw certificate or thumbprint value
beyond what's needed for the match itself — consistent with the existing
"no EC2 keys/TOTP seeds in OPA" secrets-stripping rule in doc/src/contributor/security-model.md.
Relation to other in-flight work
Complements TLS based 2FA #263 (TLS based 2FA / trusted CA registration): TLS based 2FA #263 is about
registering trusted CAs per domain and validating the client cert against
them as a complementary factor; this issue is specifically about binding the minted token to the cert's thumbprint (RFC 8705 token
confirmation), which is orthogonal and can reuse whatever CA-trust/peer-cert
extraction TLS based 2FA #263 introduces.
Should read doc/src/contributor/security-model.md before implementation
(security decisions must be keyed on the authentication chain, not scope;
see repo-wide security invariants), and probably warrants its own ADR given
the pattern of ADR 0025/0026/0028 for other auth/token changes in this
repo.
Acceptance criteria
New/extended fernet token payload carries the client cert's x5t#S256
thumbprint.
Token validation recomputes the live mTLS peer cert thumbprint and
rejects on mismatch or on a request with no client cert presented.
Webauthn registration/auth flow (crates/webauthn/) gains an
opt-in mode to require and bind the client cert at login time.
Unit tests in the relevant crates + test_api coverage (valid
auth + positive/negative policy, invalid auth, per this repo's CRUD
handler test convention).
Summary
Add a new authentication method,
webauthncombined with mutual-TLScertificate-bound access tokens per RFC 8705
("OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access
Tokens"). On successful auth, the client's TLS certificate thumbprint
(
x5t#S256, the base64url SHA-256 digest of the DER-encoded cert, per RFC 8705§3.1) is minted into the token payload. On every subsequent request, the
current mTLS peer certificate's thumbprint must match the one bound into the
token, or the token is rejected — this is the "proof of possession" defense
RFC 8705 adds on top of plain webauthn/passkey auth: even a stolen bearer
token is useless without the private key of the bound client certificate.
This is a token confirmation mechanism, not a replacement for the existing
webauthncredential check (crates/webauthn/) — it complements it, the sameway RFC 8705 complements OAuth2 rather than replacing the token issuance flow.
Background: prior art in python-keystone
python-keystone's fernet token formatter has a numbered set of payload
classes (
keystone/token/token_formatters.py); payload id10is theOAuth2 access-token payload used for the mTLS-bound OAuth2 flow, and it
carries the certificate thumbprint (
cnf.x5t#S256) alongside the standardscope fields so that validation can re-derive and compare it against the live
TLS session on every request.
Proposed approach in keystone-rs
crates/core-types/src/token.rs/crates/core-types/src/token/payload/:add a new
FernetTokenpayload variant (or extend an existing scopedpayload, mirroring python-keystone's payload-10 approach) carrying the
x5t#S256thumbprint alongside the usual scope fields.crates/core-types/src/auth.rs: extendAuthenticationContext(currentlyWebauthNhas no cert-binding variant — seeenum AuthenticationContext,~line 1294) with a cert-bound variant, or add a
cert_thumbprintfieldusable by the existing
WebauthNarm.plumbing already present for SPIFFE listeners
(
crates/keystone/src/server/listener/spiffe_tls.rs,spiffe_common.rs) rather than inventing new TLS termination handling —same pattern, different trust domain (webauthn users' own client certs
instead of SPIFFE workload certs).
TokenService::validate_to_context_impl(
crates/core/src/token/service.rs): on every validation of a cert-boundtoken, recompute the current connection's peer-cert thumbprint and reject
(new dedicated error, mirroring the
PluginVersionMismatch/ruleset_versionTOCTOU pattern already used elsewhere in this codebase)if it doesn't match the thumbprint minted into the token.
beyond what's needed for the match itself — consistent with the existing
"no EC2 keys/TOTP seeds in OPA" secrets-stripping rule in
doc/src/contributor/security-model.md.Relation to other in-flight work
registering trusted CAs per domain and validating the client cert against
them as a complementary factor; this issue is specifically about
binding the minted token to the cert's thumbprint (RFC 8705 token
confirmation), which is orthogonal and can reuse whatever CA-trust/peer-cert
extraction TLS based 2FA #263 introduces.
doc/src/contributor/security-model.mdbefore implementation(security decisions must be keyed on the authentication chain, not scope;
see repo-wide security invariants), and probably warrants its own ADR given
the pattern of ADR 0025/0026/0028 for other auth/token changes in this
repo.
Acceptance criteria
x5t#S256thumbprint.
rejects on mismatch or on a request with no client cert presented.
crates/webauthn/) gains anopt-in mode to require and bind the client cert at login time.
test_apicoverage (validauth + positive/negative policy, invalid auth, per this repo's CRUD
handler test convention).
doc/src/admin/features/page + ADR.