Skip to main content

tor_netdoc/doc/
authcert.rs

1//! Parsing implementation for Tor authority certificates
2//!
3//! An "authority certificate" is a short signed document that binds a
4//! directory authority's permanent "identity key" to its medium-term
5//! "signing key".  Using separate keys here enables the authorities
6//! to keep their identity keys securely offline, while using the
7//! signing keys to sign votes and consensuses.
8
9use 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
61/// Rules about entries that must appear in an AuthCert, and how they must
62/// be formed.
63static 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/// A single directory authority key certificate
87///
88/// This is the body, not including signatures.
89///
90/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html>
91///
92/// To make a fresh `AuthCert`, use [`AuthCertConstructor`].
93#[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    /// Intro line
100    ///
101    /// Currently must be version 3.
102    ///
103    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
104    #[deftly(constructor(default = AuthCertVersion::V3))]
105    #[deftly(netdoc(single_arg))]
106    pub dir_key_certificate_version: AuthCertVersion,
107
108    /// An IPv4 address for this authority.
109    #[deftly(netdoc(single_arg))]
110    pub dir_address: Option<net::SocketAddrV4>,
111
112    /// H(KP_auth_id_rsa)
113    ///
114    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:fingerprint>
115    #[deftly(constructor)]
116    #[deftly(netdoc(single_arg))]
117    pub fingerprint: Fingerprint,
118
119    /// Declared time when this certificate was published
120    ///
121    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-published>
122    #[deftly(constructor)]
123    #[deftly(netdoc(single_arg))]
124    pub dir_key_published: Iso8601TimeSp,
125
126    /// Declared time when this certificate expires.
127    ///
128    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-expires>
129    #[deftly(constructor)]
130    #[deftly(netdoc(single_arg))]
131    pub dir_key_expires: Iso8601TimeSp,
132
133    /// KP_auth_id_rsa
134    ///
135    /// The long-term RSA identity key for this authority
136    ///
137    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-identity-key>
138    #[deftly(constructor)]
139    pub dir_identity_key: rsa::PublicKey,
140
141    /// KP_auth_sign_rsa
142    ///
143    /// The medium-term RSA signing key for this authority
144    ///
145    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-signing-key>
146    #[deftly(constructor)]
147    pub dir_signing_key: rsa::PublicKey,
148
149    /// SHA1(DER(KP_auth_id_rsa)) signed by KP_auth_sign_rsa
150    ///
151    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
152    #[deftly(constructor)]
153    pub dir_key_crosscert: CrossCert,
154
155    #[doc(hidden)]
156    #[deftly(netdoc(skip))]
157    pub __non_exhaustive: (),
158}
159
160/// Represents the version of an [`AuthCert`].
161///
162/// Single argument.
163///
164/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
165#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
166#[non_exhaustive]
167pub enum AuthCertVersion {
168    /// The current and only version understood.
169    #[strum(serialize = "3")]
170    V3,
171}
172
173impl NormalItemArgument for AuthCertVersion {}
174
175/// A pair of key identities that identifies a certificate.
176#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
177#[allow(clippy::exhaustive_structs)]
178pub struct AuthCertKeyIds {
179    /// Fingerprint of identity key
180    pub id_fingerprint: rsa::RsaIdentity,
181    /// Fingerprint of signing key
182    pub sk_fingerprint: rsa::RsaIdentity,
183}
184
185/// An authority certificate whose signature and validity time we
186/// haven't checked.
187pub struct UncheckedAuthCert {
188    /// Where we found this AuthCert within the string containing it.
189    location: Option<Extent>,
190
191    /// The actual unchecked certificate.
192    c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
193}
194
195impl UncheckedAuthCert {
196    /// If this AuthCert was originally parsed from `haystack`, return its
197    /// text.
198    ///
199    /// TODO: This is a pretty bogus interface; there should be a
200    /// better way to remember where to look for this thing if we want
201    /// it without keeping the input alive forever.  We should
202    /// refactor.
203    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    /// Make an [`AuthCertBuilder`] object that can be used to
212    /// construct authority certificates for testing.
213    #[cfg(feature = "build_docs")]
214    #[deprecated = "use AuthCertConstructor instead"]
215    #[allow(deprecated)]
216    pub fn builder() -> AuthCertBuilder {
217        AuthCertBuilder::new()
218    }
219
220    /// Parse an authority certificate from a string.
221    ///
222    /// This function verifies the certificate's signatures, but doesn't
223    /// check its expiration dates.
224    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    /// Return an iterator yielding authority certificates from a string.
232    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    /*
244        /// Return true if this certificate is expired at a given time, or
245        /// not yet valid at that time.
246        pub fn is_expired_at(&self, when: time::SystemTime) -> bool {
247            when < self.published || when > self.expires
248        }
249    */
250    /// Return the signing key certified by this certificate.
251    pub fn signing_key(&self) -> &rsa::PublicKey {
252        &self.dir_signing_key
253    }
254
255    /// Return an AuthCertKeyIds object describing the keys in this
256    /// certificate.
257    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    /// Return an RsaIdentity for this certificate's identity key.
265    pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
266        &self.fingerprint
267    }
268
269    /// Return the time when this certificate says it was published.
270    pub fn published(&self) -> time::SystemTime {
271        *self.dir_key_published
272    }
273
274    /// Return the time when this certificate says it should expire.
275    pub fn expires(&self) -> time::SystemTime {
276        *self.dir_key_expires
277    }
278
279    /// Parse an authority certificate from a reader.
280    fn from_body(body: &Section<'_, AuthCertKwd>, s: &str) -> Result<UncheckedAuthCert> {
281        use AuthCertKwd::*;
282
283        // Make sure first and last element are correct types.  We can
284        // safely call unwrap() on first and last, since there are required
285        // tokens in the rules, so we know that at least one token will have
286        // been parsed.
287        let start_pos = {
288            // Unwrap should be safe because `.parse()` would have already
289            // returned an Error
290            #[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            // Unwrap should be safe because `.parse()` would have already
301            // returned an Error
302            #[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            // Check fingerprint for consistency with key.
346            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        // check crosscert
360        let dir_key_crosscert;
361        let v_crosscert = {
362            let crosscert = body.required(DIR_KEY_CROSSCERT)?;
363            // Unwrap should be safe because `.parse()` and `required()` would
364            // have already returned an Error
365            #[allow(clippy::unwrap_used)]
366            let mut tag = crosscert.obj_tag().unwrap();
367            // we are required to support both.
368            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            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
375
376            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        // check the signature
386        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            // Unwrap should be safe because `.parse()` would have already
392            // returned an Error
393            #[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            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
401
402            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/// Pseudo-Signature of the long-term identity key by the medium-term key.
442///
443/// This type does not implement `SignatureItemParseable` because that trait
444/// is reserved for signatures on *netdocs*, such as [`AuthCertSignature`].
445/// As `CrossCert` does not sign a full document, it implements only
446/// `ItemValueParseable`, instead.
447///
448/// Verification of this signature is done in `AuthCertUnverified::verify`,
449/// and during parsing by the old parser.
450/// So a `CrossCert` in [`AuthCert::dir_key_crosscert`] in a bare `AuthCert` has been validated.
451//
452// TODO SPEC (Diziet): it is far from clear to me that this cert serves any useful purpose.
453// However, we are far too busy now with rewriting the universe to consider transitioning it away.
454#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
455#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
456#[deftly(netdoc(no_extra_args))]
457#[non_exhaustive]
458pub struct CrossCert {
459    /// The bytes of the signature (base64-decoded).
460    #[deftly(netdoc(object))]
461    pub signature: CrossCertObject,
462}
463
464/// Wrapper around [`Vec<u8>`] implementing [`ItemObjectParseable`] properly.
465///
466/// Unfortunately, this wrapper is necessary, because the specification
467/// demands that these certificate objects must accept two labels:
468/// `SIGNATURE` and `ID SIGNATURE`.  Because the deftly template for
469/// `ItemValueParseable` only allows for a single label
470/// (`#[deftly(netdoc(object(label = "LABEL")))]`), we must implement this
471/// trait ourselves in order to allow multiple ones.
472///
473/// TODO: In the future, it might be nice to let the respective fmeta
474/// accept a pattern, as pattern matching would allow trivially for one
475/// to infinity different combinations.
476/// TODO SPEC: Alternatively we could abolish the wrong labels,
477/// or we could abolish Objects completely and just have long lines.
478///
479/// # Specifications
480///
481/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
482#[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    /// Make a `CrossCert`
489    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/// Signatures for [`AuthCert`]
503///
504/// Signed by [`AuthCert::dir_identity_key`] in order to prove ownership.
505/// Can be seen as the opposite of [`AuthCert::dir_key_crosscert`].
506///
507/// # Specifications
508///
509/// * <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certification>
510/// * <https://spec.torproject.org/dir-spec/netdoc.html#signing>
511#[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    /// Contains the actual signature, see [`AuthCertSignatures`].
517    pub dir_key_certification: RsaSha1Signature,
518}
519
520/// RSA signature for data in [`AuthCert`]
521///
522/// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
523///
524/// Compatibility type alias for [`RsaSha1Signature`].
525#[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    /// Verifies the signature of a [`AuthCert`]
565    ///
566    /// # Algorithm
567    ///
568    /// 1. Check whether this comes from a valid authority in `v3idents`.
569    /// 2. Check whether the timestamps are valid (± tolerance).
570    /// 3. Check whether the fingerprint and long-term identity key match.
571    /// 4. Check the cross-certificate (proof-of-ownership of signing key).
572    /// 5. Check the outer certificate (proof-of-ownership of identity key).
573    ///
574    /// TODO: Replace `pre_tolerance` and `post_tolerance` with
575    /// `tor_dircommon::config::DirTolerance` which is not possible at the
576    /// moment due to a circular dependency of `tor-dircommon` depending
577    /// upon `tor-netdoc`.
578    ///
579    /// TODO: Consider whether to try to deduplicate this signature checking
580    /// somehow, wrt to [`UncheckedAuthCert`].
581    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        // (1) Check whether this comes from a valid authority in `v3idents`.
591        if !v3idents.contains(&body.fingerprint.0) {
592            return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
593        }
594
595        // (2) Check whether the timestamps are valid (± tolerance).
596        let validity = *body.dir_key_published..=*body.dir_key_expires;
597        parse2::check_validity_time_tolerance(now, validity, pre_tolerance, post_tolerance)?;
598
599        // (3) Check whether the fingerprint and long-term identity key match.
600        if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
601            return Err(parse2::VerifyFailed::Inconsistent);
602        }
603
604        // (4) Check the cross-certificate (proof-of-ownership of signing key).
605        body.dir_signing_key.verify(
606            body.fingerprint.0.as_bytes(),
607            &body.dir_key_crosscert.signature,
608        )?;
609
610        // (5) Check the outer certificate (proof-of-ownership of identity key).
611        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    /// Verify the signatures (and check validity times)
620    ///
621    /// The pre and post tolerance (time check allowances) used are both zero.
622    ///
623    /// # Security considerations
624    ///
625    /// The caller must check that the KP_auth_id is correct/relevant.
626    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    /// Make the base for a new `AuthCert`
634    ///
635    /// This contains only the mandatory fields (the ones in `AuthCertConstructor`).
636    /// This method is an alternative to providing a `AuthCertConstructor` value display,
637    /// and is convenient because an authcert contains much recapitulated information.
638    ///
639    /// # Example
640    ///
641    /// ```no_run
642    /// # fn main() -> Result<(), anyhow::Error> {
643    /// use tor_netdoc::doc::authcert::AuthCert;
644    /// let (k_auth_id_rsa, k_auth_sign_rsa, published, expires) = todo!();
645    /// let authcert = AuthCert {
646    ///     dir_address: Some("192.0.2.17:7000".parse()?),
647    ///     ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
648    /// };
649    /// # Ok(())
650    /// # }
651    /// ```
652    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    /// Encode this `AuthCert` and sign it with `k_auth_id_rsa`
675    ///
676    /// Yields the string representation of the signed, encoded, document,
677    /// as an [`EncodedAuthCert`].
678    // TODO these features are quite tangled
679    // `EncodedAuthCert` is only available with `parse2` and `plain-consensus`
680    #[cfg(feature = "incomplete")] // Needs EncodedAuthCert
681    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        // This rechecks the invariants which ought to be true by construction.
694        // That is convenient for the code here, and the perf implications are irrelevant.
695        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    // @@ begin test lint list maintained by maint/add_warning @@
705    #![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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
717    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        // Taken from TESTDATA
741        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        // We should recover from the failed case and read the next data fine.
798        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        // We should recover from the failed case and read the next data fine.
811        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        // === AUTHCERT D190BF3B00E311A9AEB6D62B51980E9B2109BAD1 ===
834        // These values come from testdata2/keys/authority_certificate.
835        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        /// A system time in the range of [`DIR_KEY_PUBLISHED`] and [`DIR_KEY_EXPIRES`].
874        ///
875        /// Constructed by ourselves to have a time point we can use for testing
876        /// timestamp verification.
877        const VALID_SYSTEM_TIME: &str = "2000-06-01 00:00:00";
878
879        // === AUTHCERT 0B8997614EC647C1C6B6A044E2B5408F0B823FB0 ===
880        // This values come from ../../testdata2/cached-certs--1
881        // A different authority certificate different from the one above.
882        const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
883
884        /// Converts a string in the [`Iso8601TimeSp`] format to [`SystemTime`].
885        ///
886        /// This functions panics in the case the input is malformatted.
887        fn to_system_time(s: &str) -> SystemTime {
888            Iso8601TimeSp::from_str(s).unwrap().0
889        }
890
891        /// Converts a PEM encoded RSA Public key to an [`rsa::PublicKey`].
892        ///
893        /// This function panics in the case the input is malformatted.
894        fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
895            rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
896        }
897
898        /// Converts a hex-encoded RSA identity to an [`RsaIdentity`].
899        ///
900        /// This function panics in the case the input is malformatted.
901        fn to_rsa_id(s: &str) -> RsaIdentity {
902            RsaIdentity::from_hex(s).unwrap()
903        }
904
905        /// Tests whether a [`DirKeyCrossCert`] can be parsed properly.
906        #[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            // "Encodes" a DIR_CROSS_CERT_OBJECT by simply removing the lines
915            // indicating the BEGIN and END, as the purpose is to test multiple
916            // labels.
917            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            // Try with `SIGNATURE`.
928            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            // Try with `ID SIGNATURE`.
942            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            // Try with different label and fail.
956            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            // Try with extra args.
971            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            // Test a valid signature.
1022            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            // Test with an invalid authority.
1032            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            // Test a key too far in the future.
1045            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            // Test an almost too new.
1058            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            // Now fail when we are 1s below ...
1068            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            // ... but succeed again with a clock skew tolerance.
1081            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            // Test a key too old.
1091            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            // Test an almost too old.
1106            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            // Now fail when we are 1s above ...
1116            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            // ... but succeed again with a clock skew tolerance.
1129            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            // Check with non-matching fingerprint and long-term identity key.
1139            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            // Check invalid cross-cert.
1160            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            // Check outer signature.
1176            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}