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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
|
//! Handling of netdoc signatures
//
// TODO use tor_checkable to provide a generic .verify function.
//
// But the tor_checkable API might need some updates and this seems nontrivial.
// Each verification function seems to take different inputs.
use saturating_time::SaturatingTime;
use super::*;
/// A network document with (unverified) signatures
///
/// Typically implemented automatically, for `FooUnverified` structs, as defined by
/// [`#[derive_deftly(NetdocParseableUnverified)]`](derive_deftly_template_NetdocParseableUnverified).
///
/// Each `FooUnverified` embodies precisely the body `Body`
/// and the signatures data `SignaturesData` needed to verify it,
/// This trait is precisely the constructors/accessors/deconstructors.
pub trait NetdocParseableUnverified: Sized {
/// The body, ie not including the signatures
type Body: Sized;
/// The signatures (the whole signature section)
type Signatures: NetdocParseableSignatures;
/// Inspect the document (and its signatures)
///
/// # Security hazard
///
/// The signature has not been verified, so the returned data must not be trusted.
fn inspect_unverified(&self) -> (&Self::Body, &SignaturesData<Self>);
/// Obtain the actual document (and signatures), without verifying
///
/// # Security hazard
///
/// The signature has not been verified, so the returned data must not be trusted.
fn unwrap_unverified(self) -> (Self::Body, SignaturesData<Self>);
/// Construct a new `NetdocParseableUnverified` from a body and signatures
///
/// (Called by code generated by `#[derive_deftly(NetdocParseableUnverified)]`.)
fn from_parts(body: Self::Body, signatures: SignaturesData<Self>) -> Self;
}
/// Network document that has an unparsed body type (internal trait)
///
/// This is used internally by the
/// [`NetdocParseableUnverified` derive](derive_deftly_template_NetdocParseableUnverified).
//
// This is a separate trait so that we don't complicate `NetdocParseableUnverified`
// with the additional internal `UnverifiedParsedBody` type.
// That keeps `NetdocParseableUnverified` as simply the accessors/constructors for `FooUnverified`.
pub trait HasUnverifiedParsedBody {
/// The actual body payload.
type UnverifiedParsedBody: NetdocParseable;
/// Extract the payload
///
/// # Security hazard
///
/// The signature has not been verified, so the returned data must not be trusted.
//
// There is one call site, in `ItemStream::parse_signed`.
fn unverified_into_inner_unchecked(unverified: Self::UnverifiedParsedBody) -> Self;
}
/// The signatures information extracted from a signed network document
///
/// Each `SomeDocumentUnverified` contains:
/// * private `SomeDocument`,
/// * public `SignatureData<SomeDocumentSignatures>`
///
/// See [`NetdocParseableUnverified`]
/// and the [`NetdocParseable`](derive_deftly_template_NetdocParseable) derive.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SignaturesData<U: NetdocParseableUnverified> {
/// The signatures themselves, each including the corresponding hash
pub sigs: U::Signatures,
/// The length in bytes of the body, up to the start of the first signature item.
pub unsigned_body_len: usize,
/// The hashes which were computed as part of parsing.
///
/// This will include every hash computed by any signature item's
/// `SignatureItemParseable` implementation.
///
/// See [`NetdocParseableSignatures::HashesAccu`].
pub hashes: <U::Signatures as NetdocParseableSignatures>::HashesAccu,
}
/// A signature item that can appear in a netdoc
///
/// This is the type `T` of a field `item: T` in a netdoc signatures section type.
///
/// Types that implement this embody both:
///
/// * The item, parameters, and signature data, provided in the document.
///
/// They do *not* embody:
///
/// * The hash of the document body, which will needed during verification.
///
/// However, the hash *is* calculated by `from_unparsed_and_body`, during parsing,
/// and stored in `hash`.
///
/// Typically derived with
/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
///
/// Normal (non-signature) items implement [`ItemValueParseable`].
pub trait SignatureItemParseable: Sized {
/// The Rust type of the hash value accumulator for this item.
///
/// Often this will be `Option<H>` where `H` is the actual hash value.
///
/// This specific item's `HashAccu` will be found via the document's signatures'
/// `NetdocParseableSignatures::HashesAccu`,
/// which must `impl AsMut<SignatureItemParseable::HashAccu>`.
type HashAccu;
/// Parse the item's value, and also calculate the relevant document hash
///
/// If the document hash needed for this item is not already present in `hash`,
/// this function must store it there.
/// An existing hash should not be overwritten:
/// this is because multiple signature items of the same type and hash
/// are supposed to be as multiple signatures on the same base document,
/// not cumulative signatures where each signer signs the previous signatures.
///
/// (Parsing is entangled with hashing because some items have the hash algorithm
/// as an argument, and we don't want to parse that twice.)
//
// This API supports both these cases:
// - consensuses have multiple signatures that don't cover each other
// - routerdescs have multiple signatures from different algorithms where the
// later one in the document *does* cover the earlier one
//
// In principle it could deal with other kinds of anomalies too,
// since the signature item parser gets fed the items in sequence, and can
// maintain whatever state it needs in NetdocParseableSignatures::HashesAccu.
fn from_unparsed_and_body(
item: UnparsedItem<'_>,
hash_inputs: &SignatureHashInputs<'_>,
hash: &mut Self::HashAccu,
) -> Result<Self, ErrorProblem>;
}
/// The signatures section of a network document, that can be parsed
//
// This is separate from `NetdocParseable` because it needs to deal with hashing too.
//
// Its keyword classification can be a bit simpler because all signature items
// are structural and we do not need to impose an ordering on them during parsing.
// So long as the body data is appropriately hashed and therefore covered
// by whatever signature(s) we are relying on, we don't care what other irrelevant
// signatures might be present, and we don't care if they are or are not over-signed.
pub trait NetdocParseableSignatures: Sized {
/// The type used to accumulate document hashes during parsing
///
/// Initialised to `Default` at the start of parsing,
/// by the [`parse2` core](ItemStream::parse_signed)
///
/// Each item in a signatures section is parsed by a `SignatureItemParseable` impl.
/// That impl definites an item-specific
/// [`HashAccu`](SignatureItemParseable::HashAccu)
/// type.
///
/// The [derived](derive_deftly_template_NetdocParseableSignatures)
/// signatures section parsing code finds
/// the item-specific hash accumulator type
/// [`<ITEM as SignatureItemParseable>::HashAccu`](SignatureItemParseable::HashAccu)
/// via `AsMut`:
/// `NetdocParseableSignatures::HashesAccu`
/// must impl `AsMut` for each
/// `SignatureItemParseable::HashAccu`.
///
/// For a signatures section that can contain multiple signatures with different
/// hashes, the `AsMut` will normally be derived by [`derive_more::AsMut`].
/// For a document with only one hash type,
/// `NetdocParseableSignatures::HashesAccu` and `SignatureItemParseable::HashAccu`
/// can be the same newtype,
/// [deriving `AsMut<Self>`](derive_deftly_template_AsMutSelf).
///
/// During signature verification, the document-specific verification could
/// should throw [`VerifyFailed::Bug`] if a hash needed for a signature item
/// wasn't populated.
/// (This isn't possible if each item's `SignatureItemParseable::from_unparsed_and_body`
/// always calculates and stores the hash.)
type HashesAccu: Default + Debug + Clone;
/// Is `kw` one of this signature section's keywords
fn is_item_keyword(kw: KeywordRef<'_>) -> bool;
/// Parse the signature section from a stream of items
fn from_items<'s>(
input: &mut ItemStream<'s>,
signed_doc_body: SignedDocumentBody<'s>,
sig_hashes: &mut Self::HashesAccu,
stop_at: stop_at!(),
) -> Result<Self, ErrorProblem>;
}
/// Hash(es) for a signature item
///
/// Used by the derived implementation of [`SignatureItemParseable`]
/// generated by
/// [`ItemValueParseable`](derive_deftly_template_ItemValueParseable)
/// with `#[deftly(netdoc(signature))]`.
pub trait SignatureHashesAccumulator: Clone {
/// Update `self`, ensuring that this hash is computed
///
/// Should perform precisely the hash-related parts specified for
/// [`SignatureItemParseable::from_unparsed_and_body`].
///
/// So, if this hash is already recorded in `self`, it should not be updated.
fn update_from_netdoc_body(
&mut self,
document_body: &SignatureHashInputs<'_>,
) -> Result<(), EP>;
}
/// The part of a network document before the first signature item
///
/// This is used for both Orderly signatures
/// where the hash does not contain any part of the signature Item
/// nor of any further signatures.
/// and Disorderly signatures
/// where the hash contains part of the signature Item.
/// (The Tor protocols currently only have Disorderly signatures.)
///
/// See "Signature item ordering, and signatures covering signatures"
/// in the [`NetdocParseableSignatures` derive](derive_deftly_template_NetdocParseableSignatures)
/// and <https://gitlab.torproject.org/tpo/core/torspec/-/issues/322>.
//
// This type exists as a separate newtype mostly to avoid mistakes inside
// parser implementations, where lots of different strings are floating about.
// In particular, the parser must save this value when it starts parsing
// signatures and must then reuse it for later ones.
#[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, amplify::Getters)]
pub struct SignedDocumentBody<'s> {
/// The actual body as a string
#[getter(as_copy)]
pub(crate) body: &'s str,
}
/// Inputs needed to calculate a specific signature hash for a specific Item
///
/// Embodies:
///
/// * `&str` for the body, as for `SignedDocumentBody`.
/// For calculating Orderly signatures.
/// (That is, ones that do not include any part of the signature Item;
/// See [`SignedDocumentBody`].)
///
/// * Extra information for calculating Disorderly signatures.
/// Disorderly signature Items can only be implemented within this crate.
#[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, amplify::Getters)]
pub struct SignatureHashInputs<'s> {
/// The Orderly body (up to the first signature item)
#[getter(as_copy)]
pub(crate) body: SignedDocumentBody<'s>,
/// The part of the document up to just before this signature item.
#[getter(skip)]
pub(crate) document_sofar: &'s str,
/// The signature item keyword and the following space
#[getter(skip)]
pub(crate) signature_item_kw_spc: &'s str,
/// The whole signature item keyword line not including the final newline
#[getter(skip)]
pub(crate) signature_item_line: &'s str,
}
impl<'s> SignatureHashInputs<'s> {
/// Hash into `h` the body and the whole of the signature item's keyword line
pub(crate) fn hash_whole_keyword_line(&self, h: &mut impl Digest) {
h.update(self.body().body());
h.update(self.signature_item_line);
h.update("\n");
}
}
/// Hash types suitable for use as `#[deftly(netdoc(signature(hash_accu = TY))]`
///
/// See
/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
pub mod sig_hashes {
use super::*;
/// SHA-1 including the whole keyword line
///
/// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
#[derive(Debug, Clone, Default, Deftly)]
#[derive_deftly(AsMutSelf)]
#[allow(clippy::exhaustive_structs)]
pub struct Sha1WholeKeywordLine(pub Option<[u8; 20]>);
impl SignatureHashesAccumulator for Sha1WholeKeywordLine {
fn update_from_netdoc_body(&mut self, body: &SignatureHashInputs<'_>) -> Result<(), EP> {
self.0.get_or_insert_with(|| {
let mut h = tor_llcrypto::d::Sha1::new();
body.hash_whole_keyword_line(&mut h);
h.finalize().into()
});
Ok(())
}
}
}
/// Utility function to check that a time is within a validity period
#[deprecated]
pub fn check_validity_time(
now: SystemTime,
validity: std::ops::RangeInclusive<SystemTime>,
) -> Result<(), VF> {
if now < *validity.start() {
Err(VF::TooNew)
} else if now > *validity.end() {
Err(VF::TooOld)
} else {
Ok(())
}
}
/// Like [`check_validity_time()`] but with a tolerance to support clock skews.
///
/// This function does not use the `DirTolerance` struct because we want to be
/// agnostic of directories in this context.
#[allow(deprecated)]
#[deprecated]
pub fn check_validity_time_tolerance(
now: SystemTime,
validity: std::ops::RangeInclusive<SystemTime>,
pre_tolerance: Duration,
post_tolerance: Duration,
) -> Result<(), VF> {
let start = *validity.start();
let end = *validity.end();
let validity = start.saturating_sub(pre_tolerance)..=end.saturating_add(post_tolerance);
check_validity_time(now, validity)
}
|