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 signature item that can appear in a netdoc
13///
14/// This is the type `T` of a field `item: T` in a netdoc signatures section type.
15///
16/// Types that implement this embody both:
17///
18/// * The item, parameters, and signature data, provided in the document.
19/// * The hash of the document body, which will needed during verification.
20///
21/// Typically derived with
22/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
23///
24/// Normal (non-signature) items implement [`ItemValueParseable`].
25pub trait SignatureItemParseable: Sized {
26 /// Parse the item's value
27 fn from_unparsed_and_body(
28 item: UnparsedItem<'_>,
29 document_body: &SignatureHashInputs<'_>,
30 ) -> Result<Self, ErrorProblem>;
31}
32
33/// The part of a network document before the first signature item
34///
35/// This is used for both Regular signatures
36/// where the hash does not contain any part of the signature Item
37/// (of which there are none yet)
38/// and Irregular signatures
39/// where the hash contains part of the signature Item.
40///
41/// See <https://gitlab.torproject.org/tpo/core/torspec/-/issues/322>.
42//
43// This type exists as a separate newtype mostly to avoid mistakes inside
44// parser implementations, where lots of different strings are floating about.
45// In particular, the parser must save this value when it starts parsing
46// signatures and must then reuse it for later ones.
47#[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, amplify::Getters)]
48pub struct SignedDocumentBody<'s> {
49 /// The actual body as a string
50 #[getter(as_copy)]
51 pub(crate) body: &'s str,
52}
53
54/// Inputs needed to calculate a specific signature hash for a specific Item
55///
56/// Embodies:
57///
58/// * `&str` for the body, as for `SignedDocumentBody`.
59/// For calculating Regular signatures.
60///
61/// * Extra information for calculating Irregular signatures.
62/// Irregular signature Items can only be implemented within this crate.
63#[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, amplify::Getters)]
64pub struct SignatureHashInputs<'s> {
65 /// The Regular body
66 #[getter(as_copy)]
67 pub(crate) body: SignedDocumentBody<'s>,
68 /// The signature item keyword and the following space
69 #[getter(skip)]
70 pub(crate) signature_item_kw_spc: &'s str,
71 /// The whole signature item keyword line not including the final newline
72 #[getter(skip)]
73 pub(crate) signature_item_line: &'s str,
74}
75
76impl<'s> SignatureHashInputs<'s> {
77 /// Hash into `h` the body and the whole of the signature item's keyword line
78 pub(crate) fn hash_whole_keyword_line(&self, h: &mut impl Digest) {
79 h.update(self.body().body());
80 h.update(self.signature_item_line);
81 h.update("\n");
82 }
83}
84
85/// Methods suitable for use with `#[deftly(netdoc(sig_hash = "METHOD"))]`
86///
87/// See
88/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
89pub mod sig_hash_methods {
90 use super::*;
91
92 /// SHA-1 including the whole keyword line
93 ///
94 /// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
95 pub fn whole_keyword_line_sha1(body: &SignatureHashInputs) -> [u8; 20] {
96 let mut h = tor_llcrypto::d::Sha1::new();
97 body.hash_whole_keyword_line(&mut h);
98 h.finalize().into()
99 }
100}
101
102/// Utility function to check that a time is within a validity period
103pub fn check_validity_time(
104 now: SystemTime,
105 validity: std::ops::RangeInclusive<SystemTime>,
106) -> Result<(), VF> {
107 if now < *validity.start() {
108 Err(VF::TooNew)
109 } else if now > *validity.end() {
110 Err(VF::TooOld)
111 } else {
112 Ok(())
113 }
114}
115
116/// Like [`check_validity_time()`] but with a tolerance to support clock skews.
117///
118/// This function does not use the `DirTolerance` struct because we want to be
119/// agnostic of directories in this context.
120pub fn check_validity_time_tolerance(
121 now: SystemTime,
122 validity: std::ops::RangeInclusive<SystemTime>,
123 pre_tolerance: Duration,
124 post_tolerance: Duration,
125) -> Result<(), VF> {
126 let start = *validity.start();
127 let end = *validity.end();
128 let validity = start.saturating_sub(pre_tolerance)..=end.saturating_add(post_tolerance);
129 check_validity_time(now, validity)
130}