Skip to main content

tor_netdoc/parse2/
signatures.rs

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
8use saturating_time::SaturatingTime;
9
10use 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.
20pub 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`.
54pub 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]
78pub 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`].
113pub 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.
159pub 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))]`.
211pub 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)]
242pub 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)]
260pub 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
275impl<'s> SignatureHashInputs<'s> {
276    /// Hash into `h` the body and the whole of the signature item's keyword line
277    pub(crate) fn hash_whole_keyword_line(&self, h: &mut impl Digest) {
278        h.update(self.body().body());
279        h.update(self.signature_item_line);
280        h.update("\n");
281    }
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).
288pub 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        fn update_from_netdoc_body(&mut self, body: &SignatureHashInputs<'_>) -> Result<(), EP> {
301            self.0.get_or_insert_with(|| {
302                let mut h = tor_llcrypto::d::Sha1::new();
303                body.hash_whole_keyword_line(&mut h);
304                h.finalize().into()
305            });
306            Ok(())
307        }
308    }
309}
310
311/// Utility function to check that a time is within a validity period
312pub fn check_validity_time(
313    now: SystemTime,
314    validity: std::ops::RangeInclusive<SystemTime>,
315) -> Result<(), VF> {
316    if now < *validity.start() {
317        Err(VF::TooNew)
318    } else if now > *validity.end() {
319        Err(VF::TooOld)
320    } else {
321        Ok(())
322    }
323}
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.
329pub fn check_validity_time_tolerance(
330    now: SystemTime,
331    validity: std::ops::RangeInclusive<SystemTime>,
332    pre_tolerance: Duration,
333    post_tolerance: Duration,
334) -> Result<(), VF> {
335    let start = *validity.start();
336    let end = *validity.end();
337    let validity = start.saturating_sub(pre_tolerance)..=end.saturating_add(post_tolerance);
338    check_validity_time(now, validity)
339}