tor_netdoc/doc/hsdesc/
outer.rs1use once_cell::sync::Lazy;
4use tor_cert::Ed25519Cert;
5use tor_checkable::signed::SignatureGated;
6use tor_checkable::timed::TimerangeBound;
7use tor_checkable::Timebound;
8use tor_hscrypto::pk::HsBlindId;
9use tor_hscrypto::{RevisionCounter, Subcredential};
10use tor_llcrypto::pk::ed25519::{self, Ed25519Identity, ValidatableEd25519Signature};
11use tor_units::IntegerMinutes;
12
13use crate::parse::{keyword::Keyword, parser::SectionRules, tokenize::NetDocReader};
14use crate::types::misc::{UnvalidatedEdCert, B64};
15use crate::{Pos, Result};
16
17use super::desc_enc;
18
19pub(super) const HS_DESC_VERSION_CURRENT: &str = "3";
21
22pub(super) const HS_DESC_SIGNATURE_PREFIX: &[u8] = b"Tor onion service descriptor sig v3";
24
25#[derive(Clone, Debug)]
28#[cfg_attr(feature = "hsdesc-inner-docs", visibility::make(pub))]
29pub(super) struct HsDescOuter {
30 pub(super) lifetime: IntegerMinutes<u16>,
36 pub(super) desc_signing_key_cert: Ed25519Cert,
40 pub(super) revision_counter: RevisionCounter,
43 pub(super) superencrypted: Vec<u8>,
50}
51
52impl HsDescOuter {
53 pub(super) fn blinded_id(&self) -> HsBlindId {
55 let ident = self
56 .desc_signing_key_cert
57 .signing_key()
58 .expect("signing key was absent!?");
59 (*ident).into()
60 }
61
62 pub(super) fn desc_sign_key_id(&self) -> &Ed25519Identity {
64 self.desc_signing_key_cert
65 .subject_key()
66 .as_ed25519()
67 .expect(
68 "Somehow constructed an HsDescOuter with a non-Ed25519 signing key in its cert.",
69 )
70 }
71
72 pub(super) fn revision_counter(&self) -> RevisionCounter {
74 self.revision_counter
75 }
76
77 pub(super) fn decrypt_body(
80 &self,
81 subcredential: &Subcredential,
82 ) -> std::result::Result<Vec<u8>, desc_enc::DecryptionError> {
83 let decrypt = desc_enc::HsDescEncryption {
84 blinded_id: &self.blinded_id(),
85 desc_enc_nonce: None,
86 subcredential,
87 revision: self.revision_counter,
88 string_const: b"hsdir-superencrypted-data",
89 };
90
91 let mut body = decrypt.decrypt(&self.superencrypted[..])?;
92 let n_padding = body.iter().rev().take_while(|n| **n == 0).count();
93 body.truncate(body.len() - n_padding);
94 if !body.ends_with(b"\n") {
97 body.push(b'\n');
98 }
99 Ok(body)
100 }
101}
102
103pub(super) type UncheckedHsDescOuter = SignatureGated<TimerangeBound<HsDescOuter>>;
106
107decl_keyword! {
108 pub(crate) HsOuterKwd {
109 "hs-descriptor" => HS_DESCRIPTOR,
110 "descriptor-lifetime" => DESCRIPTOR_LIFETIME,
111 "descriptor-signing-key-cert" => DESCRIPTOR_SIGNING_KEY_CERT,
112 "revision-counter" => REVISION_COUNTER,
113 "superencrypted" => SUPERENCRYPTED,
114 "signature" => SIGNATURE
115 }
116}
117
118static HS_OUTER_RULES: Lazy<SectionRules<HsOuterKwd>> = Lazy::new(|| {
121 use HsOuterKwd::*;
122
123 let mut rules = SectionRules::builder();
124 rules.add(HS_DESCRIPTOR.rule().required().args(1..));
125 rules.add(DESCRIPTOR_LIFETIME.rule().required().args(1..));
126 rules.add(DESCRIPTOR_SIGNING_KEY_CERT.rule().required().obj_required());
127 rules.add(REVISION_COUNTER.rule().required().args(1..));
128 rules.add(SUPERENCRYPTED.rule().required().obj_required());
129 rules.add(SIGNATURE.rule().required().args(1..));
130 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
131
132 rules.build()
133});
134
135impl HsDescOuter {
136 #[cfg_attr(feature = "hsdesc-inner-docs", visibility::make(pub))]
138 pub(super) fn parse(s: &str) -> Result<UncheckedHsDescOuter> {
139 let mut reader = NetDocReader::new(s)?;
141 let result = HsDescOuter::take_from_reader(&mut reader).map_err(|e| e.within(s))?;
142 Ok(result)
143 }
144
145 fn take_from_reader(reader: &mut NetDocReader<'_, HsOuterKwd>) -> Result<UncheckedHsDescOuter> {
149 use crate::err::NetdocErrorKind as EK;
150 use HsOuterKwd::*;
151
152 let s = reader.str();
153 let body = HS_OUTER_RULES.parse(reader)?;
154
155 let signed_text = {
158 let first_item = body
159 .first_item()
160 .expect("Somehow parsing worked though no keywords were present‽");
161 let last_item = body
162 .last_item()
163 .expect("Somehow parsing worked though no keywords were present‽");
164 if first_item.kwd() != HS_DESCRIPTOR {
165 return Err(EK::WrongStartingToken
166 .with_msg(first_item.kwd_str().to_string())
167 .at_pos(first_item.pos()));
168 }
169 if last_item.kwd() != SIGNATURE {
170 return Err(EK::WrongEndingToken
171 .with_msg(last_item.kwd_str().to_string())
172 .at_pos(last_item.pos()));
173 }
174 let start_idx = first_item
175 .pos()
176 .offset_within(s)
177 .expect("Token came from nowhere within the string‽");
178 let end_idx = last_item
179 .pos()
180 .offset_within(s)
181 .expect("Token came from nowhere within the string‽");
182 let mut signed_text = HS_DESC_SIGNATURE_PREFIX.to_vec();
186 signed_text.extend_from_slice(
187 s.get(start_idx..end_idx)
188 .expect("Somehow the first item came after the last‽")
189 .as_bytes(),
190 );
191 signed_text
192 };
193
194 {
196 let version = body.required(HS_DESCRIPTOR)?.required_arg(0)?;
197 if version != HS_DESC_VERSION_CURRENT {
198 return Err(EK::BadDocumentVersion
199 .with_msg(format!("Unexpected hsdesc version {}", version))
200 .at_pos(Pos::at(version)));
201 }
202 }
203
204 let lifetime: IntegerMinutes<u16> = {
206 let tok = body.required(DESCRIPTOR_LIFETIME)?;
207 let lifetime_minutes: u16 = tok.parse_arg(0)?;
208 if !(30..=720).contains(&lifetime_minutes) {
209 return Err(EK::BadArgument
210 .with_msg(format!("Invalid HsDesc lifetime {}", lifetime_minutes))
211 .at_pos(tok.pos()));
212 }
213 lifetime_minutes.into()
214 };
215
216 let (unchecked_cert, kp_desc_sign) = {
220 let cert_tok = body.required(DESCRIPTOR_SIGNING_KEY_CERT)?;
221 let cert = cert_tok
222 .parse_obj::<UnvalidatedEdCert>("ED25519 CERT")?
223 .check_cert_type(tor_cert::CertType::HS_BLINDED_ID_V_SIGNING)?
224 .into_unchecked()
225 .should_have_signing_key()
226 .map_err(|err| {
227 EK::BadObjectVal
228 .err()
229 .with_source(err)
230 .at_pos(cert_tok.pos())
231 })?;
232 let kp_desc_sign: ed25519::PublicKey = cert
233 .peek_subject_key()
234 .as_ed25519()
235 .and_then(|id| id.try_into().ok())
236 .ok_or_else(|| {
237 EK::BadObjectVal
238 .err()
239 .with_msg("Invalid ed25519 subject key")
240 .at_pos(cert_tok.pos())
241 })?;
242 (cert, kp_desc_sign)
243 };
244
245 let revision_counter = body.required(REVISION_COUNTER)?.parse_arg::<u64>(0)?.into();
247 let encrypted_body: Vec<u8> = body.required(SUPERENCRYPTED)?.obj("MESSAGE")?;
248 let signature = body
249 .required(SIGNATURE)?
250 .parse_arg::<B64>(0)?
251 .into_array()
252 .map_err(|_| EK::BadSignature.with_msg("Bad signature object length"))?;
253 let signature = ed25519::Signature::from(signature);
254
255 let (desc_signing_key_cert, cert_signature) = unchecked_cert
258 .dangerously_split()
259 .map_err(|e| EK::Internal.err().with_source(e))?;
261 let desc_signing_key_cert = desc_signing_key_cert.dangerously_assume_timely();
262 let expiration = desc_signing_key_cert.expiry();
264
265 let desc = HsDescOuter {
267 lifetime,
268 desc_signing_key_cert,
269 revision_counter,
270 superencrypted: encrypted_body,
271 };
272 let desc = TimerangeBound::new(desc, ..expiration);
274 let signatures: Vec<Box<dyn tor_llcrypto::pk::ValidatableSignature>> = vec![
276 Box::new(cert_signature),
277 Box::new(ValidatableEd25519Signature::new(
278 kp_desc_sign,
279 signature,
280 &signed_text[..],
281 )),
282 ];
283 Ok(SignatureGated::new(desc, signatures))
284 }
285}
286
287#[cfg(test)]
288mod test {
289 #![allow(clippy::bool_assert_comparison)]
291 #![allow(clippy::clone_on_copy)]
292 #![allow(clippy::dbg_macro)]
293 #![allow(clippy::mixed_attributes_style)]
294 #![allow(clippy::print_stderr)]
295 #![allow(clippy::print_stdout)]
296 #![allow(clippy::single_char_pattern)]
297 #![allow(clippy::unwrap_used)]
298 #![allow(clippy::unchecked_duration_subtraction)]
299 #![allow(clippy::useless_vec)]
300 #![allow(clippy::needless_pass_by_value)]
301 use super::*;
303 use crate::doc::hsdesc::test_data::{TEST_DATA, TEST_SUBCREDENTIAL};
304 use tor_checkable::SelfSigned;
305
306 #[test]
307 fn parse_good() -> Result<()> {
308 let desc = HsDescOuter::parse(TEST_DATA)?;
309
310 let desc = desc
311 .check_signature()?
312 .check_valid_at(&humantime::parse_rfc3339("2023-01-23T15:00:00Z").unwrap())
313 .unwrap();
314
315 assert_eq!(desc.lifetime.as_minutes(), 180);
316 assert_eq!(desc.revision_counter(), 19655750.into());
317 assert_eq!(
318 desc.desc_sign_key_id().to_string(),
319 "CtiubqLBP1MCviR9SxAW9brjMKSguQFE/vHku3kE4Xo"
320 );
321
322 let subcred: tor_hscrypto::Subcredential = TEST_SUBCREDENTIAL.into();
323 let inner = desc.decrypt_body(&subcred).unwrap();
324
325 assert!(std::str::from_utf8(&inner)
326 .unwrap()
327 .starts_with("desc-auth-type"));
328
329 Ok(())
330 }
331}