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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//! TLS channel binding for session proofs (anti-relay).
//!
//! A pairing session runs *inside* a TLS connection, but the cryptographic
//! proofs it carries (the AEAD-sealed [`crate::envelope::Envelope`]s) are, by
//! themselves, transport-agnostic: a proof captured on one TLS connection and
//! replayed onto a *different* TLS connection would still open, because nothing
//! ties the proof to the channel it was minted on. That is the classic
//! relay / MITM attack — an attacker terminates the victim's TLS session,
//! lifts a valid proof off it, and presents it on its own session to a
//! third party.
//!
//! The fix is **channel binding**: fold a per-connection secret that *only the
//! two TLS endpoints know* into the proof, so a proof minted on channel A
//! cannot be opened on channel B. The secret is the **TLS exporter** —
//! exported keying material per RFC 5705 (TLS ≤1.2) / RFC 8446 §7.5 (TLS 1.3),
//! using the RFC 9266 `tls-exporter` label. Both endpoints of a TLS connection
//! derive the *same* exporter value; two independent connections derive
//! *different* values. Folding it into the envelope key (and AAD) makes the
//! proof open only on the channel that minted it. This is the same shape as
//! token-binding (RFC 8471) and DPoP (RFC 9449): a possession proof scoped to
//! its transport.
//!
//! # Wire / interop parameters (NORMATIVE)
//!
//! - **Label:** [`TLS_EXPORTER_LABEL`] = `EXPORTER-Channel-Binding` (RFC 9266
//! §3). This is the registered label a stock TLS stack uses for the
//! `tls-exporter` channel binding; matching it byte-for-byte is what lets an
//! auths endpoint interoperate with any RFC 9266 peer.
//! - **Context:** *absent* (not empty). RFC 5705 distinguishes an absent
//! context from a zero-length one; RFC 9266 specifies the exporter with no
//! context value, so the adapter MUST pass "no context", not `b""`.
//! - **Length:** [`TLS_EXPORTER_LEN`] = 32 bytes.
//!
//! # Ports and adapters
//!
//! This crate is transport-agnostic (no TLS stack dependency). The act of
//! *extracting* the exporter from a live connection is therefore a **port**:
//! [`ChannelBindingProvider`]. The TLS-aware crates (the pairing daemon, the
//! CLI LAN server, the mobile client) implement it as a thin **adapter** over
//! their concrete stack's `export_keying_material` — `rustls`'s
//! `ConnectionCommon::export_keying_material`, OpenSSL's
//! `SslRef::export_keying_material`, Go's `ConnectionState.ExportKeyingMaterial`.
//! The core protocol only ever sees a parsed [`ChannelBinding`].
use ;
/// RFC 9266 §3 exporter label for the `tls-exporter` channel binding.
///
/// A stock TLS stack producing a `tls-exporter` channel binding exports keying
/// material under exactly this label. Byte-identical use here is what makes an
/// auths endpoint's binding match an arbitrary RFC 9266 peer's.
pub const TLS_EXPORTER_LABEL: & = b"EXPORTER-Channel-Binding";
/// Length, in bytes, of the exported keying material used as the binding.
///
/// 32 bytes = 256 bits, matching the suite's TLS oracle and leaving no
/// shortfall against the AEAD key it is folded into.
pub const TLS_EXPORTER_LEN: usize = 32;
/// HKDF `info` domain separator for folding a channel binding into a derived
/// key. Distinct from every other label in [`crate::domain_separation`] so a
/// channel-bound key can never collide with an unbound one.
pub const CHANNEL_BINDING_INFO: & = b"auths-pairing-channel-binding-v1";
/// Errors from parsing a channel binding or extracting one from a transport.
/// A parsed TLS channel binding: the RFC 9266 `tls-exporter` keying material
/// for one connection.
///
/// Parse, don't validate: the only way to hold a `ChannelBinding` is through
/// [`ChannelBinding::from_exporter`], which enforces the length. Downstream
/// code (the envelope key derivation) can therefore trust the bytes without
/// re-checking. Two `ChannelBinding`s compare equal iff their exporter bytes
/// match — i.e. iff they came from the *same* TLS connection. The comparison
/// is constant-time so a relay attacker learns nothing from timing.
// Manual `Debug` that redacts the exporter. The value is a per-connection
// secret; logging it would hand a relay attacker the binding it needs to
// recompute.
/// Constant-time equality: equal iff the two bindings come from the same TLS
/// connection. Constant-time so a relay attacker probing "is my forged binding
/// close to the real one?" learns nothing from response timing.
/// Port: extract the channel binding from a live transport.
///
/// Implemented by the TLS-aware crates as an adapter over their concrete
/// stack. The core protocol depends only on this trait, never on a TLS
/// library — ports and adapters at the transport edge.
///
/// The adapter MUST call its stack's keying-material exporter with
/// [`TLS_EXPORTER_LABEL`], an *absent* context, and length
/// [`TLS_EXPORTER_LEN`], then hand the bytes to
/// [`ChannelBinding::from_exporter`]. An adapter that cannot produce a binding
/// (handshake incomplete, not TLS 1.3) MUST return
/// [`ChannelBindingError::ExporterUnavailable`] — never a placeholder — so the
/// caller fails closed instead of minting an unbound, relay-able proof.