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