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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! RFC 8705 §3 certificate-bound access tokens.
//!
//! This module computes the `cnf.x5t#S256` confirmation value — the
//! base64url (no padding) SHA-256 thumbprint of a client's DER-encoded X.509
//! certificate — and nothing more. It does not parse X.509, does not
//! terminate TLS, and does not validate a certificate chain: extracting the
//! *actual* peer certificate presented on a live mTLS connection is a
//! framework/deployment concern, deliberately left outside this
//! framework-agnostic crate (see [`ClientCertificateDer`]'s doc comment for
//! where that hand-off happens today).
//!
//! Consumed by `authkestra-op::handlers::token::handle_client_credentials`
//! (to stamp `cnf.x5t#S256` at issuance) and by
//! `authkestra-resource::jwt::JwtStrategy` (to verify a presented
//! certificate against it) — see issue #224.
use Engine;
use ;
/// The DER-encoded bytes of a client certificate presented on the current
/// connection.
///
/// Neither `authkestra-op` nor `authkestra-resource` terminates TLS itself,
/// so nothing in this crate family populates a `ClientCertificateDer`
/// automatically. A host application — or the mTLS-terminating layer it
/// runs in front of/alongside its service (a reverse proxy, an
/// `axum-server`/actix-web rustls acceptor configured to require and expose
/// client certificates, etc.) — is responsible for extracting the peer
/// certificate and handing its DER bytes to this crate:
///
/// - On the OP side, `authkestra-axum`'s `axum_token_handler` reads one back
/// out of an `axum::Extension<ClientCertificateDer>` (so a host inserts it
/// as a request extension via its own middleware/acceptor), and
/// `authkestra-actix`'s `actix_token_handler` reads one out of the actix
/// request's own extension map the same way. Both then forward the DER
/// bytes into
/// [`handle_token_with_client_cert`](../../../authkestra_op/handlers/token/fn.handle_token_with_client_cert.html).
/// - On the resource-server side, `JwtStrategy::authenticate` looks one up
/// in the `http::request::Parts` extension map it is handed, when
/// `ValidationConfig::require_cert_binding` is set.
///
/// If nothing ever inserts one, callers simply see `None` throughout, and
/// `client_credentials` tokens are issued as plain (unbound) bearer tokens,
/// same as before this existed.
///
/// # The source of these bytes is the entire security boundary
///
/// **Inserting a `ClientCertificateDer` from a source that has not
/// cryptographically verified the certificate — i.e. actually terminated
/// mTLS and validated the chain — provides no security benefit and a false
/// sense of one.**
///
/// Nothing here parses X.509, validates a chain, or checks that these bytes
/// are even DER; [`x5t_s256_thumbprint`] hashes whatever it is handed. So a
/// binding built from, say, a reverse-proxy header that relays a
/// client-supplied value the proxy never verified degrades to "proof that
/// the caller knows a byte string the caller chose" — while looking
/// *identical* to a real RFC 8705 binding: a `cnf.x5t#S256` claim is present
/// and `require_cert_binding` accepts it. Issuance and verification trust the
/// same extension, so both fail together, and silently.
;
/// Computes the RFC 8705 §3 `x5t#S256` confirmation value: base64url
/// (no padding) of the SHA-256 digest of the DER-encoded certificate.
/// Constant-time comparison of two `x5t#S256` thumbprints (or any two
/// strings) — a short, dependency-free byte-XOR loop rather than pulling in
/// `subtle` for a single fixed-length string comparison. Mirrors
/// `authkestra_op::attestation::constant_time_eq`, used for the analogous
/// `cnf.jkt` device-attestation check.