1
//! Handling of netdoc signatures
2
//
3
// TODO use tor_checkable to provide a generic .verify function.
4
//
5
// But the tor_checkable API might need some updates and this seems nontrivial.
6
// Each verification function seems to take different inputs.
7

            
8
use saturating_time::SaturatingTime;
9

            
10
use super::*;
11

            
12
/// A network document with (unverified) signatures
13
///
14
/// Typically implemented automatically, for `FooUnverified` structs, as defined by
15
/// [`#[derive_deftly(NetdocParseableUnverified)]`](derive_deftly_template_NetdocParseableUnverified).
16
///
17
/// Each `FooUnverified` embodies precisely the body `Body`
18
/// and the signatures data `SignaturesData` needed to verify it,
19
/// This trait is precisely the constructors/accessors/deconstructors.
20
pub trait NetdocUnverified: Sized {
21
    /// The body, ie not including the signatures
22
    type Body: Sized;
23
    /// The signatures (the whole signature section)
24
    type Signatures: NetdocParseableSignatures;
25

            
26
    /// Inspect the document (and its signatures)
27
    ///
28
    /// # Security hazard
29
    ///
30
    /// The signature has not been verified, so the returned data must not be trusted.
31
    fn inspect_unverified(&self) -> (&Self::Body, &SignaturesData<Self>);
32

            
33
    /// Obtain the actual document (and signatures), without verifying
34
    ///
35
    /// # Security hazard
36
    ///
37
    /// The signature has not been verified, so the returned data must not be trusted.
38
    fn unwrap_unverified(self) -> (Self::Body, SignaturesData<Self>);
39

            
40
    /// Construct a new `NetdocUnverified` from a body and signatures
41
    ///
42
    /// (Called by code generated by `#[derive_deftly(NetdocUnverified)]`.)
43
    fn from_parts(body: Self::Body, signatures: SignaturesData<Self>) -> Self;
44
}
45

            
46
/// Network document that has an unparsed body type (internal trait)
47
///
48
/// This is used internally by the
49
/// [`NetdocParseableUnverified` derive](derive_deftly_template_NetdocParseableUnverified).
50
//
51
// This is a separate trait so that we don't complicate `NetdocUnverified`
52
// with the additional internal `UnverifiedParsedBody` type.
53
// That keeps `NetdocUnverified` as simply the accessors/constructors for `FooUnverified`.
54
pub trait HasUnverifiedParsedBody {
55
    /// The actual body payload.
56
    type UnverifiedParsedBody: NetdocParseable;
57

            
58
    /// Extract the payload
59
    ///
60
    /// # Security hazard
61
    ///
62
    /// The signature has not been verified, so the returned data must not be trusted.
63
    //
64
    // There is one call site, in `ItemStream::parse_signed`.
65
    fn unverified_into_inner_unchecked(unverified: Self::UnverifiedParsedBody) -> Self;
66
}
67

            
68
/// The signatures information extracted from a signed network document
69
///
70
/// Each `SomeDocumentUnverified` contains:
71
///   * private `SomeDocument`,
72
///   * public `SignatureData<SomeDocumentSignatures>`
73
///
74
/// See [`NetdocUnverified`]
75
/// and the [`NetdocParseable`](derive_deftly_template_NetdocParseable) derive.
76
#[derive(Debug, Clone)]
77
#[non_exhaustive]
78
pub struct SignaturesData<U: NetdocUnverified> {
79
    /// The signatures themselves, each including the corresponding hash
80
    pub sigs: U::Signatures,
81

            
82
    /// The length in bytes of the body, up to the start of the first signature item.
83
    pub unsigned_body_len: usize,
84

            
85
    /// The hashes which were computed as part of parsing.
86
    ///
87
    /// This will include every hash computed by any signature item's
88
    /// `SignatureItemParseable` implementation.
89
    ///
90
    /// See [`NetdocParseableSignatures::HashesAccu`].
91
    pub hashes: <U::Signatures as NetdocParseableSignatures>::HashesAccu,
92
}
93

            
94
/// A signature item that can appear in a netdoc
95
///
96
/// This is the type `T` of a field `item: T` in a netdoc signatures section type.
97
///
98
/// Types that implement this embody both:
99
///
100
///   * The item, parameters, and signature data, provided in the document.
101
///
102
/// They do *not* embody:
103
///
104
///   * The hash of the document body, which will needed during verification.
105
///
106
/// However, the hash *is* calculated by `from_unparsed_and_body`, during parsing,
107
/// and stored in `hash`.
108
///
109
/// Typically derived with
110
/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
111
///
112
/// Normal (non-signature) items implement [`ItemValueParseable`].
113
pub trait SignatureItemParseable: Sized {
114
    /// The Rust type of the hash value accumulator for this item.
115
    ///
116
    /// Often this will be `Option<H>` where `H` is the actual hash value.
117
    ///
118
    /// This specific item's `HashAccu` will be found via the document's signatures'
119
    /// `NetdocParseableSignatures::HashesAccu`,
120
    /// which must `impl AsMut<SignatureItemParseable::HashAccu>`.
121
    type HashAccu;
122

            
123
    /// Parse the item's value, and also calculate the relevant document hash
124
    ///
125
    /// If the document hash needed for this item is not already present in `hash`,
126
    /// this function must store it there.
127
    /// An existing hash should not be overwritten:
128
    /// this is because multiple signature items of the same type and hash
129
    /// are supposed to be as multiple signatures on the same base document,
130
    /// not cumulative signatures where each signer signs the previous signatures.
131
    ///
132
    /// (Parsing is entangled with hashing because some items have the hash algorithm
133
    /// as an argument, and we don't want to parse that twice.)
134
    //
135
    // This API supports both these cases:
136
    //  - consensuses have multiple signatures that don't cover each other
137
    //  - routerdescs have multiple signatures from different algorithms where the
138
    //    later one in the document *does* cover the earlier one
139
    //
140
    // In principle it could deal with other kinds of anomalies too,
141
    // since the signature item parser gets fed the items in sequence, and can
142
    // maintain whatever state it needs in NetdocParseableSignatures::HashesAccu.
143
    fn from_unparsed_and_body(
144
        item: UnparsedItem<'_>,
145
        hash_inputs: &SignatureHashInputs<'_>,
146
        hash: &mut Self::HashAccu,
147
    ) -> Result<Self, ErrorProblem>;
148
}
149

            
150
/// The signatures section of a network document, that can be parsed
151
//
152
// This is separate from `NetdocParseable` because it needs to deal with hashing too.
153
//
154
// Its keyword classification can be a bit simpler because all signature items
155
// are structural and we do not need to impose an ordering on them during parsing.
156
// So long as the body data is appropriately hashed and therefore covered
157
// by whatever signature(s) we are relying on, we don't care what other irrelevant
158
// signatures might be present, and we don't care if they are or are not over-signed.
159
pub trait NetdocParseableSignatures: Sized {
160
    /// The type used to accumulate document hashes during parsing
161
    ///
162
    /// Initialised to `Default` at the start of parsing,
163
    /// by the [`parse2` core](ItemStream::parse_signed)
164
    ///
165
    /// Each item in a signatures section is parsed by a `SignatureItemParseable` impl.
166
    /// That impl definites an item-specific
167
    /// [`HashAccu`](SignatureItemParseable::HashAccu)
168
    /// type.
169
    ///
170
    /// The [derived](derive_deftly_template_NetdocParseableSignatures)
171
    /// signatures section parsing code finds
172
    /// the item-specific hash accumulator type
173
    /// [`<ITEM as SignatureItemParseable>::HashAccu`](SignatureItemParseable::HashAccu)
174
    /// via `AsMut`:
175
    /// `NetdocParseableSignatures::HashesAccu`
176
    /// must impl `AsMut` for each
177
    /// `SignatureItemParseable::HashAccu`.
178
    ///
179
    /// For a signatures section that can contain multiple signatures with different
180
    /// hashes, the `AsMut` will normally be derived by [`derive_more::AsMut`].
181
    /// For a document with only one hash type,
182
    /// `NetdocParseableSignatures::HashesAccu` and `SignatureItemParseable::HashAccu`
183
    /// can be the same newtype,
184
    /// [deriving `AsMut<Self>`](derive_deftly_template_AsMutSelf).
185
    ///
186
    /// During signature verification, the document-specific verification could
187
    /// should throw [`VerifyFailed::Bug`] if a hash needed for a signature item
188
    /// wasn't populated.
189
    /// (This isn't possible if each item's `SignatureItemParseable::from_unparsed_and_body`
190
    /// always calculates and stores the hash.)
191
    type HashesAccu: Default + Debug + Clone;
192

            
193
    /// Is `kw` one of this signature section's keywords
194
    fn is_item_keyword(kw: KeywordRef<'_>) -> bool;
195

            
196
    /// Parse the signature section from a stream of items
197
    fn from_items<'s>(
198
        input: &mut ItemStream<'s>,
199
        signed_doc_body: SignedDocumentBody<'s>,
200
        sig_hashes: &mut Self::HashesAccu,
201
        stop_at: stop_at!(),
202
    ) -> Result<Self, ErrorProblem>;
203
}
204

            
205
/// Hash(es) for a signature item
206
///
207
/// Used by the derived implementation of [`SignatureItemParseable`]
208
/// generated by
209
/// [`ItemValueParseable`](derive_deftly_template_ItemValueParseable)
210
/// with `#[deftly(netdoc(signature))]`.
211
pub trait SignatureHashesAccumulator: Clone {
212
    /// Update `self`, ensuring that this hash is computed
213
    ///
214
    /// Should perform precisely the hash-related parts specified for
215
    /// [`SignatureItemParseable::from_unparsed_and_body`].
216
    ///
217
    /// So, if this hash is already recorded in `self`, it should not be updated.
218
    fn update_from_netdoc_body(
219
        &mut self,
220
        document_body: &SignatureHashInputs<'_>,
221
    ) -> Result<(), EP>;
222
}
223

            
224
/// The part of a network document before the first signature item
225
///
226
/// This is used for both Orderly signatures
227
/// where the hash does not contain any part of the signature Item
228
/// nor of any further signatures.
229
/// and Disorderly signatures
230
/// where the hash contains part of the signature Item.
231
/// (The Tor protocols currently only have Disorderly signatures.)
232
///
233
/// See "Signature item ordering, and signatures covering signatures"
234
/// in the [`NetdocParseableSignatures` derive](derive_deftly_template_NetdocParseableSignatures)
235
/// and <https://gitlab.torproject.org/tpo/core/torspec/-/issues/322>.
236
//
237
// This type exists as a separate newtype mostly to avoid mistakes inside
238
// parser implementations, where lots of different strings are floating about.
239
// In particular, the parser must save this value when it starts parsing
240
// signatures and must then reuse it for later ones.
241
#[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, amplify::Getters)]
242
pub struct SignedDocumentBody<'s> {
243
    /// The actual body as a string
244
    #[getter(as_copy)]
245
    pub(crate) body: &'s str,
246
}
247

            
248
/// Inputs needed to calculate a specific signature hash for a specific Item
249
///
250
/// Embodies:
251
///
252
///  * `&str` for the body, as for `SignedDocumentBody`.
253
///    For calculating Orderly signatures.
254
///    (That is, ones that do not include any part of the signature Item;
255
///    See [`SignedDocumentBody`].)
256
///
257
///  * Extra information for calculating Disorderly signatures.
258
///    Disorderly signature Items can only be implemented within this crate.
259
#[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, amplify::Getters)]
260
pub struct SignatureHashInputs<'s> {
261
    /// The Orderly body (up to the first signature item)
262
    #[getter(as_copy)]
263
    pub(crate) body: SignedDocumentBody<'s>,
264
    /// The part of the document up to just before this signature item.
265
    #[getter(skip)]
266
    pub(crate) document_sofar: &'s str,
267
    /// The signature item keyword and the following space
268
    #[getter(skip)]
269
    pub(crate) signature_item_kw_spc: &'s str,
270
    /// The whole signature item keyword line not including the final newline
271
    #[getter(skip)]
272
    pub(crate) signature_item_line: &'s str,
273
}
274

            
275
impl<'s> SignatureHashInputs<'s> {
276
    /// Hash into `h` the body and the whole of the signature item's keyword line
277
921
    pub(crate) fn hash_whole_keyword_line(&self, h: &mut impl Digest) {
278
921
        h.update(self.body().body());
279
921
        h.update(self.signature_item_line);
280
921
        h.update("\n");
281
921
    }
282
}
283

            
284
/// Hash types suitable for use as `#[deftly(netdoc(signature(hash_accu = "TY"))]`
285
///
286
/// See
287
/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
288
pub mod sig_hashes {
289
    use super::*;
290

            
291
    /// SHA-1 including the whole keyword line
292
    ///
293
    /// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
294
    #[derive(Debug, Clone, Default, Deftly)]
295
    #[derive_deftly(AsMutSelf)]
296
    #[allow(clippy::exhaustive_structs)]
297
    pub struct Sha1WholeKeywordLine(pub Option<[u8; 20]>);
298

            
299
    impl SignatureHashesAccumulator for Sha1WholeKeywordLine {
300
921
        fn update_from_netdoc_body(&mut self, body: &SignatureHashInputs<'_>) -> Result<(), EP> {
301
948
            self.0.get_or_insert_with(|| {
302
921
                let mut h = tor_llcrypto::d::Sha1::new();
303
921
                body.hash_whole_keyword_line(&mut h);
304
921
                h.finalize().into()
305
921
            });
306
921
            Ok(())
307
921
        }
308
    }
309
}
310

            
311
/// Utility function to check that a time is within a validity period
312
513
pub fn check_validity_time(
313
513
    now: SystemTime,
314
513
    validity: std::ops::RangeInclusive<SystemTime>,
315
513
) -> Result<(), VF> {
316
513
    if now < *validity.start() {
317
4
        Err(VF::TooNew)
318
509
    } else if now > *validity.end() {
319
4
        Err(VF::TooOld)
320
    } else {
321
505
        Ok(())
322
    }
323
513
}
324

            
325
/// Like [`check_validity_time()`] but with a tolerance to support clock skews.
326
///
327
/// This function does not use the `DirTolerance` struct because we want to be
328
/// agnostic of directories in this context.
329
501
pub fn check_validity_time_tolerance(
330
501
    now: SystemTime,
331
501
    validity: std::ops::RangeInclusive<SystemTime>,
332
501
    pre_tolerance: Duration,
333
501
    post_tolerance: Duration,
334
501
) -> Result<(), VF> {
335
501
    let start = *validity.start();
336
501
    let end = *validity.end();
337
501
    let validity = start.saturating_sub(pre_tolerance)..=end.saturating_add(post_tolerance);
338
501
    check_validity_time(now, validity)
339
501
}