1use crate::batching_split_before::IteratorExt as _;
10use crate::encode::{
11 Bug, ItemArgument, ItemEncoder, ItemObjectEncodable, NetdocEncodable, NetdocEncoder,
12};
13use crate::parse::keyword::Keyword;
14use crate::parse::parser::{Section, SectionRules};
15use crate::parse::tokenize::{ItemResult, NetDocReader};
16use crate::parse2::{
17 self, ArgumentError, ArgumentStream, ItemArgumentParseable, ItemObjectParseable,
18 NetdocUnverified as _, sig_hashes::Sha1WholeKeywordLine,
19};
20use crate::types::misc::{Fingerprint, Iso8601TimeSp, RsaPublicParse1Helper, RsaSha1Signature};
21use crate::util::str::Extent;
22use crate::{NetdocErrorKind as EK, NormalItemArgument, Result};
23
24use tor_basic_utils::impl_debug_hex;
25use tor_checkable::{signed, timed};
26use tor_error::into_internal;
27use tor_llcrypto::pk::rsa;
28use tor_llcrypto::{d, pk, pk::rsa::RsaIdentity};
29
30use std::sync::LazyLock;
31
32use std::result::Result as StdResult;
33use std::{net, time, time::Duration, time::SystemTime};
34
35use derive_deftly::Deftly;
36use digest::Digest;
37
38#[cfg(feature = "build_docs")]
39mod build;
40
41#[cfg(feature = "build_docs")]
42#[allow(deprecated)]
43pub use build::AuthCertBuilder;
44
45#[cfg(feature = "incomplete")]
46mod encoded;
47#[cfg(feature = "incomplete")]
48pub use encoded::EncodedAuthCert;
49
50decl_keyword! {
51 pub(crate) AuthCertKwd {
52 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
53 "dir-address" => DIR_ADDRESS,
54 "fingerprint" => FINGERPRINT,
55 "dir-identity-key" => DIR_IDENTITY_KEY,
56 "dir-key-published" => DIR_KEY_PUBLISHED,
57 "dir-key-expires" => DIR_KEY_EXPIRES,
58 "dir-signing-key" => DIR_SIGNING_KEY,
59 "dir-key-crosscert" => DIR_KEY_CROSSCERT,
60 "dir-key-certification" => DIR_KEY_CERTIFICATION,
61 }
62}
63
64static AUTHCERT_RULES: LazyLock<SectionRules<AuthCertKwd>> = LazyLock::new(|| {
67 use AuthCertKwd::*;
68
69 let mut rules = SectionRules::builder();
70 rules.add(DIR_KEY_CERTIFICATE_VERSION.rule().required().args(1..));
71 rules.add(DIR_ADDRESS.rule().args(1..));
72 rules.add(FINGERPRINT.rule().required().args(1..));
73 rules.add(DIR_IDENTITY_KEY.rule().required().no_args().obj_required());
74 rules.add(DIR_SIGNING_KEY.rule().required().no_args().obj_required());
75 rules.add(DIR_KEY_PUBLISHED.rule().required());
76 rules.add(DIR_KEY_EXPIRES.rule().required());
77 rules.add(DIR_KEY_CROSSCERT.rule().required().no_args().obj_required());
78 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
79 rules.add(
80 DIR_KEY_CERTIFICATION
81 .rule()
82 .required()
83 .no_args()
84 .obj_required(),
85 );
86 rules.build()
87});
88
89#[derive(Clone, Debug, Deftly)]
97#[derive_deftly(Constructor)]
98#[derive_deftly(NetdocParseableUnverified, NetdocEncodable)]
99#[cfg_attr(test, derive(PartialEq, Eq))]
100#[allow(clippy::exhaustive_structs)]
101pub struct AuthCert {
102 #[deftly(constructor(default = AuthCertVersion::V3))]
108 #[deftly(netdoc(single_arg))]
109 pub dir_key_certificate_version: AuthCertVersion,
110
111 #[deftly(netdoc(single_arg))]
113 pub dir_address: Option<net::SocketAddrV4>,
114
115 #[deftly(constructor)]
119 #[deftly(netdoc(single_arg))]
120 pub fingerprint: Fingerprint,
121
122 #[deftly(constructor)]
126 #[deftly(netdoc(single_arg))]
127 pub dir_key_published: Iso8601TimeSp,
128
129 #[deftly(constructor)]
133 #[deftly(netdoc(single_arg))]
134 pub dir_key_expires: Iso8601TimeSp,
135
136 #[deftly(constructor)]
142 pub dir_identity_key: rsa::PublicKey,
143
144 #[deftly(constructor)]
150 pub dir_signing_key: rsa::PublicKey,
151
152 #[deftly(constructor)]
156 pub dir_key_crosscert: CrossCert,
157
158 #[doc(hidden)]
159 #[deftly(netdoc(skip))]
160 pub __non_exhaustive: (),
161}
162
163#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
169#[non_exhaustive]
170pub enum AuthCertVersion {
171 #[strum(serialize = "3")]
173 V3,
174}
175
176impl NormalItemArgument for AuthCertVersion {}
177
178#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
180#[allow(clippy::exhaustive_structs)]
181pub struct AuthCertKeyIds {
182 pub id_fingerprint: rsa::RsaIdentity,
184 pub sk_fingerprint: rsa::RsaIdentity,
186}
187
188pub struct UncheckedAuthCert {
191 location: Option<Extent>,
193
194 c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
196}
197
198impl UncheckedAuthCert {
199 pub fn within<'a>(&self, haystack: &'a str) -> Option<&'a str> {
207 self.location
208 .as_ref()
209 .and_then(|ext| ext.reconstruct(haystack))
210 }
211}
212
213impl AuthCert {
214 #[cfg(feature = "build_docs")]
217 #[deprecated = "use AuthCertConstructor instead"]
218 #[allow(deprecated)]
219 pub fn builder() -> AuthCertBuilder {
220 AuthCertBuilder::new()
221 }
222
223 pub fn parse(s: &str) -> Result<UncheckedAuthCert> {
228 let mut reader = NetDocReader::new(s)?;
229 let body = AUTHCERT_RULES.parse(&mut reader)?;
230 reader.should_be_exhausted()?;
231 AuthCert::from_body(&body, s).map_err(|e| e.within(s))
232 }
233
234 pub fn parse_multiple(s: &str) -> Result<impl Iterator<Item = Result<UncheckedAuthCert>> + '_> {
236 use AuthCertKwd::*;
237 let sections = NetDocReader::new(s)?
238 .batching_split_before_loose(|item| item.is_ok_with_kwd(DIR_KEY_CERTIFICATE_VERSION));
239 Ok(sections
240 .map(|mut section| {
241 let body = AUTHCERT_RULES.parse(&mut section)?;
242 AuthCert::from_body(&body, s)
243 })
244 .map(|r| r.map_err(|e| e.within(s))))
245 }
246 pub fn signing_key(&self) -> &rsa::PublicKey {
255 &self.dir_signing_key
256 }
257
258 pub fn key_ids(&self) -> AuthCertKeyIds {
261 AuthCertKeyIds {
262 id_fingerprint: self.fingerprint.0,
263 sk_fingerprint: self.dir_signing_key.to_rsa_identity(),
264 }
265 }
266
267 pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
269 &self.fingerprint
270 }
271
272 pub fn published(&self) -> time::SystemTime {
274 *self.dir_key_published
275 }
276
277 pub fn expires(&self) -> time::SystemTime {
279 *self.dir_key_expires
280 }
281
282 fn from_body(body: &Section<'_, AuthCertKwd>, s: &str) -> Result<UncheckedAuthCert> {
284 use AuthCertKwd::*;
285
286 let start_pos = {
291 #[allow(clippy::unwrap_used)]
294 let first_item = body.first_item().unwrap();
295 if first_item.kwd() != DIR_KEY_CERTIFICATE_VERSION {
296 return Err(EK::WrongStartingToken
297 .with_msg(first_item.kwd_str().to_string())
298 .at_pos(first_item.pos()));
299 }
300 first_item.pos()
301 };
302 let end_pos = {
303 #[allow(clippy::unwrap_used)]
306 let last_item = body.last_item().unwrap();
307 if last_item.kwd() != DIR_KEY_CERTIFICATION {
308 return Err(EK::WrongEndingToken
309 .with_msg(last_item.kwd_str().to_string())
310 .at_pos(last_item.pos()));
311 }
312 last_item.end_pos()
313 };
314
315 let version = body
316 .required(DIR_KEY_CERTIFICATE_VERSION)?
317 .parse_arg::<u32>(0)?;
318 if version != 3 {
319 return Err(EK::BadDocumentVersion.with_msg(format!("unexpected version {}", version)));
320 }
321 let dir_key_certificate_version = AuthCertVersion::V3;
322
323 let dir_signing_key: rsa::PublicKey = body
324 .required(DIR_SIGNING_KEY)?
325 .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
326 .check_len(1024..)?
327 .check_exponent(65537)?
328 .into();
329
330 let dir_identity_key: rsa::PublicKey = body
331 .required(DIR_IDENTITY_KEY)?
332 .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
333 .check_len(1024..)?
334 .check_exponent(65537)?
335 .into();
336
337 let dir_key_published = body
338 .required(DIR_KEY_PUBLISHED)?
339 .args_as_str()
340 .parse::<Iso8601TimeSp>()?;
341
342 let dir_key_expires = body
343 .required(DIR_KEY_EXPIRES)?
344 .args_as_str()
345 .parse::<Iso8601TimeSp>()?;
346
347 {
348 let fp_tok = body.required(FINGERPRINT)?;
350 let fingerprint: RsaIdentity = fp_tok.args_as_str().parse::<Fingerprint>()?.into();
351 if fingerprint != dir_identity_key.to_rsa_identity() {
352 return Err(EK::BadArgument
353 .at_pos(fp_tok.pos())
354 .with_msg("fingerprint does not match RSA identity"));
355 }
356 }
357
358 let dir_address = body
359 .maybe(DIR_ADDRESS)
360 .parse_args_as_str::<net::SocketAddrV4>()?;
361
362 let dir_key_crosscert;
364 let v_crosscert = {
365 let crosscert = body.required(DIR_KEY_CROSSCERT)?;
366 #[allow(clippy::unwrap_used)]
369 let mut tag = crosscert.obj_tag().unwrap();
370 if tag != "ID SIGNATURE" && tag != "SIGNATURE" {
372 tag = "ID SIGNATURE";
373 }
374 let sig = crosscert.obj(tag)?;
375
376 let signed = dir_identity_key.to_rsa_identity();
377 let v = rsa::ValidatableRsaSignature::new(&dir_signing_key, &sig, signed.as_bytes());
380
381 dir_key_crosscert = CrossCert {
382 signature: CrossCertObject(sig),
383 };
384
385 v
386 };
387
388 let v_sig = {
390 let signature = body.required(DIR_KEY_CERTIFICATION)?;
391 let sig = signature.obj("SIGNATURE")?;
392
393 let mut sha1 = d::Sha1::new();
394 #[allow(clippy::unwrap_used)]
397 let start_offset = body.first_item().unwrap().offset_in(s).unwrap();
398 #[allow(clippy::unwrap_used)]
399 let end_offset = body.last_item().unwrap().offset_in(s).unwrap();
400 let end_offset = end_offset + "dir-key-certification\n".len();
401 sha1.update(&s[start_offset..end_offset]);
402 let sha1 = sha1.finalize();
403 rsa::ValidatableRsaSignature::new(&dir_identity_key, &sig, &sha1)
406 };
407
408 let id_fingerprint = dir_identity_key.to_rsa_identity();
409
410 let location = {
411 let start_idx = start_pos.offset_within(s);
412 let end_idx = end_pos.offset_within(s);
413 match (start_idx, end_idx) {
414 (Some(a), Some(b)) => Extent::new(s, &s[a..b + 1]),
415 _ => None,
416 }
417 };
418
419 let authcert = AuthCert {
420 dir_key_certificate_version,
421 dir_address,
422 dir_identity_key,
423 dir_signing_key,
424 dir_key_published,
425 dir_key_expires,
426 dir_key_crosscert,
427 fingerprint: Fingerprint(id_fingerprint),
428 __non_exhaustive: (),
429 };
430
431 let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
432 vec![Box::new(v_crosscert), Box::new(v_sig)];
433
434 let timed = timed::TimerangeBound::new(authcert, *dir_key_published..*dir_key_expires);
435 let signed = signed::SignatureGated::new(timed, signatures);
436 let unchecked = UncheckedAuthCert {
437 location,
438 c: signed,
439 };
440 Ok(unchecked)
441 }
442}
443
444pub(crate) mod keyids_directory_signature_args {
456 use super::*;
457 use std::result::Result;
458
459 pub(crate) fn from_args<'s>(
461 args: &mut ArgumentStream<'s>,
462 ) -> Result<AuthCertKeyIds, ArgumentError> {
463 let mut fp = || Ok::<_, ArgumentError>(Fingerprint::from_args(args)?.0);
464 Ok(AuthCertKeyIds {
465 id_fingerprint: fp()?,
466 sk_fingerprint: fp()?,
467 })
468 }
469
470 pub(crate) fn write_arg_onto(
472 self_: &AuthCertKeyIds,
473 out: &mut ItemEncoder<'_>,
474 ) -> Result<(), Bug> {
475 let mut fp = |id| Fingerprint(id).write_arg_onto(out);
476 fp(self_.id_fingerprint)?;
477 fp(self_.sk_fingerprint)?;
478 Ok(())
479 }
480}
481
482#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
496#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
497#[deftly(netdoc(no_extra_args))]
498#[non_exhaustive]
499pub struct CrossCert {
500 #[deftly(netdoc(object))]
502 pub signature: CrossCertObject,
503}
504
505#[derive(Clone, PartialEq, Eq, derive_more::Deref)]
524#[non_exhaustive]
525pub struct CrossCertObject(pub Vec<u8>);
526impl_debug_hex! { CrossCertObject . 0 }
527
528impl CrossCert {
529 pub fn new(
531 k_auth_sign_rsa: &rsa::KeyPair,
532 h_kp_auth_id_rsa: &RsaIdentity,
533 ) -> StdResult<Self, Bug> {
534 let signature = k_auth_sign_rsa
535 .sign(h_kp_auth_id_rsa.as_bytes())
536 .map_err(into_internal!("failed to sign cross-cert"))?;
537 Ok(CrossCert {
538 signature: CrossCertObject(signature),
539 })
540 }
541}
542
543#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
553#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
554#[deftly(netdoc(signatures(hashes_accu = Sha1WholeKeywordLine)))]
555#[non_exhaustive]
556pub struct AuthCertSignatures {
557 pub dir_key_certification: RsaSha1Signature,
559}
560
561#[deprecated = "use RsaSha1Signature"]
567pub type AuthCertSignature = RsaSha1Signature;
568
569impl ItemObjectParseable for CrossCertObject {
570 fn check_label(label: &str) -> StdResult<(), parse2::EP> {
571 match label {
572 "SIGNATURE" | "ID SIGNATURE" => Ok(()),
573 _ => Err(parse2::EP::ObjectIncorrectLabel),
574 }
575 }
576
577 fn from_bytes(input: &[u8]) -> StdResult<Self, parse2::EP> {
578 Ok(Self(input.to_vec()))
579 }
580}
581
582impl ItemObjectEncodable for CrossCertObject {
583 fn label(&self) -> &str {
584 "ID SIGNATURE"
585 }
586
587 fn write_object_onto(&self, b: &mut Vec<u8>) -> StdResult<(), Bug> {
588 b.extend(&self.0);
589 Ok(())
590 }
591}
592
593impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
594 type Error = signature::Error;
595
596 fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
597 self.c.dangerously_assume_wellsigned()
598 }
599 fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
600 self.c.is_well_signed()
601 }
602}
603
604impl AuthCertUnverified {
605 pub fn verify(
623 self,
624 v3idents: &[RsaIdentity],
625 pre_tolerance: Duration,
626 post_tolerance: Duration,
627 now: SystemTime,
628 ) -> StdResult<AuthCert, parse2::VerifyFailed> {
629 let (body, sigs) = (self.body, self.sigs);
630
631 if !v3idents.contains(&body.fingerprint.0) {
633 return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
634 }
635
636 let validity = *body.dir_key_published..=*body.dir_key_expires;
638 parse2::check_validity_time_tolerance(now, validity, pre_tolerance, post_tolerance)?;
639
640 if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
642 return Err(parse2::VerifyFailed::Inconsistent);
643 }
644
645 body.dir_signing_key.verify(
647 body.fingerprint.0.as_bytes(),
648 &body.dir_key_crosscert.signature,
649 )?;
650
651 body.dir_identity_key.verify(
653 &sigs.hashes.0.ok_or(parse2::VerifyFailed::Bug)?,
654 &sigs.sigs.dir_key_certification.signature,
655 )?;
656
657 Ok(body)
658 }
659
660 pub fn verify_selfcert(self, now: SystemTime) -> StdResult<AuthCert, parse2::VerifyFailed> {
668 let h_kp_auth_id_rsa = self.inspect_unverified().0.fingerprint.0;
669 self.verify(&[h_kp_auth_id_rsa], Duration::ZERO, Duration::ZERO, now)
670 }
671}
672
673impl AuthCert {
674 pub fn new_base(
694 k_auth_id_rsa: &rsa::KeyPair,
695 k_auth_sign_rsa: &rsa::KeyPair,
696 published: SystemTime,
697 expires: SystemTime,
698 ) -> StdResult<Self, Bug> {
699 let fingerprint = k_auth_id_rsa.to_public_key().to_rsa_identity();
700 let dir_key_crosscert = CrossCert::new(k_auth_sign_rsa, &fingerprint)?;
701
702 let base = AuthCertConstructor {
703 fingerprint: fingerprint.into(),
704 dir_key_published: published.into(),
705 dir_key_expires: expires.into(),
706 dir_identity_key: k_auth_id_rsa.to_public_key(),
707 dir_signing_key: k_auth_sign_rsa.to_public_key(),
708 dir_key_crosscert,
709 }
710 .construct();
711
712 Ok(base)
713 }
714
715 #[cfg(feature = "incomplete")] pub fn encode_sign(&self, k_auth_id_rsa: &rsa::KeyPair) -> StdResult<EncodedAuthCert, Bug> {
723 let mut encoder = NetdocEncoder::new();
724 self.encode_unsigned(&mut encoder)?;
725
726 let signature =
727 RsaSha1Signature::new_sign_netdoc(k_auth_id_rsa, &encoder, "dir-key-certification")?;
728 let sigs = AuthCertSignatures {
729 dir_key_certification: signature,
730 };
731 sigs.encode_unsigned(&mut encoder)?;
732
733 let encoded = encoder.finish()?;
734 let encoded = encoded
737 .try_into()
738 .map_err(into_internal!("generated broken authcert"))?;
739 Ok(encoded)
740 }
741}
742
743#[cfg(test)]
744mod test {
745 #![allow(clippy::bool_assert_comparison)]
747 #![allow(clippy::clone_on_copy)]
748 #![allow(clippy::dbg_macro)]
749 #![allow(clippy::mixed_attributes_style)]
750 #![allow(clippy::print_stderr)]
751 #![allow(clippy::print_stdout)]
752 #![allow(clippy::single_char_pattern)]
753 #![allow(clippy::unwrap_used)]
754 #![allow(clippy::unchecked_time_subtraction)]
755 #![allow(clippy::useless_vec)]
756 #![allow(clippy::needless_pass_by_value)]
757 use super::*;
759 use crate::{
760 Pos,
761 parse2::{ErrorProblem, ParseError, ParseInput, VerifyFailed, parse_netdoc},
762 types,
763 };
764 use humantime::parse_rfc3339;
765 use std::result::Result;
766 use std::{
767 net::{Ipv4Addr, SocketAddrV4},
768 str::FromStr,
769 };
770 use tor_basic_utils::test_rng;
771
772 const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
773
774 fn bad_data(fname: &str) -> String {
775 use std::fs;
776 use std::path::PathBuf;
777 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
778 path.push("testdata");
779 path.push("bad-certs");
780 path.push(fname);
781
782 fs::read_to_string(path).unwrap()
783 }
784
785 #[test]
786 fn parse_one() -> crate::Result<()> {
787 use tor_checkable::{SelfSigned, Timebound};
788 let cert = AuthCert::parse(TESTDATA)?
789 .check_signature()
790 .unwrap()
791 .dangerously_assume_timely();
792
793 assert_eq!(
795 cert.id_fingerprint().to_string(),
796 "$ed03bb616eb2f60bec80151114bb25cef515b226"
797 );
798 assert_eq!(
799 cert.key_ids().sk_fingerprint.to_string(),
800 "$c4f720e2c59f9ddd4867fff465ca04031e35648f"
801 );
802
803 Ok(())
804 }
805
806 #[test]
807 fn parse_bad() {
808 fn check(fname: &str, err: &crate::Error) {
809 let contents = bad_data(fname);
810 let cert = AuthCert::parse(&contents);
811 assert!(cert.is_err());
812 assert_eq!(&cert.err().unwrap(), err);
813 }
814
815 check(
816 "bad-cc-tag",
817 &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
818 );
819 check(
820 "bad-fingerprint",
821 &EK::BadArgument
822 .at_pos(Pos::from_line(2, 1))
823 .with_msg("fingerprint does not match RSA identity"),
824 );
825 check(
826 "bad-version",
827 &EK::BadDocumentVersion.with_msg("unexpected version 4"),
828 );
829 check(
830 "wrong-end",
831 &EK::WrongEndingToken
832 .with_msg("dir-key-crosscert")
833 .at_pos(Pos::from_line(37, 1)),
834 );
835 check(
836 "wrong-start",
837 &EK::WrongStartingToken
838 .with_msg("fingerprint")
839 .at_pos(Pos::from_line(1, 1)),
840 );
841 }
842
843 #[test]
844 fn test_recovery_1() {
845 let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
846 data += TESTDATA;
847
848 let res: Vec<crate::Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
849
850 assert!(res[0].is_err());
852 assert!(res[1].is_ok());
853 assert_eq!(res.len(), 2);
854 }
855
856 #[test]
857 fn test_recovery_2() {
858 let mut data = bad_data("bad-version");
859 data += TESTDATA;
860
861 let res: Vec<crate::Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
862
863 assert!(res[0].is_err());
865 assert!(res[1].is_ok());
866 assert_eq!(res.len(), 2);
867 }
868
869 const DIR_KEY_PUBLISHED: &str = "2000-01-01 00:00:05";
872 const DIR_KEY_EXPIRES: &str = "2001-01-01 00:00:05";
873 const FINGERPRINT: &str = "D190BF3B00E311A9AEB6D62B51980E9B2109BAD1";
874 const DIR_ADDRESS: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 7100);
875 const DIR_IDENTITY_KEY: &str = "
876-----BEGIN RSA PUBLIC KEY-----
877MIIBigKCAYEAt0rXD+1gYwKFAxrO4uNHQ9dQVUOGx5FxkioYNSct5Z3JU00dTKNJ
878jt4OGkFYwixWwk6KLDOiB+I/q9YIdA1NlQ5R3Hz8jjvFPVl0JQQm2LYzdSzv7/CZ
879U1qq5rYeeoYKx8qMQg4q3WgR251GEnOG+rVqzFSs0oyC+SDfYn9iMt00/pmN3HXf
880wmasY6BescVrYoDbnpkwKATizd4lzx5K8V8aXUXtd8qnYzSyHLlhiO1eufVX07YC
881+AVHV7W7qCTY/4I5Sm0dQ9jF/r04JBHnpH+aae48JOjWDCZj9AINi3rCKS8XClGb
882BB/LJidoQAZraQEEtu3Ql1mjdLreeyWfXpfZFvwKuYn44FtQsOT2TVAVNqNF8N4v
883yfwfiPN6FQWlPyMCEB81HerCn03Zi5WgQLGo7PAeO4LFrLrU16DUC5/oJENeHs0T
88427FZQyrlf0rAxiHh7TJKcjLmzeyxCQVQlr2AXXs28gKHV0AQnEcdrVOpTrquSCQQ
885hWBehR+ct4OJAgMBAAE=
886-----END RSA PUBLIC KEY-----
887 ";
888 const DIR_SIGNING_KEY: &str = "
889-----BEGIN RSA PUBLIC KEY-----
890MIIBCgKCAQEAtPF94+bThLI28kn6e+MmUECMMJ5UBlnQ+Mvwn8Zd85awPQTDz5Wu
89113sZDN3nWnhgSuP5q/WDYc5GPPtQdSWBiG1nJA2XLgEHTHf29iGZ+jAoGfIMJvBV
8921xN8baTnsha5LGx5BQ4UqzlUmoaPzwbjehnPd00FgVkpcCvKZu1HU7fGMVwn4MMh
893zuxJTqTgfcuFWTEu0H0ukOFX+51ih6WO3GWYqRiqgU0Q5/Ets8ccCTq7ND9d2u1P
894d7kQzUHbVP0KmYGK4qYntGDfP4g9SmpBoUUHyP3j9en9S6PMYv8m1YFO7M7JKu6Q
895dQZfGTxj9C/0b/jRklgn5JlKAl9eJQvCdwIDAQAB
896-----END RSA PUBLIC KEY-----
897";
898 const DIR_CROSS_CERT_OBJECT: &str = "
899-----BEGIN ID SIGNATURE-----
900NBaPdBNCNMah6cklrALzj0RdHymF/jPGOv9NmeqaXc0uTN06S/BlVM/xTjilu+dj
901sjPuT0BQL4/ZWyZR+R+gJJojKYILSId4IQ1elzRSxpFN+u2u/ZEmS6SR2SwpA05A
902btOYBKAmYkY6rLsTCbXGx3lAH2kAXfcrltCNKZXV6gqW7X379fiOnSId1OWhKPe1
903/1p3pQGZxgb8FOT1kpHxOMRBClF9Ulm3d9fQZr80Wn73gZ2Bp1RXn9c7c/71HD1c
904mzMT023bleZ574az+117yNAr6XbIgqQfzbySzVLPXM8ZN9BrGR40KDZ2638ZJjRu
9058HK5TzuknWlkRv3hCyRX+g==
906-----END ID SIGNATURE-----
907";
908 const AUTHCERT_RAW: &str = include_str!("../../testdata2/keys/authority_certificate");
909 const VALID_SYSTEM_TIME: &str = "2000-06-01 00:00:00";
914
915 const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
919
920 fn to_system_time(s: &str) -> SystemTime {
924 Iso8601TimeSp::from_str(s).unwrap().0
925 }
926
927 fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
931 rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
932 }
933
934 fn to_rsa_id(s: &str) -> RsaIdentity {
938 RsaIdentity::from_hex(s).unwrap()
939 }
940
941 #[test]
943 fn dir_auth_cross_cert() {
944 #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
945 #[derive_deftly(NetdocParseable)]
946 struct Dummy {
947 dir_key_crosscert: CrossCert,
948 }
949
950 let encoded = DIR_CROSS_CERT_OBJECT
954 .lines()
955 .filter(|line| !line.starts_with("-----"))
956 .collect::<Vec<_>>()
957 .join("\n");
958 let decoded = pem::parse(DIR_CROSS_CERT_OBJECT)
959 .unwrap()
960 .contents()
961 .to_vec();
962
963 let cert = format!(
965 "dir-key-crosscert\n-----BEGIN SIGNATURE-----\n{encoded}\n-----END SIGNATURE-----"
966 );
967 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
968 assert_eq!(
969 res,
970 Dummy {
971 dir_key_crosscert: CrossCert {
972 signature: CrossCertObject(decoded.clone())
973 }
974 }
975 );
976
977 let cert = format!(
979 "dir-key-crosscert\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
980 );
981 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
982 assert_eq!(
983 res,
984 Dummy {
985 dir_key_crosscert: CrossCert {
986 signature: CrossCertObject(decoded.clone())
987 }
988 }
989 );
990
991 let cert =
993 format!("dir-key-crosscert\n-----BEGIN WHAT-----\n{encoded}\n-----END WHAT-----");
994 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
995 match res {
996 Err(ParseError {
997 problem: ErrorProblem::ObjectIncorrectLabel,
998 doctype: "dir-key-crosscert",
999 file: _,
1000 lno: 1,
1001 column: None,
1002 }) => {}
1003 other => panic!("not expected error {other:#?}"),
1004 }
1005
1006 let cert = format!(
1008 "dir-key-crosscert arg1\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
1009 );
1010 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
1011 match res {
1012 Err(ParseError {
1013 problem: ErrorProblem::UnexpectedArgument { column: 19 },
1014 doctype: "dir-key-crosscert",
1015 file: _,
1016 lno: 1,
1017 column: Some(19),
1018 }) => {}
1019 other => panic!("not expected error {other:#?}"),
1020 }
1021 }
1022
1023 #[test]
1024 fn dir_auth_cert() {
1025 let res =
1026 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1027 assert_eq!(
1028 *res.inspect_unverified().0,
1029 AuthCert {
1030 dir_key_certificate_version: AuthCertVersion::V3,
1031 dir_address: Some(DIR_ADDRESS),
1032 fingerprint: types::Fingerprint(to_rsa_id(FINGERPRINT)),
1033 dir_key_published: Iso8601TimeSp(to_system_time(DIR_KEY_PUBLISHED)),
1034 dir_key_expires: Iso8601TimeSp(to_system_time(DIR_KEY_EXPIRES)),
1035 dir_identity_key: pem_to_rsa_pk(DIR_IDENTITY_KEY),
1036 dir_signing_key: pem_to_rsa_pk(DIR_SIGNING_KEY),
1037 dir_key_crosscert: CrossCert {
1038 signature: CrossCertObject(
1039 pem::parse(DIR_CROSS_CERT_OBJECT)
1040 .unwrap()
1041 .contents()
1042 .to_vec()
1043 )
1044 },
1045 __non_exhaustive: (),
1046 }
1047 );
1048 }
1049
1050 #[test]
1051 fn dir_auth_signature() {
1052 let res =
1053 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1054
1055 res.clone()
1057 .verify(
1058 &[to_rsa_id(FINGERPRINT)],
1059 Duration::ZERO,
1060 Duration::ZERO,
1061 to_system_time(VALID_SYSTEM_TIME),
1062 )
1063 .unwrap();
1064
1065 assert_eq!(
1067 res.clone()
1068 .verify(
1069 &[],
1070 Duration::ZERO,
1071 Duration::ZERO,
1072 to_system_time(VALID_SYSTEM_TIME),
1073 )
1074 .unwrap_err(),
1075 VerifyFailed::InsufficientTrustedSigners
1076 );
1077
1078 assert_eq!(
1080 res.clone()
1081 .verify(
1082 &[to_rsa_id(FINGERPRINT)],
1083 Duration::ZERO,
1084 Duration::ZERO,
1085 SystemTime::UNIX_EPOCH,
1086 )
1087 .unwrap_err(),
1088 VerifyFailed::TooNew
1089 );
1090
1091 res.clone()
1093 .verify(
1094 &[to_rsa_id(FINGERPRINT)],
1095 Duration::ZERO,
1096 Duration::ZERO,
1097 to_system_time(DIR_KEY_PUBLISHED),
1098 )
1099 .unwrap();
1100
1101 assert_eq!(
1103 res.clone()
1104 .verify(
1105 &[to_rsa_id(FINGERPRINT)],
1106 Duration::ZERO,
1107 Duration::ZERO,
1108 to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1),
1109 )
1110 .unwrap_err(),
1111 VerifyFailed::TooNew
1112 );
1113
1114 res.clone()
1116 .verify(
1117 &[to_rsa_id(FINGERPRINT)],
1118 Duration::from_secs(1),
1119 Duration::ZERO,
1120 to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1),
1121 )
1122 .unwrap();
1123
1124 assert_eq!(
1126 res.clone()
1127 .verify(
1128 &[to_rsa_id(FINGERPRINT)],
1129 Duration::ZERO,
1130 Duration::ZERO,
1131 SystemTime::UNIX_EPOCH
1132 .checked_add(Duration::from_secs(2000000000))
1133 .unwrap(),
1134 )
1135 .unwrap_err(),
1136 VerifyFailed::TooOld
1137 );
1138
1139 res.clone()
1141 .verify(
1142 &[to_rsa_id(FINGERPRINT)],
1143 Duration::ZERO,
1144 Duration::ZERO,
1145 to_system_time(DIR_KEY_EXPIRES),
1146 )
1147 .unwrap();
1148
1149 assert_eq!(
1151 res.clone()
1152 .verify(
1153 &[to_rsa_id(FINGERPRINT)],
1154 Duration::ZERO,
1155 Duration::ZERO,
1156 to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1),
1157 )
1158 .unwrap_err(),
1159 VerifyFailed::TooOld
1160 );
1161
1162 res.clone()
1164 .verify(
1165 &[to_rsa_id(FINGERPRINT)],
1166 Duration::ZERO,
1167 Duration::from_secs(1),
1168 to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1),
1169 )
1170 .unwrap();
1171
1172 let mut cert =
1174 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1175 let alternative_cert = parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(
1176 ALTERNATIVE_AUTHCERT_RAW,
1177 "",
1178 ))
1179 .unwrap();
1180 cert.body.dir_identity_key = alternative_cert.body.dir_identity_key.clone();
1181 assert_eq!(
1182 cert.verify(
1183 &[to_rsa_id(FINGERPRINT)],
1184 Duration::ZERO,
1185 Duration::ZERO,
1186 to_system_time(VALID_SYSTEM_TIME),
1187 )
1188 .unwrap_err(),
1189 VerifyFailed::Inconsistent
1190 );
1191
1192 let mut cert =
1194 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1195 cert.body.dir_key_crosscert = alternative_cert.body.dir_key_crosscert.clone();
1196 assert_eq!(
1197 cert.verify(
1198 &[to_rsa_id(FINGERPRINT)],
1199 Duration::ZERO,
1200 Duration::ZERO,
1201 to_system_time(VALID_SYSTEM_TIME),
1202 )
1203 .unwrap_err(),
1204 VerifyFailed::VerifyFailed
1205 );
1206
1207 let mut cert =
1209 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1210 cert.sigs = alternative_cert.sigs.clone();
1211 assert_eq!(
1212 cert.verify(
1213 &[to_rsa_id(FINGERPRINT)],
1214 Duration::ZERO,
1215 Duration::ZERO,
1216 to_system_time(VALID_SYSTEM_TIME),
1217 )
1218 .unwrap_err(),
1219 VerifyFailed::VerifyFailed
1220 );
1221 }
1222
1223 #[test]
1224 fn keyids_for_directory_signature() -> anyhow::Result<()> {
1225 #[derive(Deftly)]
1226 #[derive_deftly(NetdocEncodable, NetdocParseable)]
1227 struct Doc {
1228 intro: (),
1229 ids: Item,
1230 }
1231 #[derive(Deftly)]
1232 #[derive_deftly(ItemValueEncodable, ItemValueParseable)]
1233 struct Item {
1234 #[deftly(netdoc(with = keyids_directory_signature_args))]
1235 ids: AuthCertKeyIds,
1236 }
1237
1238 let text = r#"intro
1239ids 1234567812345678123456781234567812345678 ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
1240"#;
1241 let doc = parse2::parse_netdoc::<Doc>(&ParseInput::new(text, "<text>"))?;
1242 let mut re_encode = NetdocEncoder::new();
1243 doc.encode_unsigned(&mut re_encode)?;
1244 let re_encode = re_encode.finish()?;
1245
1246 assert_eq!(text, re_encode);
1247 Ok(())
1248 }
1249
1250 #[test]
1251 #[cfg(feature = "incomplete")]
1252 fn roundtrip() -> Result<(), anyhow::Error> {
1253 let mut rng = test_rng::testing_rng();
1254 let k_auth_id_rsa = rsa::KeyPair::generate(&mut rng)?;
1255 let k_auth_sign_rsa = rsa::KeyPair::generate(&mut rng)?;
1256
1257 let secs = |s| Duration::from_secs(s);
1258 let now = parse_rfc3339("1993-01-01T00:00:00Z")?;
1259 let published = now - secs(1000);
1260 let expires = published + secs(86400);
1261 let tolerance = secs(10);
1262
1263 let input_value = AuthCert {
1264 dir_address: Some("192.0.2.17:7000".parse()?),
1265 ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
1266 };
1267 dbg!(&input_value);
1268
1269 let encoded = input_value.encode_sign(&k_auth_id_rsa)?;
1270
1271 let reparsed_uv: AuthCertUnverified =
1272 parse_netdoc(&ParseInput::new(encoded.as_ref(), "<encoded>"))?;
1273 let reparsed_value = reparsed_uv.verify(
1274 &[k_auth_id_rsa.to_public_key().to_rsa_identity()],
1275 tolerance,
1276 tolerance,
1277 now,
1278 )?;
1279 dbg!(&reparsed_value);
1280
1281 assert_eq!(input_value, reparsed_value);
1282 Ok(())
1283 }
1284}