Skip to main content

tor_netdoc/types/
descriptor.rs

1//! Shared types between router descriptors and extra-info documents.
2//!
3//! For now, this only includes code related to signatures.
4//!
5//! Despite this being used by router descriptors and extra-info documents,
6//! many types inside this module are prefixed with "router".  We keep this
7//! prefix because we want the type names to align with the item keyword,
8//! which is the same in both documents.
9
10use crate::{
11    encode::NetdocEncoder,
12    parse2::{
13        ErrorProblem as EP, ItemArgumentParseable, SignatureHashInputs, SignatureItemParseable,
14        UnparsedItem,
15    },
16    types::FixedB64,
17};
18
19use derive_deftly::Deftly;
20
21use digest::Digest;
22use tor_error::Bug;
23use tor_llcrypto::pk::ed25519;
24
25/// Accumulator for router descriptor hash signatures.
26#[derive(Debug, Clone, Default, Deftly)]
27#[derive_deftly(AsMutSelf)]
28#[allow(clippy::exhaustive_structs)]
29pub struct RouterHashAccu {
30    /// Potentially the SHA-1 for the signature.
31    pub sha1: Option<[u8; 20]>,
32    /// Potentially the SHA-256 for the signature.
33    pub sha256: Option<[u8; 32]>,
34}
35
36/// SHA-256 router descriptor signature including magic and the keyword.
37#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
38#[derive_deftly(ItemValueEncodable)]
39#[allow(clippy::exhaustive_structs)]
40// TODO SPEC is RouterSigEd25519 not a standard-ish kind of signature?
41// TODO DIRAUTH is RouterSigEd25519 not a standard-ish kind of signature?
42pub struct RouterSigEd25519(pub ed25519::Signature);
43
44impl RouterSigEd25519 {
45    /// The magic prefix for hashing this type of signature.
46    //
47    // TODO DIRMIRROR have the old parser's verification code use this
48    // constant, thereby de-duplicating.
49    const HASH_PREFIX_MAGIC: &str = "Tor router descriptor signature v1";
50
51    /// Calculate the hash for signature
52    ///
53    /// `signature_item_kw_spc` is the keyword *with a trailing space*.
54    /// It's `&[&str]` for the convenience of the two call sites.
55    fn hash(document_sofar: &str, signature_item_kw_spc: &[&str]) -> [u8; 32] {
56        debug_assert!(
57            signature_item_kw_spc
58                .last()
59                .expect("signature_item_kw_spc")
60                .ends_with(" ")
61        );
62        let mut h = tor_llcrypto::d::Sha256::new();
63        h.update(Self::HASH_PREFIX_MAGIC);
64        h.update(document_sofar);
65        for b in signature_item_kw_spc {
66            h.update(b);
67        }
68        h.finalize().into()
69    }
70
71    /// Make a signature during document encoding
72    ///
73    /// `item_keyword` is the keyword for the signature item.
74    ///
75    /// # Example
76    ///
77    /// ```
78    /// use derive_deftly::Deftly;
79    /// use tor_error::Bug;
80    /// use tor_llcrypto::pk::ed25519;
81    /// use tor_netdoc::derive_deftly_template_NetdocEncodable;
82    /// use tor_netdoc::encode::{NetdocEncodable, NetdocEncoder};
83    /// use tor_netdoc::types::descriptor::RouterSigEd25519;
84    ///
85    /// #[derive(Deftly, Default)]
86    /// #[derive_deftly(NetdocEncodable)]
87    /// pub struct Document {
88    ///     pub document_intro_keyword: (),
89    /// }
90    /// #[derive(Deftly)]
91    /// #[derive_deftly(NetdocEncodable)]
92    /// pub struct DocumentSignatures {
93    ///     pub document_signature: RouterSigEd25519,
94    /// }
95    /// impl Document {
96    ///     pub fn encode_sign(&self, k: &ed25519::Keypair) -> Result<String, Bug> {
97    ///         let mut encoder = NetdocEncoder::new();
98    ///         self.encode_unsigned(&mut encoder)?;
99    ///         let document_signature =
100    ///             RouterSigEd25519::new_sign_netdoc(k, &encoder, "document-signature")?;
101    ///         let sigs = DocumentSignatures { document_signature };
102    ///         sigs.encode_unsigned(&mut encoder)?;
103    ///         let encoded = encoder.finish()?;
104    ///         Ok(encoded)
105    ///     }
106    /// }
107    ///
108    /// # fn main() -> Result<(), anyhow::Error> {
109    /// let k = ed25519::Keypair::generate(&mut tor_basic_utils::test_rng::testing_rng());
110    /// let doc = Document::default();
111    /// let encoded = doc.encode_sign(&k)?;
112    /// assert!(encoded.starts_with(concat!(
113    ///     "document-intro-keyword\n",
114    ///     "document-signature ",
115    /// )));
116    /// # Ok(())
117    /// # }
118    /// ```
119    pub fn new_sign_netdoc(
120        private_key: &ed25519::Keypair,
121        encoder: &NetdocEncoder,
122        item_keyword: &str,
123    ) -> Result<Self, Bug> {
124        let signature = private_key
125            .sign(&Self::hash(encoder.text_sofar()?, &[item_keyword, " "]))
126            .to_bytes()
127            .into();
128        Ok(RouterSigEd25519(signature))
129    }
130}
131
132impl SignatureItemParseable for RouterSigEd25519 {
133    type HashAccu = RouterHashAccu;
134
135    fn from_unparsed_and_body(
136        mut item: UnparsedItem<'_>,
137        hash_inputs: &SignatureHashInputs<'_>,
138        hash: &mut Self::HashAccu,
139    ) -> Result<Self, EP> {
140        // TODO DIRMIRROR break this out into impl ItemArgumentParseable for Signature
141        let args = item.args_mut();
142        let sig = FixedB64::<64>::from_args(args)
143            .map_err(|e| args.handle_error("router-sig-ed25519", e))?
144            .0;
145        let sig = ed25519::Signature::from(sig);
146        hash.sha256 = Some(Self::hash(
147            hash_inputs.document_sofar,
148            &[hash_inputs.signature_item_kw_spc],
149        ));
150        Ok(Self(sig))
151    }
152}
153
154/// SHA-1 router descriptor signature over `router-sig-ed25519`.
155// TODO DIRMIRROR Is this not the same as RsaSha1Signature ?
156#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
157#[derive_deftly(ItemValueEncodable)]
158#[allow(clippy::exhaustive_structs)]
159pub struct RouterSignature(
160    #[deftly(netdoc(object(label = "SIGNATURE"), with = crate::types::raw_data_object))] //
161    pub  Vec<u8>,
162);
163
164impl SignatureItemParseable for RouterSignature {
165    type HashAccu = RouterHashAccu;
166
167    fn from_unparsed_and_body(
168        mut item: UnparsedItem<'_>,
169        hash_inputs: &SignatureHashInputs<'_>,
170        hash: &mut Self::HashAccu,
171    ) -> Result<Self, EP> {
172        // There must be no additonal arguments.
173        let args = item.args_mut();
174        if args.next().is_some() {
175            return Err(EP::UnexpectedArgument {
176                column: args.prev_arg_column(),
177            });
178        }
179        let obj = item.object().ok_or(EP::MissingObject)?.decode_data()?;
180
181        let mut h = tor_llcrypto::d::Sha1::new();
182        h.update(hash_inputs.document_sofar);
183        h.update(hash_inputs.signature_item_line);
184        h.update("\n");
185        hash.sha1 = Some(h.finalize().into());
186
187        Ok(Self(obj))
188    }
189}