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 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`].
25
pub 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)]
48
pub 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)]
64
pub 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

            
76
impl<'s> SignatureHashInputs<'s> {
77
    /// Hash into `h` the body and the whole of the signature item's keyword line
78
120
    pub(crate) fn hash_whole_keyword_line(&self, h: &mut impl Digest) {
79
120
        h.update(self.body().body());
80
120
        h.update(self.signature_item_line);
81
120
        h.update("\n");
82
120
    }
83
}
84

            
85
/// Methods suitable for use with `#[deftly(netdoc(sig_hash = "METHOD"))]`
86
///
87
/// See
88
/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
89
pub 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
120
    pub fn whole_keyword_line_sha1(body: &SignatureHashInputs) -> [u8; 20] {
96
120
        let mut h = tor_llcrypto::d::Sha1::new();
97
120
        body.hash_whole_keyword_line(&mut h);
98
120
        h.finalize().into()
99
120
    }
100
}
101

            
102
/// Utility function to check that a time is within a validity period
103
85
pub fn check_validity_time(
104
85
    now: SystemTime,
105
85
    validity: std::ops::RangeInclusive<SystemTime>,
106
85
) -> Result<(), VF> {
107
85
    if now < *validity.start() {
108
4
        Err(VF::TooNew)
109
81
    } else if now > *validity.end() {
110
4
        Err(VF::TooOld)
111
    } else {
112
77
        Ok(())
113
    }
114
85
}
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.
120
73
pub fn check_validity_time_tolerance(
121
73
    now: SystemTime,
122
73
    validity: std::ops::RangeInclusive<SystemTime>,
123
73
    pre_tolerance: Duration,
124
73
    post_tolerance: Duration,
125
73
) -> Result<(), VF> {
126
73
    let start = *validity.start();
127
73
    let end = *validity.end();
128
73
    let validity = start.saturating_sub(pre_tolerance)..=end.saturating_add(post_tolerance);
129
73
    check_validity_time(now, validity)
130
73
}