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
//! Serving the challenge file the *upstream* CA asks for.
//!
//! ## Why this exists at all
//!
//! The deliberate twin of [`super::dns01`], and it exists for the same
//! asymmetry. When the upstream is a real CA it issues its own challenge, and
//! the key authorization it expects is computed from **this proxy's** account
//! thumbprint at that upstream — not the end client's. The two are different
//! accounts on different servers, so the original client cannot answer it even
//! in principle: only this server knows the right value.
//!
//! Where `dns01` answers that by writing a TXT record, this module answers it
//! by holding the key authorization in memory for the seconds the CA needs it,
//! and letting a route on the server's own root router serve it.
//!
//! ## Why a route, and not a listener or a webroot
//!
//! RFC 8555 §8.3 fixes the *path* the CA fetches, not the port and not the
//! host it ultimately reaches: §8.3 explicitly permits following redirects,
//! and every real CA does. So the responder does not need to be the thing
//! listening on port 80 at the identifier — it only needs the operator's
//! existing web server to forward or redirect `/.well-known/acme-challenge/`
//! here. That is one `proxy_pass` or one `return 301`.
//!
//! Given that, a second listener bound to port 80 would buy nothing but a
//! privileged bind and a socket to reason about, and a webroot provider would
//! buy nothing but a shared filesystem to arrange. Neither is offered. What
//! *is* required — the forwarder — is stated at startup by [`super`]'s
//! `from_config`, because it is out of this process's reach and would
//! otherwise be discovered at the first failed issuance.
//!
//! ## The file's content is not defined here
//!
//! [`crate::challenge::http_01`] owns the well-known path, and
//! [`super::flow`] builds the key authorization from the account thumbprint.
//! This module only stores bytes under a token — the same separation
//! [`super::dns01`] keeps by calling into [`crate::challenge::dns_01`] rather
//! than restating the record convention.
use HashMap;
use ;
/// Holds the key authorizations the responder route serves.
///
/// A trait rather than a concrete type for the same two reasons
/// [`super::dns01::DnsUpdater`] is one: the orchestration in [`super::flow`]
/// can be driven against a stub, and a future provider slots in without
/// touching the relay.
/// The process-local store the `http01` strategy publishes into.
///
/// A `std::sync::RwLock`, not a `tokio::sync` one: unlike
/// [`local_ca`](crate::signer::local_ca)'s revocation ledger — which uses an
/// async mutex precisely because it awaits two file writes inside its critical
/// section — every section here is one hash-map operation and awaits nothing.
///
/// Poisoning is recovered from rather than propagated. A panic anywhere else
/// holding this lock must not turn the responder into a permanent 404 for
/// every certificate this server will ever relay; the worst a torn write could
/// leave behind is a stale token, which the CA would simply fail to match.
/// A published token that retracts itself when dropped.
///
/// [`super::flow`]'s `answer_dns01` retracts explicitly, in a `let triggered =
/// …; delete; triggered?` dance, because its retraction is a network round
/// trip and cannot run from `Drop`. This one can, and that closes a hole the
/// dns-01 side structurally cannot: `spawn_relay` wraps the whole relay in a
/// `tokio::time::timeout`, so a slow upstream **drops the future mid-poll** and
/// an explicit retract would simply never run — leaking one live key
/// authorization per timed-out relay, for the life of the process.