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 NetdocParseableUnverified 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::{
26 Timebound, signed,
27 timed::{self, TimerangeBound},
28};
29use tor_error::{internal, into_internal};
30use tor_llcrypto::pk::rsa;
31use tor_llcrypto::{d, pk, pk::rsa::RsaIdentity};
32
33use std::sync::LazyLock;
34
35use std::result::Result as StdResult;
36use std::{net, time, time::SystemTime};
37
38use derive_deftly::Deftly;
39use digest::Digest;
40
41#[cfg(feature = "build_docs")]
42mod build;
43
44#[cfg(feature = "build_docs")]
45#[allow(deprecated)]
46pub use build::AuthCertBuilder;
47
48#[cfg(feature = "incomplete")]
49mod encoded;
50#[cfg(feature = "incomplete")]
51pub use encoded::EncodedAuthCert;
52
53decl_keyword! {
54 pub(crate) AuthCertKwd {
55 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
56 "dir-address" => DIR_ADDRESS,
57 "fingerprint" => FINGERPRINT,
58 "dir-identity-key" => DIR_IDENTITY_KEY,
59 "dir-key-published" => DIR_KEY_PUBLISHED,
60 "dir-key-expires" => DIR_KEY_EXPIRES,
61 "dir-signing-key" => DIR_SIGNING_KEY,
62 "dir-key-crosscert" => DIR_KEY_CROSSCERT,
63 "dir-key-certification" => DIR_KEY_CERTIFICATION,
64 }
65}
66
67static AUTHCERT_RULES: LazyLock<SectionRules<AuthCertKwd>> = LazyLock::new(|| {
70 use AuthCertKwd::*;
71
72 let mut rules = SectionRules::builder();
73 rules.add(DIR_KEY_CERTIFICATE_VERSION.rule().required().args(1..));
74 rules.add(DIR_ADDRESS.rule().args(1..));
75 rules.add(FINGERPRINT.rule().required().args(1..));
76 rules.add(DIR_IDENTITY_KEY.rule().required().no_args().obj_required());
77 rules.add(DIR_SIGNING_KEY.rule().required().no_args().obj_required());
78 rules.add(DIR_KEY_PUBLISHED.rule().required());
79 rules.add(DIR_KEY_EXPIRES.rule().required());
80 rules.add(DIR_KEY_CROSSCERT.rule().required().no_args().obj_required());
81 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
82 rules.add(
83 DIR_KEY_CERTIFICATION
84 .rule()
85 .required()
86 .no_args()
87 .obj_required(),
88 );
89 rules.build()
90});
91
92#[derive(Clone, Debug, Deftly)]
100#[derive_deftly(Constructor)]
101#[derive_deftly(NetdocParseableUnverified, NetdocEncodable)]
102#[cfg_attr(test, derive(PartialEq, Eq))]
103#[allow(clippy::exhaustive_structs)]
104pub struct AuthCert {
105 #[deftly(constructor(default = AuthCertVersion::V3))]
111 #[deftly(netdoc(single_arg))]
112 pub dir_key_certificate_version: AuthCertVersion,
113
114 #[deftly(netdoc(single_arg))]
116 pub dir_address: Option<net::SocketAddrV4>,
117
118 #[deftly(constructor)]
122 #[deftly(netdoc(single_arg))]
123 pub fingerprint: Fingerprint,
124
125 #[deftly(constructor)]
129 #[deftly(netdoc(single_arg))]
130 pub dir_key_published: Iso8601TimeSp,
131
132 #[deftly(constructor)]
136 #[deftly(netdoc(single_arg))]
137 pub dir_key_expires: Iso8601TimeSp,
138
139 #[deftly(constructor)]
145 pub dir_identity_key: rsa::PublicKey,
146
147 #[deftly(constructor)]
153 pub dir_signing_key: rsa::PublicKey,
154
155 #[deftly(constructor)]
159 pub dir_key_crosscert: CrossCert,
160
161 #[doc(hidden)]
162 #[deftly(netdoc(skip))]
163 pub __non_exhaustive: (),
164}
165
166#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
172#[non_exhaustive]
173pub enum AuthCertVersion {
174 #[strum(serialize = "3")]
176 V3,
177}
178
179impl NormalItemArgument for AuthCertVersion {}
180
181#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
183#[allow(clippy::exhaustive_structs)]
184pub struct AuthCertKeyIds {
185 pub id_fingerprint: rsa::RsaIdentity,
187 pub sk_fingerprint: rsa::RsaIdentity,
189}
190
191pub struct UncheckedAuthCert {
194 location: Option<Extent>,
196
197 c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
199}
200
201impl UncheckedAuthCert {
202 pub fn within<'a>(&self, haystack: &'a str) -> Option<&'a str> {
210 self.location
211 .as_ref()
212 .and_then(|ext| ext.reconstruct(haystack))
213 }
214}
215
216impl AuthCert {
217 #[cfg(feature = "build_docs")]
220 #[deprecated = "use AuthCertConstructor instead"]
221 #[allow(deprecated)]
222 pub fn builder() -> AuthCertBuilder {
223 AuthCertBuilder::new()
224 }
225
226 pub fn parse(s: &str) -> Result<UncheckedAuthCert> {
231 let mut reader = NetDocReader::new(s)?;
232 let body = AUTHCERT_RULES.parse(&mut reader)?;
233 reader.should_be_exhausted()?;
234 AuthCert::from_body(&body, s).map_err(|e| e.within(s))
235 }
236
237 pub fn parse_multiple(s: &str) -> Result<impl Iterator<Item = Result<UncheckedAuthCert>> + '_> {
239 use AuthCertKwd::*;
240 let sections = NetDocReader::new(s)?
241 .batching_split_before_loose(|item| item.is_ok_with_kwd(DIR_KEY_CERTIFICATE_VERSION));
242 Ok(sections
243 .map(|mut section| {
244 let body = AUTHCERT_RULES.parse(&mut section)?;
245 AuthCert::from_body(&body, s)
246 })
247 .map(|r| r.map_err(|e| e.within(s))))
248 }
249 pub fn signing_key(&self) -> &rsa::PublicKey {
258 &self.dir_signing_key
259 }
260
261 pub fn key_ids(&self) -> AuthCertKeyIds {
264 AuthCertKeyIds {
265 id_fingerprint: self.fingerprint.0,
266 sk_fingerprint: self.dir_signing_key.to_rsa_identity(),
267 }
268 }
269
270 pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
272 &self.fingerprint
273 }
274
275 pub fn published(&self) -> time::SystemTime {
277 *self.dir_key_published
278 }
279
280 pub fn expires(&self) -> time::SystemTime {
282 *self.dir_key_expires
283 }
284
285 fn from_body(body: &Section<'_, AuthCertKwd>, s: &str) -> Result<UncheckedAuthCert> {
287 use AuthCertKwd::*;
288
289 let start_pos = {
294 #[allow(clippy::unwrap_used)]
297 let first_item = body.first_item().unwrap();
298 if first_item.kwd() != DIR_KEY_CERTIFICATE_VERSION {
299 return Err(EK::WrongStartingToken
300 .with_msg(first_item.kwd_str().to_string())
301 .at_pos(first_item.pos()));
302 }
303 first_item.pos()
304 };
305 let end_pos = {
306 #[allow(clippy::unwrap_used)]
309 let last_item = body.last_item().unwrap();
310 if last_item.kwd() != DIR_KEY_CERTIFICATION {
311 return Err(EK::WrongEndingToken
312 .with_msg(last_item.kwd_str().to_string())
313 .at_pos(last_item.pos()));
314 }
315 last_item.end_pos()
316 };
317
318 let version = body
319 .required(DIR_KEY_CERTIFICATE_VERSION)?
320 .parse_arg::<u32>(0)?;
321 if version != 3 {
322 return Err(EK::BadDocumentVersion.with_msg(format!("unexpected version {}", version)));
323 }
324 let dir_key_certificate_version = AuthCertVersion::V3;
325
326 let dir_signing_key: rsa::PublicKey = body
327 .required(DIR_SIGNING_KEY)?
328 .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
329 .check_len(1024..)?
330 .check_exponent(65537)?
331 .into();
332
333 let dir_identity_key: rsa::PublicKey = body
334 .required(DIR_IDENTITY_KEY)?
335 .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
336 .check_len(1024..)?
337 .check_exponent(65537)?
338 .into();
339
340 let dir_key_published = body
341 .required(DIR_KEY_PUBLISHED)?
342 .args_as_str()
343 .parse::<Iso8601TimeSp>()?;
344
345 let dir_key_expires = body
346 .required(DIR_KEY_EXPIRES)?
347 .args_as_str()
348 .parse::<Iso8601TimeSp>()?;
349
350 {
351 let fp_tok = body.required(FINGERPRINT)?;
353 let fingerprint: RsaIdentity = fp_tok.args_as_str().parse::<Fingerprint>()?.into();
354 if fingerprint != dir_identity_key.to_rsa_identity() {
355 return Err(EK::BadArgument
356 .at_pos(fp_tok.pos())
357 .with_msg("fingerprint does not match RSA identity"));
358 }
359 }
360
361 let dir_address = body
362 .maybe(DIR_ADDRESS)
363 .parse_args_as_str::<net::SocketAddrV4>()?;
364
365 let dir_key_crosscert;
367 let v_crosscert = {
368 let crosscert = body.required(DIR_KEY_CROSSCERT)?;
369 #[allow(clippy::unwrap_used)]
372 let mut tag = crosscert.obj_tag().unwrap();
373 if tag != "ID SIGNATURE" && tag != "SIGNATURE" {
375 tag = "ID SIGNATURE";
376 }
377 let sig = crosscert.obj(tag)?;
378
379 let signed = dir_identity_key.to_rsa_identity();
380 let v = rsa::ValidatableRsaSignature::new(&dir_signing_key, &sig, signed.as_bytes());
383
384 dir_key_crosscert = CrossCert {
385 signature: CrossCertObject(sig),
386 };
387
388 v
389 };
390
391 let v_sig = {
393 let signature = body.required(DIR_KEY_CERTIFICATION)?;
394 let sig = signature.obj("SIGNATURE")?;
395
396 let mut sha1 = d::Sha1::new();
397 #[allow(clippy::unwrap_used)]
400 let start_offset = body.first_item().unwrap().offset_in(s).unwrap();
401 #[allow(clippy::unwrap_used)]
402 let end_offset = body.last_item().unwrap().offset_in(s).unwrap();
403 let end_offset = end_offset + "dir-key-certification\n".len();
404 sha1.update(
405 s.get(start_offset..end_offset)
406 .ok_or(internal!("chopped utf8"))?,
407 );
408 let sha1 = sha1.finalize();
409 rsa::ValidatableRsaSignature::new(&dir_identity_key, &sig, &sha1)
412 };
413
414 let id_fingerprint = dir_identity_key.to_rsa_identity();
415
416 let location = {
417 let start_idx = start_pos.offset_within(s);
418 let end_idx = end_pos.offset_within(s);
419 match (start_idx, end_idx) {
420 (Some(a), Some(b)) => {
421 Extent::new(s, s.get(a..b + 1).ok_or(internal!("chopped utf8"))?)
422 }
423 _ => None,
424 }
425 };
426
427 let authcert = AuthCert {
428 dir_key_certificate_version,
429 dir_address,
430 dir_identity_key,
431 dir_signing_key,
432 dir_key_published,
433 dir_key_expires,
434 dir_key_crosscert,
435 fingerprint: Fingerprint(id_fingerprint),
436 __non_exhaustive: (),
437 };
438
439 let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
440 vec![Box::new(v_crosscert), Box::new(v_sig)];
441
442 let timed = timed::TimerangeBound::new(authcert, *dir_key_published..*dir_key_expires);
443 let signed = signed::SignatureGated::new(timed, signatures);
444 let unchecked = UncheckedAuthCert {
445 location,
446 c: signed,
447 };
448 Ok(unchecked)
449 }
450}
451
452pub(crate) mod keyids_directory_signature_args {
464 use super::*;
465 use std::result::Result;
466
467 pub(crate) fn from_args<'s>(
469 args: &mut ArgumentStream<'s>,
470 ) -> Result<AuthCertKeyIds, ArgumentError> {
471 let mut fp = || Ok::<_, ArgumentError>(Fingerprint::from_args(args)?.0);
472 Ok(AuthCertKeyIds {
473 id_fingerprint: fp()?,
474 sk_fingerprint: fp()?,
475 })
476 }
477
478 pub(crate) fn write_arg_onto(
480 self_: &AuthCertKeyIds,
481 out: &mut ItemEncoder<'_>,
482 ) -> Result<(), Bug> {
483 let mut fp = |id| Fingerprint(id).write_arg_onto(out);
484 fp(self_.id_fingerprint)?;
485 fp(self_.sk_fingerprint)?;
486 Ok(())
487 }
488}
489
490#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
504#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
505#[deftly(netdoc(no_extra_args))]
506#[non_exhaustive]
507pub struct CrossCert {
508 #[deftly(netdoc(object))]
510 pub signature: CrossCertObject,
511}
512
513#[derive(Clone, PartialEq, Eq, derive_more::Deref)]
532#[non_exhaustive]
533pub struct CrossCertObject(pub Vec<u8>);
534impl_debug_hex! { CrossCertObject . 0 }
535
536impl CrossCert {
537 pub fn new(
539 k_auth_sign_rsa: &rsa::KeyPair,
540 h_kp_auth_id_rsa: &RsaIdentity,
541 ) -> StdResult<Self, Bug> {
542 let signature = k_auth_sign_rsa
543 .sign(h_kp_auth_id_rsa.as_bytes())
544 .map_err(into_internal!("failed to sign cross-cert"))?;
545 Ok(CrossCert {
546 signature: CrossCertObject(signature),
547 })
548 }
549}
550
551#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
561#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
562#[deftly(netdoc(signatures(hashes_accu = Sha1WholeKeywordLine)))]
563#[non_exhaustive]
564pub struct AuthCertSignatures {
565 pub dir_key_certification: RsaSha1Signature,
567}
568
569#[deprecated = "use RsaSha1Signature"]
575pub type AuthCertSignature = RsaSha1Signature;
576
577impl ItemObjectParseable for CrossCertObject {
578 fn check_label(label: &str) -> StdResult<(), parse2::EP> {
579 match label {
580 "SIGNATURE" | "ID SIGNATURE" => Ok(()),
581 _ => Err(parse2::EP::ObjectIncorrectLabel),
582 }
583 }
584
585 fn from_bytes(input: &[u8]) -> StdResult<Self, parse2::EP> {
586 Ok(Self(input.to_vec()))
587 }
588}
589
590impl ItemObjectEncodable for CrossCertObject {
591 fn label(&self) -> &str {
592 "ID SIGNATURE"
593 }
594
595 fn write_object_onto(&self, b: &mut Vec<u8>) -> StdResult<(), Bug> {
596 b.extend(&self.0);
597 Ok(())
598 }
599}
600
601impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
602 type Error = signature::Error;
603
604 fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
605 self.c.dangerously_assume_wellsigned()
606 }
607 fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
608 self.c.is_well_signed()
609 }
610}
611
612impl AuthCertUnverified {
613 pub fn verify(
631 self,
632 v3idents: &[RsaIdentity],
633 ) -> StdResult<TimerangeBound<AuthCert>, parse2::VerifyFailed> {
634 let (body, sigs) = (self.body, self.sigs);
635
636 if !v3idents.contains(&body.fingerprint.0) {
638 return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
639 }
640
641 let validity = *body.dir_key_published..=*body.dir_key_expires;
643
644 if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
646 return Err(parse2::VerifyFailed::Inconsistent);
647 }
648
649 body.dir_signing_key.verify(
651 body.fingerprint.0.as_bytes(),
652 &body.dir_key_crosscert.signature,
653 )?;
654
655 body.dir_identity_key.verify(
657 &sigs.hashes.0.ok_or(parse2::VerifyFailed::Bug)?,
658 &sigs.sigs.dir_key_certification.signature,
659 )?;
660
661 Ok(TimerangeBound::new(body, validity))
662 }
663
664 pub fn verify_selfcert(self, now: SystemTime) -> StdResult<AuthCert, parse2::VerifyFailed> {
672 let h_kp_auth_id_rsa = self.inspect_unverified().0.fingerprint.0;
673 Ok(self.verify(&[h_kp_auth_id_rsa])?.check_valid_at(&now)?)
674 }
675}
676
677impl AuthCert {
678 pub fn new_base(
698 k_auth_id_rsa: &rsa::KeyPair,
699 k_auth_sign_rsa: &rsa::KeyPair,
700 published: SystemTime,
701 expires: SystemTime,
702 ) -> StdResult<Self, Bug> {
703 let fingerprint = k_auth_id_rsa.to_public_key().to_rsa_identity();
704 let dir_key_crosscert = CrossCert::new(k_auth_sign_rsa, &fingerprint)?;
705
706 let base = AuthCertConstructor {
707 fingerprint: fingerprint.into(),
708 dir_key_published: published.into(),
709 dir_key_expires: expires.into(),
710 dir_identity_key: k_auth_id_rsa.to_public_key(),
711 dir_signing_key: k_auth_sign_rsa.to_public_key(),
712 dir_key_crosscert,
713 }
714 .construct();
715
716 Ok(base)
717 }
718
719 #[cfg(feature = "incomplete")] pub fn encode_sign(&self, k_auth_id_rsa: &rsa::KeyPair) -> StdResult<EncodedAuthCert, Bug> {
727 let mut encoder = NetdocEncoder::new();
728 self.encode_unsigned(&mut encoder)?;
729
730 let signature =
731 RsaSha1Signature::new_sign_netdoc(k_auth_id_rsa, &encoder, "dir-key-certification")?;
732 let sigs = AuthCertSignatures {
733 dir_key_certification: signature,
734 };
735 sigs.encode_unsigned(&mut encoder)?;
736
737 let encoded = encoder.finish()?;
738 let encoded = encoded
741 .try_into()
742 .map_err(into_internal!("generated broken authcert"))?;
743 Ok(encoded)
744 }
745}
746
747#[cfg(test)]
748mod test {
749 #![allow(clippy::bool_assert_comparison)]
751 #![allow(clippy::clone_on_copy)]
752 #![allow(clippy::dbg_macro)]
753 #![allow(clippy::mixed_attributes_style)]
754 #![allow(clippy::print_stderr)]
755 #![allow(clippy::print_stdout)]
756 #![allow(clippy::single_char_pattern)]
757 #![allow(clippy::unwrap_used)]
758 #![allow(clippy::unchecked_time_subtraction)]
759 #![allow(clippy::useless_vec)]
760 #![allow(clippy::needless_pass_by_value)]
761 #![allow(clippy::string_slice)] use super::*;
764 use crate::{
765 Pos,
766 parse2::{ErrorProblem, ParseError, ParseInput, VerifyFailed, parse_netdoc},
767 types,
768 };
769 use humantime::parse_rfc3339;
770 use std::result::Result;
771 use std::{
772 fs,
773 net::{Ipv4Addr, SocketAddrV4},
774 str::FromStr,
775 time::Duration,
776 };
777 use tor_basic_utils::test_rng;
778
779 const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
780
781 fn bad_data(fname: &str) -> String {
782 use std::fs;
783 use std::path::PathBuf;
784 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
785 path.push("testdata");
786 path.push("bad-certs");
787 path.push(fname);
788
789 fs::read_to_string(path).unwrap()
790 }
791
792 #[test]
793 fn parse_one() -> crate::Result<()> {
794 use tor_checkable::{SelfSigned, Timebound};
795 let cert = AuthCert::parse(TESTDATA)?
796 .check_signature()
797 .unwrap()
798 .dangerously_assume_timely();
799
800 assert_eq!(
802 cert.id_fingerprint().to_string(),
803 "$ed03bb616eb2f60bec80151114bb25cef515b226"
804 );
805 assert_eq!(
806 cert.key_ids().sk_fingerprint.to_string(),
807 "$c4f720e2c59f9ddd4867fff465ca04031e35648f"
808 );
809
810 Ok(())
811 }
812
813 #[test]
814 fn parse_bad() {
815 fn check(fname: &str, err: &crate::Error) {
816 let contents = bad_data(fname);
817 let cert = AuthCert::parse(&contents);
818 assert!(cert.is_err());
819 assert_eq!(&cert.err().unwrap(), err);
820 }
821
822 check(
823 "bad-cc-tag",
824 &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
825 );
826 check(
827 "bad-fingerprint",
828 &EK::BadArgument
829 .at_pos(Pos::from_line(2, 1))
830 .with_msg("fingerprint does not match RSA identity"),
831 );
832 check(
833 "bad-version",
834 &EK::BadDocumentVersion.with_msg("unexpected version 4"),
835 );
836 check(
837 "wrong-end",
838 &EK::WrongEndingToken
839 .with_msg("dir-key-crosscert")
840 .at_pos(Pos::from_line(37, 1)),
841 );
842 check(
843 "wrong-start",
844 &EK::WrongStartingToken
845 .with_msg("fingerprint")
846 .at_pos(Pos::from_line(1, 1)),
847 );
848 }
849
850 #[test]
851 fn test_recovery_1() {
852 let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
853 data += TESTDATA;
854
855 let res: Vec<crate::Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
856
857 assert!(res[0].is_err());
859 assert!(res[1].is_ok());
860 assert_eq!(res.len(), 2);
861 }
862
863 #[test]
864 fn test_recovery_2() {
865 let mut data = bad_data("bad-version");
866 data += TESTDATA;
867
868 let res: Vec<crate::Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
869
870 assert!(res[0].is_err());
872 assert!(res[1].is_ok());
873 assert_eq!(res.len(), 2);
874 }
875
876 const DIR_KEY_PUBLISHED: &str = "2000-01-01 00:00:05";
879 const DIR_KEY_EXPIRES: &str = "2001-01-01 00:00:05";
880 const FINGERPRINT: &str = "D190BF3B00E311A9AEB6D62B51980E9B2109BAD1";
881 const DIR_ADDRESS: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 7100);
882 const DIR_IDENTITY_KEY: &str = "
883-----BEGIN RSA PUBLIC KEY-----
884MIIBigKCAYEAt0rXD+1gYwKFAxrO4uNHQ9dQVUOGx5FxkioYNSct5Z3JU00dTKNJ
885jt4OGkFYwixWwk6KLDOiB+I/q9YIdA1NlQ5R3Hz8jjvFPVl0JQQm2LYzdSzv7/CZ
886U1qq5rYeeoYKx8qMQg4q3WgR251GEnOG+rVqzFSs0oyC+SDfYn9iMt00/pmN3HXf
887wmasY6BescVrYoDbnpkwKATizd4lzx5K8V8aXUXtd8qnYzSyHLlhiO1eufVX07YC
888+AVHV7W7qCTY/4I5Sm0dQ9jF/r04JBHnpH+aae48JOjWDCZj9AINi3rCKS8XClGb
889BB/LJidoQAZraQEEtu3Ql1mjdLreeyWfXpfZFvwKuYn44FtQsOT2TVAVNqNF8N4v
890yfwfiPN6FQWlPyMCEB81HerCn03Zi5WgQLGo7PAeO4LFrLrU16DUC5/oJENeHs0T
89127FZQyrlf0rAxiHh7TJKcjLmzeyxCQVQlr2AXXs28gKHV0AQnEcdrVOpTrquSCQQ
892hWBehR+ct4OJAgMBAAE=
893-----END RSA PUBLIC KEY-----
894 ";
895 const DIR_SIGNING_KEY: &str = "
896-----BEGIN RSA PUBLIC KEY-----
897MIIBCgKCAQEAtPF94+bThLI28kn6e+MmUECMMJ5UBlnQ+Mvwn8Zd85awPQTDz5Wu
89813sZDN3nWnhgSuP5q/WDYc5GPPtQdSWBiG1nJA2XLgEHTHf29iGZ+jAoGfIMJvBV
8991xN8baTnsha5LGx5BQ4UqzlUmoaPzwbjehnPd00FgVkpcCvKZu1HU7fGMVwn4MMh
900zuxJTqTgfcuFWTEu0H0ukOFX+51ih6WO3GWYqRiqgU0Q5/Ets8ccCTq7ND9d2u1P
901d7kQzUHbVP0KmYGK4qYntGDfP4g9SmpBoUUHyP3j9en9S6PMYv8m1YFO7M7JKu6Q
902dQZfGTxj9C/0b/jRklgn5JlKAl9eJQvCdwIDAQAB
903-----END RSA PUBLIC KEY-----
904";
905 const DIR_CROSS_CERT_OBJECT: &str = "
906-----BEGIN ID SIGNATURE-----
907NBaPdBNCNMah6cklrALzj0RdHymF/jPGOv9NmeqaXc0uTN06S/BlVM/xTjilu+dj
908sjPuT0BQL4/ZWyZR+R+gJJojKYILSId4IQ1elzRSxpFN+u2u/ZEmS6SR2SwpA05A
909btOYBKAmYkY6rLsTCbXGx3lAH2kAXfcrltCNKZXV6gqW7X379fiOnSId1OWhKPe1
910/1p3pQGZxgb8FOT1kpHxOMRBClF9Ulm3d9fQZr80Wn73gZ2Bp1RXn9c7c/71HD1c
911mzMT023bleZ574az+117yNAr6XbIgqQfzbySzVLPXM8ZN9BrGR40KDZ2638ZJjRu
9128HK5TzuknWlkRv3hCyRX+g==
913-----END ID SIGNATURE-----
914";
915 const AUTHCERT_RAW: &str = include_str!("../../testdata2/keys/authority_certificate");
916 const VALID_SYSTEM_TIME: &str = "2000-06-01 00:00:00";
921
922 const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
926
927 fn to_system_time(s: &str) -> SystemTime {
931 Iso8601TimeSp::from_str(s).unwrap().0
932 }
933
934 fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
938 rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
939 }
940
941 fn to_rsa_id(s: &str) -> RsaIdentity {
945 RsaIdentity::from_hex(s).unwrap()
946 }
947
948 #[test]
950 fn dir_auth_cross_cert() {
951 #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
952 #[derive_deftly(NetdocParseable)]
953 struct Dummy {
954 dir_key_crosscert: CrossCert,
955 }
956
957 let encoded = DIR_CROSS_CERT_OBJECT
961 .lines()
962 .filter(|line| !line.starts_with("-----"))
963 .collect::<Vec<_>>()
964 .join("\n");
965 let decoded = pem::parse(DIR_CROSS_CERT_OBJECT)
966 .unwrap()
967 .contents()
968 .to_vec();
969
970 let cert = format!(
972 "dir-key-crosscert\n-----BEGIN SIGNATURE-----\n{encoded}\n-----END SIGNATURE-----"
973 );
974 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
975 assert_eq!(
976 res,
977 Dummy {
978 dir_key_crosscert: CrossCert {
979 signature: CrossCertObject(decoded.clone())
980 }
981 }
982 );
983
984 let cert = format!(
986 "dir-key-crosscert\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
987 );
988 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
989 assert_eq!(
990 res,
991 Dummy {
992 dir_key_crosscert: CrossCert {
993 signature: CrossCertObject(decoded.clone())
994 }
995 }
996 );
997
998 let cert =
1000 format!("dir-key-crosscert\n-----BEGIN WHAT-----\n{encoded}\n-----END WHAT-----");
1001 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
1002 match res {
1003 Err(ParseError {
1004 problem: ErrorProblem::ObjectIncorrectLabel,
1005 doctype: "dir-key-crosscert",
1006 file: _,
1007 lno: 1,
1008 column: None,
1009 }) => {}
1010 other => panic!("not expected error {other:#?}"),
1011 }
1012
1013 let cert = format!(
1015 "dir-key-crosscert arg1\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
1016 );
1017 let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
1018 match res {
1019 Err(ParseError {
1020 problem: ErrorProblem::UnexpectedArgument { column: 19 },
1021 doctype: "dir-key-crosscert",
1022 file: _,
1023 lno: 1,
1024 column: Some(19),
1025 }) => {}
1026 other => panic!("not expected error {other:#?}"),
1027 }
1028 }
1029
1030 #[test]
1031 fn dir_auth_cert() {
1032 let res =
1033 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1034 assert_eq!(
1035 *res.inspect_unverified().0,
1036 AuthCert {
1037 dir_key_certificate_version: AuthCertVersion::V3,
1038 dir_address: Some(DIR_ADDRESS),
1039 fingerprint: types::Fingerprint(to_rsa_id(FINGERPRINT)),
1040 dir_key_published: Iso8601TimeSp(to_system_time(DIR_KEY_PUBLISHED)),
1041 dir_key_expires: Iso8601TimeSp(to_system_time(DIR_KEY_EXPIRES)),
1042 dir_identity_key: pem_to_rsa_pk(DIR_IDENTITY_KEY),
1043 dir_signing_key: pem_to_rsa_pk(DIR_SIGNING_KEY),
1044 dir_key_crosscert: CrossCert {
1045 signature: CrossCertObject(
1046 pem::parse(DIR_CROSS_CERT_OBJECT)
1047 .unwrap()
1048 .contents()
1049 .to_vec()
1050 )
1051 },
1052 __non_exhaustive: (),
1053 }
1054 );
1055 }
1056
1057 #[test]
1058 fn dir_auth_signature() {
1059 let res =
1060 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1061
1062 let _: AuthCert = res
1064 .clone()
1065 .verify(&[to_rsa_id(FINGERPRINT)])
1066 .unwrap()
1067 .check_valid_at(&to_system_time(VALID_SYSTEM_TIME))
1068 .unwrap();
1069
1070 assert_eq!(
1072 res.clone().verify(&[],).unwrap_err(),
1073 VerifyFailed::InsufficientTrustedSigners
1074 );
1075
1076 assert_eq!(
1078 res.clone()
1079 .verify(&[to_rsa_id(FINGERPRINT)],)
1080 .unwrap()
1081 .check_valid_at(&SystemTime::UNIX_EPOCH,)
1082 .map_err(VerifyFailed::from)
1083 .unwrap_err(),
1084 VerifyFailed::TooNew
1085 );
1086
1087 let _: AuthCert = res
1089 .clone()
1090 .verify(&[to_rsa_id(FINGERPRINT)])
1091 .unwrap()
1092 .check_valid_at(&to_system_time(DIR_KEY_PUBLISHED))
1093 .unwrap();
1094
1095 assert_eq!(
1097 res.clone()
1098 .verify(&[to_rsa_id(FINGERPRINT)],)
1099 .unwrap()
1100 .check_valid_at(&(to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1)),)
1101 .map_err(VerifyFailed::from)
1102 .unwrap_err(),
1103 VerifyFailed::TooNew
1104 );
1105
1106 let _: AuthCert = res
1108 .clone()
1109 .verify(&[to_rsa_id(FINGERPRINT)])
1110 .unwrap()
1111 .extend_pre_tolerance(Duration::from_secs(1))
1112 .check_valid_at(&(to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1)))
1113 .unwrap();
1114
1115 assert_eq!(
1117 res.clone()
1118 .verify(&[to_rsa_id(FINGERPRINT)],)
1119 .unwrap()
1120 .check_valid_at(
1121 &SystemTime::UNIX_EPOCH
1122 .checked_add(Duration::from_secs(2000000000))
1123 .unwrap(),
1124 )
1125 .map_err(VerifyFailed::from)
1126 .unwrap_err(),
1127 VerifyFailed::TooOld
1128 );
1129
1130 let _: AuthCert = res
1132 .clone()
1133 .verify(&[to_rsa_id(FINGERPRINT)])
1134 .unwrap()
1135 .check_valid_at(&to_system_time(DIR_KEY_EXPIRES))
1136 .unwrap();
1137
1138 assert_eq!(
1140 res.clone()
1141 .verify(&[to_rsa_id(FINGERPRINT)],)
1142 .unwrap()
1143 .check_valid_at(&(to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1)),)
1144 .map_err(VerifyFailed::from)
1145 .unwrap_err(),
1146 VerifyFailed::TooOld
1147 );
1148
1149 let _: AuthCert = res
1151 .clone()
1152 .verify(&[to_rsa_id(FINGERPRINT)])
1153 .unwrap()
1154 .extend_tolerance(Duration::from_secs(1))
1155 .check_valid_at(&(to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1)))
1156 .unwrap();
1157
1158 let mut cert =
1160 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1161 let alternative_cert = parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(
1162 ALTERNATIVE_AUTHCERT_RAW,
1163 "",
1164 ))
1165 .unwrap();
1166 cert.body.dir_identity_key = alternative_cert.body.dir_identity_key.clone();
1167 assert_eq!(
1168 cert.verify(&[to_rsa_id(FINGERPRINT)],).unwrap_err(),
1169 VerifyFailed::Inconsistent
1170 );
1171
1172 let mut cert =
1174 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1175 cert.body.dir_key_crosscert = alternative_cert.body.dir_key_crosscert.clone();
1176 assert_eq!(
1177 cert.verify(&[to_rsa_id(FINGERPRINT)],).unwrap_err(),
1178 VerifyFailed::VerifyFailed
1179 );
1180
1181 let mut cert =
1183 parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1184 cert.sigs = alternative_cert.sigs.clone();
1185 assert_eq!(
1186 cert.verify(&[to_rsa_id(FINGERPRINT)],).unwrap_err(),
1187 VerifyFailed::VerifyFailed
1188 );
1189 }
1190
1191 #[test]
1192 fn keyids_for_directory_signature() -> anyhow::Result<()> {
1193 #[derive(Deftly)]
1194 #[derive_deftly(NetdocEncodable, NetdocParseable)]
1195 struct Doc {
1196 intro: (),
1197 ids: Item,
1198 }
1199 #[derive(Deftly)]
1200 #[derive_deftly(ItemValueEncodable, ItemValueParseable)]
1201 struct Item {
1202 #[deftly(netdoc(with = keyids_directory_signature_args))]
1203 ids: AuthCertKeyIds,
1204 }
1205
1206 let text = r#"intro
1207ids 1234567812345678123456781234567812345678 ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
1208"#;
1209 let doc = parse2::parse_netdoc::<Doc>(&ParseInput::new(text, "<text>"))?;
1210 let mut re_encode = NetdocEncoder::new();
1211 doc.encode_unsigned(&mut re_encode)?;
1212 let re_encode = re_encode.finish()?;
1213
1214 assert_eq_or_diff!(text, re_encode);
1215 Ok(())
1216 }
1217
1218 #[test]
1219 #[cfg(feature = "incomplete")]
1220 fn roundtrip() -> Result<(), anyhow::Error> {
1221 let mut rng = test_rng::testing_rng();
1222 let k_auth_id_rsa = rsa::KeyPair::generate(&mut rng)?;
1223 let k_auth_sign_rsa = rsa::KeyPair::generate(&mut rng)?;
1224
1225 let secs = |s| Duration::from_secs(s);
1226 let now = parse_rfc3339("1993-01-01T00:00:00Z")?;
1227 let published = now - secs(1000);
1228 let expires = published + secs(86400);
1229 let tolerance = secs(10);
1230
1231 let input_value = AuthCert {
1232 dir_address: Some("192.0.2.17:7000".parse()?),
1233 ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
1234 };
1235 dbg!(&input_value);
1236
1237 let encoded = input_value.encode_sign(&k_auth_id_rsa)?;
1238
1239 let reparsed_uv: AuthCertUnverified =
1240 parse_netdoc(&ParseInput::new(encoded.as_ref(), "<encoded>"))?;
1241 let reparsed_value = reparsed_uv
1242 .verify(&[k_auth_id_rsa.to_public_key().to_rsa_identity()])?
1243 .extend_pre_tolerance(tolerance)
1244 .extend_tolerance(tolerance)
1245 .check_valid_at(&now)?;
1246 dbg!(&reparsed_value);
1247
1248 assert_eq!(input_value, reparsed_value);
1249 Ok(())
1250 }
1251
1252 #[cfg(feature = "incomplete")]
1253 #[test]
1254 fn parse_authcert() -> anyhow::Result<()> {
1255 let file = "testdata2/cached-certs--1";
1256 let now = parse_rfc3339("2000-06-01T00:00:05Z")?;
1257 let text = fs::read_to_string(file)?;
1258 let input = ParseInput::new(&text, file);
1259 let doc: AuthCertUnverified = parse_netdoc(&input)?;
1260 let doc = doc.verify_selfcert(now)?;
1261 println!("{doc:?}");
1262 assert_eq!(
1263 doc.fingerprint.0.to_string(),
1264 "$0b8997614ec647c1c6b6a044e2b5408f0b823fb0",
1265 );
1266 Ok(())
1267 }
1268}