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}