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
//! The key that decides whether a ticket outlives the process.
//!
//! An endpoint's secret key is its name on the network: its public half is
//! what a ticket carries, and what a connecting peer dials. Generated fresh
//! per process — the default, and what every version before this one did —
//! it makes every ticket disposable. Restart the listener and every ticket
//! ever handed out names a peer nobody is, which is the ticket rotation the
//! README sells, and also the reason a laptop has to be re-paired every time
//! a desktop reboots.
//!
//! Storing the key swaps one of those for the other, and it is worth being
//! exact about which. It does **not** weaken revocation: a leaked ticket is
//! killed by deleting this file and restarting, which costs precisely what
//! restarting cost before — a re-pairing of every device. What it removes is
//! revocation *by accident*, which is what a reboot used to be. What it adds
//! is a secret on disk, and that is the real cost: there was nothing to
//! steal before and now there is.
//!
//! **A durable ticket is not the same as a reachable one**, and the gap is
//! worth naming here because this module is where people will look. The key
//! fixes the *name* in a ticket; the addresses beside it are a snapshot,
//! and a restarted process holds a different UDP port. Closing that gap is
//! discovery's job — by default n0's, which the README's disclosure section
//! covers — so a peer whose address has changed is found by resolving the
//! endpoint id, not by the ticket alone.
//!
//! Measured, on a host with n0's DNS blocked: a listener restarted with the
//! same identity minted the same ticket, a *fresh* ticket from it paired
//! and served, and the *old* ticket could not reach it at all. Nothing was
//! wrong with the key. Somewhere with discovery reachable the old ticket
//! resolves the same id to the new address, which is the whole design; this
//! is only a note that the two halves are separate, and that switching off
//! the one this crate does not control takes the other with it.
//!
//! Pure of iroh, deliberately. This hands back thirty-two bytes and
//! [`crate::transport`] is where they become a key, so the whole of the
//! file handling — the format, the permissions, the refusals — is
//! exercisable without binding an endpoint.
use fs;
use io;
use Path;
use crateServeError;
use cratebase32;
/// Bytes in an endpoint's secret key. Fixed by the curve, not by us.
pub const KEY_BYTES: usize = 32;
/// Read the key stored at `path`, minting and storing one if there is none.
///
/// The mint-on-absence is what makes the flag usable as a single step: a
/// first run creates the file, and every run after it reads the same key
/// back and mints the same ticket. Requiring the operator to generate one
/// first would be a second command whose only job is to make this one work.
///
/// # Errors
///
/// [`ServeError::Identity`] for a file that exists and is not a key this
/// can use, or one it cannot read or write. All of them are permanent: the
/// path came from the operator, and retrying it fails the same way.
pub
/// The stored form: base32 of the key's bytes, one line.
///
/// Text rather than raw bytes so the file survives a copy-paste, an editor
/// and a config-management tool that assumes UTF-8 — and base32 rather than
/// hex or base64 for the reason the token uses it: no character a person can
/// confuse reading it off a screen, and nothing a shell wants to quote.
///
/// Read case-insensitively and written lower-case, matching the ticket. The
/// trailing newline is written because every editor adds one anyway, and
/// trimmed on read for the same reason `--token-file` trims it.
/// Write `key` where only this user can read it.
/// Create the file with the key already unreadable to anyone else.
///
/// `create_new`, so a race between two listeners starting at once is an
/// error rather than one of them silently overwriting the other's key —
/// which would leave the loser serving a ticket nobody holds.
///
/// The mode is set **at creation** rather than afterwards. Creating a
/// world-readable file and then tightening it leaves a window in which the
/// key is on disk and readable, and a key that was briefly readable is a key
/// that leaked.
/// The same, on a platform with no mode to set.
///
/// The file lands with whatever the directory grants, and this crate has no
/// way to narrow it. Said plainly in `SECURITY.md` rather than papered over:
/// on Windows, choose a directory only you can read.
/// Refuse a key anyone else on this machine can read.
///
/// The check `ssh` makes on a private key, for the reason it makes it: a
/// key is only a secret while it is one, and a file that has become
/// group-readable — restored from a backup, copied with the wrong umask,
/// left in a shared directory — is a ticket somebody else can mint at any
/// time, silently, for as long as the file lives.
///
/// Refusing is the safe direction and the message says what to do. Unix
/// only, because there is no mode to inspect elsewhere; see
/// [`create_private`].
/// The same, where there is no mode to inspect.
///
/// The signature is its unix twin's rather than its own: the caller chains
/// this into a `Result`, and a stub that returned `()` here would make the
/// call site itself `#[cfg]`-dependent — which is how the two platforms
/// stop being the same code with one function swapped.
/// Thirty-two bytes from the operating system's CSPRNG.
///
/// The same source and the same reasoning as the bearer token: a CSPRNG
/// that cannot produce bytes is not a condition to paper over with a weaker
/// one, because an endpoint key anybody can guess is worse than no listener.
/// Every failure here is the same verdict with a different cause, so the
/// cause rides in `source` and the variant names the file.