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
226
227
228
229
//! Session binding: ties a session to client-specific signals to detect hijacking.
//!
//! Session binding prevents session hijacking by computing an HMAC-SHA256
//! fingerprint of client-specific request properties (e.g. the `User-Agent`
//! header) and storing it in the session. On every subsequent request the
//! fingerprint is recomputed and compared using constant-time equality. A
//! mismatch indicates the session cookie may have been stolen and replayed
//! from a different client, so the session is reset to `Guest`.
//!
//! # When bindings are created
//!
//! The [`SessionLayer`](super::layer::SessionLayer) pre-computes a
//! `pending_fingerprint` from the incoming request on every request. This
//! pending value is stored in
//! `SessionInner::pending_fingerprint`
//! and applied (written into [`SessionData::fingerprint`](super::data::SessionData::fingerprint))
//! at the earliest auth-state transition that moves the session out of `Guest`:
//!
//! - **`set_identifying`**: when the user submits their username.
//! - **`begin_authenticating`**: when a multi-factor flow starts.
//! - **`set_authenticated` / `advance_factor`**: when authentication completes.
//!
//! Binding as early as possible (during `Identifying`) ensures that even
//! pre-MFA sessions cannot be replayed from a different device. Once the
//! fingerprint is set it is never overwritten; it persists for the lifetime
//! of the session.
//!
//! # Usage
//!
//! ```text
//! let layer = SessionLayer::new(store, key)
//! .with_binding(UserAgentBinding);
//! ```
//!
//! Implement [`SessionBinding`] for custom binding strategies (e.g. combining
//! User-Agent with IP subnet or TLS channel binding).
use ;
use ;
use Mac;
/// Extracts a binding value from a request for session-to-client binding.
///
/// The returned string is stored as an HMAC-SHA256 digest in the session.
/// On subsequent requests, the HMAC is recomputed and compared to detect
/// session hijacking (cookie theft from a different client).
///
/// Return `None` if the binding signal is absent (e.g. no User-Agent header),
/// in which case binding is skipped for that request.
///
/// # Scope: where binding is checked
///
/// The fingerprint is recomputed and compared **once per HTTP request**
/// at [`SessionLayer`](super::layer::SessionLayer) entry. It is not
/// re-checked at any other point in the request lifecycle. The
/// implications:
///
/// - **Persistent connections (WebSocket, Server-Sent Events) are
/// bound only at upgrade.** Once the WS handshake completes, the
/// layer no longer sees the underlying frames: an attacker who
/// manages to splice into the existing socket can drive it
/// indefinitely without the binding firing. If you need to
/// re-validate, do so in your own message handler at
/// policy-relevant intervals.
/// - **gRPC streaming** has the same caveat: only the initial HTTP/2
/// stream-open request runs through this middleware.
/// - **Long-poll request bodies** that pause for minutes do not
/// re-check binding mid-flight, but each new HTTP request does, so
/// a long-poll loop fired from a hijacked client will fail at the
/// next round trip.
/// - **Background jobs the application enqueues "as the user"** are
/// not bound by this trait at all; the application owns that
/// threat model. If you spawn a long-running task on behalf of a
/// user, do not assume the user is still legitimately connected
/// when it finishes.
/// Compute the HMAC-SHA256 fingerprint used for storage and comparison.
///
/// Keyed with the session signing key so that an attacker who reads the session
/// data from the store cannot recompute a valid fingerprint without the key.
pub
/// Binds the session to the `User-Agent` header.
///
/// **Threat model, read this before relying on it.** UA binding catches
/// only the dumbest hijacking modes: the attacker steals the cookie via
/// log scraping, browser-extension exfiltration, or a prior breach where
/// the UA wasn't captured, and replays it from a different client. It
/// does **not** stop:
///
/// - **XSS / cookie-jacking attacks where the attacker is in the
/// victim's browser.** The User-Agent is plaintext on every request and
/// trivially copyable; an attacker who exfiltrates the cookie via a
/// malicious script or browser extension also has full access to
/// `navigator.userAgent`.
/// - **Network-level interception** where the attacker observes the
/// victim's traffic (TLS-stripping proxies, compromised CA, captured
/// PCAPs). The UA travels in clear with the cookie.
/// - **Phishing** that proxies the victim's browser to your origin:
/// the proxy forwards the real UA verbatim.
///
/// What it *is* useful for: defense in depth against database/log dumps
/// where the attacker has cookies but no captured UA, and as a cheap
/// signal for hijack telemetry. Combine with at least one of: client-IP
/// `/24` binding (acceptable when users don't roam between networks
/// often), TLS channel binding (RFC 8471), or a hardware-bound key
/// (FIDO2). Document the limits to your security reviewers; assuming
/// UA binding stops cookie theft is a common misreading.
;