1use crate::batching_split_before::IteratorExt as _;
10use crate::encode::{Bug, ItemObjectEncodable, NetdocEncodable, NetdocEncoder};
11use crate::parse::keyword::Keyword;
12use crate::parse::parser::{Section, SectionRules};
13use crate::parse::tokenize::{ItemResult, NetDocReader};
14use crate::parse2::{
15 self, ItemObjectParseable, NetdocUnverified as _, sig_hashes::Sha1WholeKeywordLine,
16};
17use crate::types::misc::{Fingerprint, Iso8601TimeSp, RsaPublicParse1Helper, RsaSha1Signature};
18use crate::util::str::Extent;
19use crate::{NetdocErrorKind as EK, NormalItemArgument, Result};
20
21use tor_basic_utils::impl_debug_hex;
22use tor_checkable::{signed, timed};
23use tor_error::into_internal;
24use tor_llcrypto::pk::rsa;
25use tor_llcrypto::{d, pk, pk::rsa::RsaIdentity};
26
27use std::sync::LazyLock;
28
29use std::result::Result as StdResult;
30use std::{net, time, time::Duration, time::SystemTime};
31
32use derive_deftly::Deftly;
33use digest::Digest;
34
35#[cfg(feature = "build_docs")]
36mod build;
37
38#[cfg(feature = "build_docs")]
39#[allow(deprecated)]
40pub use build::AuthCertBuilder;
41
42#[cfg(feature = "incomplete")]
43mod encoded;
44#[cfg(feature = "incomplete")]
45pub use encoded::EncodedAuthCert;
46
47decl_keyword! {
48 pub(crate) AuthCertKwd {
49 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
50 "dir-address" => DIR_ADDRESS,
51 "fingerprint" => FINGERPRINT,
52 "dir-identity-key" => DIR_IDENTITY_KEY,
53 "dir-key-published" => DIR_KEY_PUBLISHED,
54 "dir-key-expires" => DIR_KEY_EXPIRES,
55 "dir-signing-key" => DIR_SIGNING_KEY,
56 "dir-key-crosscert" => DIR_KEY_CROSSCERT,
57 "dir-key-certification" => DIR_KEY_CERTIFICATION,
58 }
59}
60
61static AUTHCERT_RULES: LazyLock<SectionRules<AuthCertKwd>> = LazyLock::new(|| {
64 use AuthCertKwd::*;
65
66 let mut rules = SectionRules::builder();
67 rules.add(DIR_KEY_CERTIFICATE_VERSION.rule().required().args(1..));
68 rules.add(DIR_ADDRESS.rule().args(1..));
69 rules.add(FINGERPRINT.rule().required().args(1..));
70 rules.add(DIR_IDENTITY_KEY.rule().required().no_args().obj_required());
71 rules.add(DIR_SIGNING_KEY.rule().required().no_args().obj_required());
72 rules.add(DIR_KEY_PUBLISHED.rule().required());
73 rules.add(DIR_KEY_EXPIRES.rule().required());
74 rules.add(DIR_KEY_CROSSCERT.rule().required().no_args().obj_required());
75 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
76 rules.add(
77 DIR_KEY_CERTIFICATION
78 .rule()
79 .required()
80 .no_args()
81 .obj_required(),
82 );
83 rules.build()
84});
85
86#[derive(Clone, Debug, Deftly)]
94#[derive_deftly(Constructor)]
95#[derive_deftly(NetdocParseableUnverified, NetdocEncodable)]
96#[cfg_attr(test, derive(PartialEq, Eq))]
97#[allow(clippy::exhaustive_structs)]
98pub struct AuthCert {
99 #[deftly(constructor(default = AuthCertVersion::V3))]
105 #[deftly(netdoc(single_arg))]
106 pub dir_key_certificate_version: AuthCertVersion,
107
108 #[deftly(netdoc(single_arg))]
110 pub dir_address: Option<net::SocketAddrV4>,
111
112 #[deftly(constructor)]
116 #[deftly(netdoc(single_arg))]
117 pub fingerprint: Fingerprint,
118
119 #[deftly(constructor)]
123 #[deftly(netdoc(single_arg))]
124 pub dir_key_published: Iso8601TimeSp,
125
126 #[deftly(constructor)]
130 #[deftly(netdoc(single_arg))]
131 pub dir_key_expires: Iso8601TimeSp,
132
133 #[deftly(constructor)]
139 pub dir_identity_key: rsa::PublicKey,
140
141 #[deftly(constructor)]
147 pub dir_signing_key: rsa::PublicKey,
148
149 #[deftly(constructor)]
153 pub dir_key_crosscert: CrossCert,
154
155 #[doc(hidden)]
156 #[deftly(netdoc(skip))]
157 pub __non_exhaustive: (),
158}
159
160#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
166#[non_exhaustive]
167pub enum AuthCertVersion {
168 #[strum(serialize = "3")]
170 V3,
171}
172
173impl NormalItemArgument for AuthCertVersion {}
174
175#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
177#[allow(clippy::exhaustive_structs)]
178pub struct AuthCertKeyIds {
179 pub id_fingerprint: rsa::RsaIdentity,
181 pub sk_fingerprint: rsa::RsaIdentity,
183}
184
185pub struct UncheckedAuthCert {
188 location: Option<Extent>,
190
191 c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
193}
194
195impl UncheckedAuthCert {
196 pub fn within<'a>(&self, haystack: &'a str) -> Option<&'a str> {
204 self.location
205 .as_ref()
206 .and_then(|ext| ext.reconstruct(haystack))
207 }
208}
209
210impl AuthCert {
211 #[cfg(feature = "build_docs")]
214 #[deprecated = "use AuthCertConstructor instead"]
215 #[allow(deprecated)]
216 pub fn builder() -> AuthCertBuilder {
217 AuthCertBuilder::new()
218 }
219
220 pub fn parse(s: &str) -> Result<UncheckedAuthCert> {
225 let mut reader = NetDocReader::new(s)?;
226 let body = AUTHCERT_RULES.parse(&mut reader)?;
227 reader.should_be_exhausted()?;
228 AuthCert::from_body(&body, s).map_err(|e| e.within(s))
229 }
230
231 pub fn parse_multiple(s: &str) -> Result<impl Iterator<Item = Result<UncheckedAuthCert>> + '_> {
233 use AuthCertKwd::*;
234 let sections = NetDocReader::new(s)?
235 .batching_split_before_loose(|item| item.is_ok_with_kwd(DIR_KEY_CERTIFICATE_VERSION));
236 Ok(sections
237 .map(|mut section| {
238 let body = AUTHCERT_RULES.parse(&mut section)?;
239 AuthCert::from_body(&body, s)
240 })
241 .map(|r| r.map_err(|e| e.within(s))))
242 }
243 pub fn signing_key(&self) -> &rsa::PublicKey {
252 &self.dir_signing_key
253 }
254
255 pub fn key_ids(&self) -> AuthCertKeyIds {
258 AuthCertKeyIds {
259 id_fingerprint: self.fingerprint.0,
260 sk_fingerprint: self.dir_signing_key.to_rsa_identity(),
261 }
262 }
263
264 pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
266 &self.fingerprint
267 }
268
269 pub fn published(&self) -> time::SystemTime {
271 *self.dir_key_published
272 }
273
274 pub fn expires(&self) -> time::SystemTime {
276 *self.dir_key_expires
277 }
278
279 fn from_body(body: &Section<'_, AuthCertKwd>, s: &str) -> Result<UncheckedAuthCert> {
281 use AuthCertKwd::*;
282
283 let start_pos = {
288 #[allow(clippy::unwrap_used)]
291 let first_item = body.first_item().unwrap();
292 if first_item.kwd() != DIR_KEY_CERTIFICATE_VERSION {
293 return Err(EK::WrongStartingToken
294 .with_msg(first_item.kwd_str().to_string())
295 .at_pos(first_item.pos()));
296 }
297 first_item.pos()
298 };
299 let end_pos = {
300 #[allow(clippy::unwrap_used)]
303 let last_item = body.last_item().unwrap();
304 if last_item.kwd() != DIR_KEY_CERTIFICATION {
305 return Err(EK::WrongEndingToken
306 .with_msg(last_item.kwd_str().to_string())
307 .at_pos(last_item.pos()));
308 }
309 last_item.end_pos()
310 };
311
312 let version = body
313 .required(DIR_KEY_CERTIFICATE_VERSION)?
314 .parse_arg::<u32>(0)?;
315 if version != 3 {
316 return Err(EK::BadDocumentVersion.with_msg(format!("unexpected version {}", version)));
317 }
318 let dir_key_certificate_version = AuthCertVersion::V3;
319
320 let dir_signing_key: rsa::PublicKey = body
321 .required(DIR_SIGNING_KEY)?
322 .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
323 .check_len(1024..)?
324 .check_exponent(65537)?
325 .into();
326
327 let dir_identity_key: rsa::PublicKey = body
328 .required(DIR_IDENTITY_KEY)?
329 .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
330 .check_len(1024..)?
331 .check_exponent(65537)?
332 .into();
333
334 let dir_key_published = body
335 .required(DIR_KEY_PUBLISHED)?
336 .args_as_str()
337 .parse::<Iso8601TimeSp>()?;
338
339 let dir_key_expires = body
340 .required(DIR_KEY_EXPIRES)?
341 .args_as_str()
342 .parse::<Iso8601TimeSp>()?;
343
344 {
345 let fp_tok = body.required(FINGERPRINT)?;
347 let fingerprint: RsaIdentity = fp_tok.args_as_str().parse::<Fingerprint>()?.into();
348 if fingerprint != dir_identity_key.to_rsa_identity() {
349 return Err(EK::BadArgument
350 .at_pos(fp_tok.pos())
351 .with_msg("fingerprint does not match RSA identity"));
352 }
353 }
354
355 let dir_address = body
356 .maybe(DIR_ADDRESS)
357 .parse_args_as_str::<net::SocketAddrV4>()?;
358
359 let dir_key_crosscert;
361 let v_crosscert = {
362 let crosscert = body.required(DIR_KEY_CROSSCERT)?;
363 #[allow(clippy::unwrap_used)]
366 let mut tag = crosscert.obj_tag().unwrap();
367 if tag != "ID SIGNATURE" && tag != "SIGNATURE" {
369 tag = "ID SIGNATURE";
370 }
371 let sig = crosscert.obj(tag)?;
372
373 let signed = dir_identity_key.to_rsa_identity();
374 let v = rsa::ValidatableRsaSignature::new(&dir_signing_key, &sig, signed.as_bytes());
377
378 dir_key_crosscert = CrossCert {
379 signature: CrossCertObject(sig),
380 };
381
382 v
383 };
384
385 let v_sig = {
387 let signature = body.required(DIR_KEY_CERTIFICATION)?;
388 let sig = signature.obj("SIGNATURE")?;
389
390 let mut sha1 = d::Sha1::new();
391 #[allow(clippy::unwrap_used)]
394 let start_offset = body.first_item().unwrap().offset_in(s).unwrap();
395 #[allow(clippy::unwrap_used)]
396 let end_offset = body.last_item().unwrap().offset_in(s).unwrap();
397 let end_offset = end_offset + "dir-key-certification\n".len();
398 sha1.update(&s[start_offset..end_offset]);
399 let sha1 = sha1.finalize();
400 rsa::ValidatableRsaSignature::new(&dir_identity_key, &sig, &sha1)
403 };
404
405 let id_fingerprint = dir_identity_key.to_rsa_identity();
406
407 let location = {
408 let start_idx = start_pos.offset_within(s);
409 let end_idx = end_pos.offset_within(s);
410 match (start_idx, end_idx) {
411 (Some(a), Some(b)) => Extent::new(s, &s[a..b + 1]),
412 _ => None,
413 }
414 };
415
416 let authcert = AuthCert {
417 dir_key_certificate_version,
418 dir_address,
419 dir_identity_key,
420 dir_signing_key,
421 dir_key_published,
422 dir_key_expires,
423 dir_key_crosscert,
424 fingerprint: Fingerprint(id_fingerprint),
425 __non_exhaustive: (),
426 };
427
428 let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
429 vec![Box::new(v_crosscert), Box::new(v_sig)];
430
431 let timed = timed::TimerangeBound::new(authcert, *dir_key_published..*dir_key_expires);
432 let signed = signed::SignatureGated::new(timed, signatures);
433 let unchecked = UncheckedAuthCert {
434 location,
435 c: signed,
436 };
437 Ok(unchecked)
438 }
439}
440
441#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
455#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
456#[deftly(netdoc(no_extra_args))]
457#[non_exhaustive]
458pub struct CrossCert {
459 #[deftly(netdoc(object))]
461 pub signature: CrossCertObject,
462}
463
464#[derive(Clone, PartialEq, Eq, derive_more::Deref)]
483#[non_exhaustive]
484pub struct CrossCertObject(pub Vec<u8>);
485impl_debug_hex! { CrossCertObject . 0 }
486
487impl CrossCert {
488 pub fn new(
490 k_auth_sign_rsa: &rsa::KeyPair,
491 h_kp_auth_id_rsa: &RsaIdentity,
492 ) -> StdResult<Self, Bug> {
493 let signature = k_auth_sign_rsa
494 .sign(h_kp_auth_id_rsa.as_bytes())
495 .map_err(into_internal!("failed to sign cross-cert"))?;
496 Ok(CrossCert {
497 signature: CrossCertObject(signature),
498 })
499 }
500}
501
502#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
512#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
513#[deftly(netdoc(signatures(hashes_accu = Sha1WholeKeywordLine)))]
514#[non_exhaustive]
515pub struct AuthCertSignatures {
516 pub dir_key_certification: RsaSha1Signature,
518}
519
520#[deprecated = "use RsaSha1Signature"]
526pub type AuthCertSignature = RsaSha1Signature;
527
528impl ItemObjectParseable for CrossCertObject {
529 fn check_label(label: &str) -> StdResult<(), parse2::EP> {
530 match label {
531 "SIGNATURE" | "ID SIGNATURE" => Ok(()),
532 _ => Err(parse2::EP::ObjectIncorrectLabel),
533 }
534 }
535
536 fn from_bytes(input: &[u8]) -> StdResult<Self, parse2::EP> {
537 Ok(Self(input.to_vec()))
538 }
539}
540
541impl ItemObjectEncodable for CrossCertObject {
542 fn label(&self) -> &str {
543 "ID SIGNATURE"
544 }
545
546 fn write_object_onto(&self, b: &mut Vec<u8>) -> StdResult<(), Bug> {
547 b.extend(&self.0);
548 Ok(())
549 }
550}
551
552impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
553 type Error = signature::Error;
554
555 fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
556 self.c.dangerously_assume_wellsigned()
557 }
558 fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
559 self.c.is_well_signed()
560 }
561}
562
563impl AuthCertUnverified {
564 pub fn verify(
582 self,
583 v3idents: &[RsaIdentity],
584 pre_tolerance: Duration,
585 post_tolerance: Duration,
586 now: SystemTime,
587 ) -> StdResult<AuthCert, parse2::VerifyFailed> {
588 let (body, sigs) = (self.body, self.sigs);
589
590 if !v3idents.contains(&body.fingerprint.0) {
592 return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
593 }
594
595 let validity = *body.dir_key_published..=*body.dir_key_expires;
597 parse2::check_validity_time_tolerance(now, validity, pre_tolerance, post_tolerance)?;
598
599 if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
601 return Err(parse2::VerifyFailed::Inconsistent);
602 }
603
604 body.dir_signing_key.verify(
606 body.fingerprint.0.as_bytes(),
607 &body.dir_key_crosscert.signature,
608 )?;
609
610 body.dir_identity_key.verify(
612 &sigs.hashes.0.ok_or(parse2::VerifyFailed::Bug)?,
613 &sigs.sigs.dir_key_certification.signature,
614 )?;
615
616 Ok(body)
617 }
618
619 pub fn verify_selfcert(self, now: SystemTime) -> StdResult<AuthCert, parse2::VerifyFailed> {
627 let h_kp_auth_id_rsa = self.inspect_unverified().0.fingerprint.0;
628 self.verify(&[h_kp_auth_id_rsa], Duration::ZERO, Duration::ZERO, now)
629 }
630}
631
632impl AuthCert {
633 pub fn new_base(
653 k_auth_id_rsa: &rsa::KeyPair,
654 k_auth_sign_rsa: &rsa::KeyPair,
655 published: SystemTime,
656 expires: SystemTime,
657 ) -> StdResult<Self, Bug> {
658 let fingerprint = k_auth_id_rsa.to_public_key().to_rsa_identity();
659 let dir_key_crosscert = CrossCert::new(k_auth_sign_rsa, &fingerprint)?;
660
661 let base = AuthCertConstructor {
662 fingerprint: fingerprint.into(),
663 dir_key_published: published.into(),
664 dir_key_expires: expires.into(),
665 dir_identity_key: k_auth_id_rsa.to_public_key(),
666 dir_signing_key: k_auth_sign_rsa.to_public_key(),
667 dir_key_crosscert,
668 }
669 .construct();
670
671 Ok(base)
672 }
673
674 #[cfg(feature = "incomplete")] pub fn encode_sign(&self, k_auth_id_rsa: &rsa::KeyPair) -> StdResult<EncodedAuthCert, Bug> {
682 let mut encoder = NetdocEncoder::new();
683 self.encode_unsigned(&mut encoder)?;
684
685 let signature =
686 RsaSha1Signature::new_sign_netdoc(k_auth_id_rsa, &encoder, "dir-key-certification")?;
687 let sigs = AuthCertSignatures {
688 dir_key_certification: signature,
689 };
690 sigs.encode_unsigned(&mut encoder)?;
691
692 let encoded = encoder.finish()?;
693 let encoded = encoded
696 .try_into()
697 .map_err(into_internal!("generated broken authcert"))?;
698 Ok(encoded)
699 }
700}
701
702#[cfg(test)]
703mod test {
704 #![allow(clippy::bool_assert_comparison)]
706 #![allow(clippy::clone_on_copy)]
707 #![allow(clippy::dbg_macro)]
708 #![allow(clippy::mixed_attributes_style)]
709 #![allow(clippy::print_stderr)]
710 #![allow(clippy::print_stdout)]
711 #![allow(clippy::single_char_pattern)]
712 #![allow(clippy::unwrap_used)]
713 #![allow(clippy::unchecked_time_subtraction)]
714 #![allow(clippy::useless_vec)]
715 #![allow(clippy::needless_pass_by_value)]
716 use super::*;
718 use crate::{Error, Pos};
719 const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
720
721 fn bad_data(fname: &str) -> String {
722 use std::fs;
723 use std::path::PathBuf;
724 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
725 path.push("testdata");
726 path.push("bad-certs");
727 path.push(fname);
728
729 fs::read_to_string(path).unwrap()
730 }
731
732 #[test]
733 fn parse_one() -> Result<()> {
734 use tor_checkable::{SelfSigned, Timebound};
735 let cert = AuthCert::parse(TESTDATA)?
736 .check_signature()
737 .unwrap()
738 .dangerously_assume_timely();
739
740 assert_eq!(
742 cert.id_fingerprint().to_string(),
743 "$ed03bb616eb2f60bec80151114bb25cef515b226"
744 );
745 assert_eq!(
746 cert.key_ids().sk_fingerprint.to_string(),
747 "$c4f720e2c59f9ddd4867fff465ca04031e35648f"
748 );
749
750 Ok(())
751 }
752
753 #[test]
754 fn parse_bad() {
755 fn check(fname: &str, err: &Error) {
756 let contents = bad_data(fname);
757 let cert = AuthCert::parse(&contents);
758 assert!(cert.is_err());
759 assert_eq!(&cert.err().unwrap(), err);
760 }
761
762 check(
763 "bad-cc-tag",
764 &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
765 );
766 check(
767 "bad-fingerprint",
768 &EK::BadArgument
769 .at_pos(Pos::from_line(2, 1))
770 .with_msg("fingerprint does not match RSA identity"),
771 );
772 check(
773 "bad-version",
774 &EK::BadDocumentVersion.with_msg("unexpected version 4"),
775 );
776 check(
777 "wrong-end",
778 &EK::WrongEndingToken
779 .with_msg("dir-key-crosscert")
780 .at_pos(Pos::from_line(37, 1)),
781 );
782 check(
783 "wrong-start",
784 &EK::WrongStartingToken
785 .with_msg("fingerprint")
786 .at_pos(Pos::from_line(1, 1)),
787 );
788 }
789
790 #[test]
791 fn test_recovery_1() {
792 let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
793 data += TESTDATA;
794
795 let res: Vec<Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
796
797 assert!(res[0].is_err());
799 assert!(res[1].is_ok());
800 assert_eq!(res.len(), 2);
801 }
802
803 #[test]
804 fn test_recovery_2() {
805 let mut data = bad_data("bad-version");
806 data += TESTDATA;
807
808 let res: Vec<Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
809
810 assert!(res[0].is_err());
812 assert!(res[1].is_ok());
813 assert_eq!(res.len(), 2);
814 }
815
816 mod parse2_test {
817 use super::{AuthCert, AuthCertUnverified, AuthCertVersion, CrossCert, CrossCertObject};
818
819 use std::{
820 net::{Ipv4Addr, SocketAddrV4},
821 str::FromStr,
822 time::{Duration, SystemTime},
823 };
824
825 use crate::{
826 parse2::{self, ErrorProblem, NetdocUnverified, ParseError, ParseInput, VerifyFailed},
827 types::{self, Iso8601TimeSp},
828 };
829
830 use derive_deftly::Deftly;
831 use tor_llcrypto::pk::rsa::{self, RsaIdentity};
832
833 const DIR_KEY_PUBLISHED: &str = "2000-01-01 00:00:05";
836 const DIR_KEY_EXPIRES: &str = "2001-01-01 00:00:05";
837 const FINGERPRINT: &str = "D190BF3B00E311A9AEB6D62B51980E9B2109BAD1";
838 const DIR_ADDRESS: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 7100);
839 const DIR_IDENTITY_KEY: &str = "
840-----BEGIN RSA PUBLIC KEY-----
841MIIBigKCAYEAt0rXD+1gYwKFAxrO4uNHQ9dQVUOGx5FxkioYNSct5Z3JU00dTKNJ
842jt4OGkFYwixWwk6KLDOiB+I/q9YIdA1NlQ5R3Hz8jjvFPVl0JQQm2LYzdSzv7/CZ
843U1qq5rYeeoYKx8qMQg4q3WgR251GEnOG+rVqzFSs0oyC+SDfYn9iMt00/pmN3HXf
844wmasY6BescVrYoDbnpkwKATizd4lzx5K8V8aXUXtd8qnYzSyHLlhiO1eufVX07YC
845+AVHV7W7qCTY/4I5Sm0dQ9jF/r04JBHnpH+aae48JOjWDCZj9AINi3rCKS8XClGb
846BB/LJidoQAZraQEEtu3Ql1mjdLreeyWfXpfZFvwKuYn44FtQsOT2TVAVNqNF8N4v
847yfwfiPN6FQWlPyMCEB81HerCn03Zi5WgQLGo7PAeO4LFrLrU16DUC5/oJENeHs0T
84827FZQyrlf0rAxiHh7TJKcjLmzeyxCQVQlr2AXXs28gKHV0AQnEcdrVOpTrquSCQQ
849hWBehR+ct4OJAgMBAAE=
850-----END RSA PUBLIC KEY-----
851 ";
852 const DIR_SIGNING_KEY: &str = "
853-----BEGIN RSA PUBLIC KEY-----
854MIIBCgKCAQEAtPF94+bThLI28kn6e+MmUECMMJ5UBlnQ+Mvwn8Zd85awPQTDz5Wu
85513sZDN3nWnhgSuP5q/WDYc5GPPtQdSWBiG1nJA2XLgEHTHf29iGZ+jAoGfIMJvBV
8561xN8baTnsha5LGx5BQ4UqzlUmoaPzwbjehnPd00FgVkpcCvKZu1HU7fGMVwn4MMh
857zuxJTqTgfcuFWTEu0H0ukOFX+51ih6WO3GWYqRiqgU0Q5/Ets8ccCTq7ND9d2u1P
858d7kQzUHbVP0KmYGK4qYntGDfP4g9SmpBoUUHyP3j9en9S6PMYv8m1YFO7M7JKu6Q
859dQZfGTxj9C/0b/jRklgn5JlKAl9eJQvCdwIDAQAB
860-----END RSA PUBLIC KEY-----
861";
862 const DIR_CROSS_CERT_OBJECT: &str = "
863-----BEGIN ID SIGNATURE-----
864NBaPdBNCNMah6cklrALzj0RdHymF/jPGOv9NmeqaXc0uTN06S/BlVM/xTjilu+dj
865sjPuT0BQL4/ZWyZR+R+gJJojKYILSId4IQ1elzRSxpFN+u2u/ZEmS6SR2SwpA05A
866btOYBKAmYkY6rLsTCbXGx3lAH2kAXfcrltCNKZXV6gqW7X379fiOnSId1OWhKPe1
867/1p3pQGZxgb8FOT1kpHxOMRBClF9Ulm3d9fQZr80Wn73gZ2Bp1RXn9c7c/71HD1c
868mzMT023bleZ574az+117yNAr6XbIgqQfzbySzVLPXM8ZN9BrGR40KDZ2638ZJjRu
8698HK5TzuknWlkRv3hCyRX+g==
870-----END ID SIGNATURE-----
871";
872 const AUTHCERT_RAW: &str = include_str!("../../testdata2/keys/authority_certificate");
873 const VALID_SYSTEM_TIME: &str = "2000-06-01 00:00:00";
878
879 const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
883
884 fn to_system_time(s: &str) -> SystemTime {
888 Iso8601TimeSp::from_str(s).unwrap().0
889 }
890
891 fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
895 rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
896 }
897
898 fn to_rsa_id(s: &str) -> RsaIdentity {
902 RsaIdentity::from_hex(s).unwrap()
903 }
904
905 #[test]
907 fn dir_auth_cross_cert() {
908 #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
909 #[derive_deftly(NetdocParseable)]
910 struct Dummy {
911 dir_key_crosscert: CrossCert,
912 }
913
914 let encoded = DIR_CROSS_CERT_OBJECT
918 .lines()
919 .filter(|line| !line.starts_with("-----"))
920 .collect::<Vec<_>>()
921 .join("\n");
922 let decoded = pem::parse(DIR_CROSS_CERT_OBJECT)
923 .unwrap()
924 .contents()
925 .to_vec();
926
927 let cert = format!(
929 "dir-key-crosscert\n-----BEGIN SIGNATURE-----\n{encoded}\n-----END SIGNATURE-----"
930 );
931 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
932 assert_eq!(
933 res,
934 Dummy {
935 dir_key_crosscert: CrossCert {
936 signature: CrossCertObject(decoded.clone())
937 }
938 }
939 );
940
941 let cert = format!(
943 "dir-key-crosscert\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
944 );
945 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
946 assert_eq!(
947 res,
948 Dummy {
949 dir_key_crosscert: CrossCert {
950 signature: CrossCertObject(decoded.clone())
951 }
952 }
953 );
954
955 let cert =
957 format!("dir-key-crosscert\n-----BEGIN WHAT-----\n{encoded}\n-----END WHAT-----");
958 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
959 match res {
960 Err(ParseError {
961 problem: ErrorProblem::ObjectIncorrectLabel,
962 doctype: "dir-key-crosscert",
963 file: _,
964 lno: 1,
965 column: None,
966 }) => {}
967 other => panic!("not expected error {other:#?}"),
968 }
969
970 let cert = format!(
972 "dir-key-crosscert arg1\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
973 );
974 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
975 match res {
976 Err(ParseError {
977 problem: ErrorProblem::UnexpectedArgument { column: 19 },
978 doctype: "dir-key-crosscert",
979 file: _,
980 lno: 1,
981 column: Some(19),
982 }) => {}
983 other => panic!("not expected error {other:#?}"),
984 }
985 }
986
987 #[test]
988 fn dir_auth_cert() {
989 let res =
990 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
991 .unwrap();
992 assert_eq!(
993 *res.inspect_unverified().0,
994 AuthCert {
995 dir_key_certificate_version: AuthCertVersion::V3,
996 dir_address: Some(DIR_ADDRESS),
997 fingerprint: types::Fingerprint(to_rsa_id(FINGERPRINT)),
998 dir_key_published: Iso8601TimeSp(to_system_time(DIR_KEY_PUBLISHED)),
999 dir_key_expires: Iso8601TimeSp(to_system_time(DIR_KEY_EXPIRES)),
1000 dir_identity_key: pem_to_rsa_pk(DIR_IDENTITY_KEY),
1001 dir_signing_key: pem_to_rsa_pk(DIR_SIGNING_KEY),
1002 dir_key_crosscert: CrossCert {
1003 signature: CrossCertObject(
1004 pem::parse(DIR_CROSS_CERT_OBJECT)
1005 .unwrap()
1006 .contents()
1007 .to_vec()
1008 )
1009 },
1010 __non_exhaustive: (),
1011 }
1012 );
1013 }
1014
1015 #[test]
1016 fn dir_auth_signature() {
1017 let res =
1018 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
1019 .unwrap();
1020
1021 res.clone()
1023 .verify(
1024 &[to_rsa_id(FINGERPRINT)],
1025 Duration::ZERO,
1026 Duration::ZERO,
1027 to_system_time(VALID_SYSTEM_TIME),
1028 )
1029 .unwrap();
1030
1031 assert_eq!(
1033 res.clone()
1034 .verify(
1035 &[],
1036 Duration::ZERO,
1037 Duration::ZERO,
1038 to_system_time(VALID_SYSTEM_TIME),
1039 )
1040 .unwrap_err(),
1041 VerifyFailed::InsufficientTrustedSigners
1042 );
1043
1044 assert_eq!(
1046 res.clone()
1047 .verify(
1048 &[to_rsa_id(FINGERPRINT)],
1049 Duration::ZERO,
1050 Duration::ZERO,
1051 SystemTime::UNIX_EPOCH,
1052 )
1053 .unwrap_err(),
1054 VerifyFailed::TooNew
1055 );
1056
1057 res.clone()
1059 .verify(
1060 &[to_rsa_id(FINGERPRINT)],
1061 Duration::ZERO,
1062 Duration::ZERO,
1063 to_system_time(DIR_KEY_PUBLISHED),
1064 )
1065 .unwrap();
1066
1067 assert_eq!(
1069 res.clone()
1070 .verify(
1071 &[to_rsa_id(FINGERPRINT)],
1072 Duration::ZERO,
1073 Duration::ZERO,
1074 to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1),
1075 )
1076 .unwrap_err(),
1077 VerifyFailed::TooNew
1078 );
1079
1080 res.clone()
1082 .verify(
1083 &[to_rsa_id(FINGERPRINT)],
1084 Duration::from_secs(1),
1085 Duration::ZERO,
1086 to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1),
1087 )
1088 .unwrap();
1089
1090 assert_eq!(
1092 res.clone()
1093 .verify(
1094 &[to_rsa_id(FINGERPRINT)],
1095 Duration::ZERO,
1096 Duration::ZERO,
1097 SystemTime::UNIX_EPOCH
1098 .checked_add(Duration::from_secs(2000000000))
1099 .unwrap(),
1100 )
1101 .unwrap_err(),
1102 VerifyFailed::TooOld
1103 );
1104
1105 res.clone()
1107 .verify(
1108 &[to_rsa_id(FINGERPRINT)],
1109 Duration::ZERO,
1110 Duration::ZERO,
1111 to_system_time(DIR_KEY_EXPIRES),
1112 )
1113 .unwrap();
1114
1115 assert_eq!(
1117 res.clone()
1118 .verify(
1119 &[to_rsa_id(FINGERPRINT)],
1120 Duration::ZERO,
1121 Duration::ZERO,
1122 to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1),
1123 )
1124 .unwrap_err(),
1125 VerifyFailed::TooOld
1126 );
1127
1128 res.clone()
1130 .verify(
1131 &[to_rsa_id(FINGERPRINT)],
1132 Duration::ZERO,
1133 Duration::from_secs(1),
1134 to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1),
1135 )
1136 .unwrap();
1137
1138 let mut cert =
1140 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
1141 .unwrap();
1142 let alternative_cert = parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(
1143 ALTERNATIVE_AUTHCERT_RAW,
1144 "",
1145 ))
1146 .unwrap();
1147 cert.body.dir_identity_key = alternative_cert.body.dir_identity_key.clone();
1148 assert_eq!(
1149 cert.verify(
1150 &[to_rsa_id(FINGERPRINT)],
1151 Duration::ZERO,
1152 Duration::ZERO,
1153 to_system_time(VALID_SYSTEM_TIME),
1154 )
1155 .unwrap_err(),
1156 VerifyFailed::Inconsistent
1157 );
1158
1159 let mut cert =
1161 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
1162 .unwrap();
1163 cert.body.dir_key_crosscert = alternative_cert.body.dir_key_crosscert.clone();
1164 assert_eq!(
1165 cert.verify(
1166 &[to_rsa_id(FINGERPRINT)],
1167 Duration::ZERO,
1168 Duration::ZERO,
1169 to_system_time(VALID_SYSTEM_TIME),
1170 )
1171 .unwrap_err(),
1172 VerifyFailed::VerifyFailed
1173 );
1174
1175 let mut cert =
1177 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
1178 .unwrap();
1179 cert.sigs = alternative_cert.sigs.clone();
1180 assert_eq!(
1181 cert.verify(
1182 &[to_rsa_id(FINGERPRINT)],
1183 Duration::ZERO,
1184 Duration::ZERO,
1185 to_system_time(VALID_SYSTEM_TIME),
1186 )
1187 .unwrap_err(),
1188 VerifyFailed::VerifyFailed
1189 );
1190 }
1191 }
1192
1193 #[cfg(feature = "incomplete")]
1194 mod encode_test {
1195 use super::*;
1196 use crate::parse2::{ParseInput, parse_netdoc};
1197 use humantime::parse_rfc3339;
1198 use std::result::Result;
1199 use tor_basic_utils::test_rng;
1200
1201 #[test]
1202 fn roundtrip() -> Result<(), anyhow::Error> {
1203 let mut rng = test_rng::testing_rng();
1204 let k_auth_id_rsa = rsa::KeyPair::generate(&mut rng)?;
1205 let k_auth_sign_rsa = rsa::KeyPair::generate(&mut rng)?;
1206
1207 let secs = |s| Duration::from_secs(s);
1208 let now = parse_rfc3339("1993-01-01T00:00:00Z")?;
1209 let published = now - secs(1000);
1210 let expires = published + secs(86400);
1211 let tolerance = secs(10);
1212
1213 let input_value = AuthCert {
1214 dir_address: Some("192.0.2.17:7000".parse()?),
1215 ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
1216 };
1217 dbg!(&input_value);
1218
1219 let encoded = input_value.encode_sign(&k_auth_id_rsa)?;
1220
1221 let reparsed_uv: AuthCertUnverified =
1222 parse_netdoc(&ParseInput::new(encoded.as_ref(), "<encoded>"))?;
1223 let reparsed_value = reparsed_uv.verify(
1224 &[k_auth_id_rsa.to_public_key().to_rsa_identity()],
1225 tolerance,
1226 tolerance,
1227 now,
1228 )?;
1229 dbg!(&reparsed_value);
1230
1231 assert_eq!(input_value, reparsed_value);
1232 Ok(())
1233 }
1234 }
1235}