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::{
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
65/// Rules about entries that must appear in an AuthCert, and how they must
66/// be formed.
67static 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/// A single directory authority key certificate
91///
92/// This is the body, not including signatures.
93///
94/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html>
95///
96/// To make a fresh `AuthCert`, use [`AuthCertConstructor`].
97#[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    /// Intro line
104    ///
105    /// Currently must be version 3.
106    ///
107    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
108    #[deftly(constructor(default = AuthCertVersion::V3))]
109    #[deftly(netdoc(single_arg))]
110    pub dir_key_certificate_version: AuthCertVersion,
111
112    /// An IPv4 address for this authority.
113    #[deftly(netdoc(single_arg))]
114    pub dir_address: Option<net::SocketAddrV4>,
115
116    /// H(KP_auth_id_rsa)
117    ///
118    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:fingerprint>
119    #[deftly(constructor)]
120    #[deftly(netdoc(single_arg))]
121    pub fingerprint: Fingerprint,
122
123    /// Declared time when this certificate was published
124    ///
125    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-published>
126    #[deftly(constructor)]
127    #[deftly(netdoc(single_arg))]
128    pub dir_key_published: Iso8601TimeSp,
129
130    /// Declared time when this certificate expires.
131    ///
132    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-expires>
133    #[deftly(constructor)]
134    #[deftly(netdoc(single_arg))]
135    pub dir_key_expires: Iso8601TimeSp,
136
137    /// KP_auth_id_rsa
138    ///
139    /// The long-term RSA identity key for this authority
140    ///
141    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-identity-key>
142    #[deftly(constructor)]
143    pub dir_identity_key: rsa::PublicKey,
144
145    /// KP_auth_sign_rsa
146    ///
147    /// The medium-term RSA signing key for this authority
148    ///
149    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-signing-key>
150    #[deftly(constructor)]
151    pub dir_signing_key: rsa::PublicKey,
152
153    /// SHA1(DER(KP_auth_id_rsa)) signed by KP_auth_sign_rsa
154    ///
155    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
156    #[deftly(constructor)]
157    pub dir_key_crosscert: CrossCert,
158
159    #[doc(hidden)]
160    #[deftly(netdoc(skip))]
161    pub __non_exhaustive: (),
162}
163
164/// Represents the version of an [`AuthCert`].
165///
166/// Single argument.
167///
168/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
169#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
170#[non_exhaustive]
171pub enum AuthCertVersion {
172    /// The current and only version understood.
173    #[strum(serialize = "3")]
174    V3,
175}
176
177impl NormalItemArgument for AuthCertVersion {}
178
179/// A pair of key identities that identifies a certificate.
180#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
181#[allow(clippy::exhaustive_structs)]
182pub struct AuthCertKeyIds {
183    /// Fingerprint of identity key
184    pub id_fingerprint: rsa::RsaIdentity,
185    /// Fingerprint of signing key
186    pub sk_fingerprint: rsa::RsaIdentity,
187}
188
189/// An authority certificate whose signature and validity time we
190/// haven't checked.
191pub struct UncheckedAuthCert {
192    /// Where we found this AuthCert within the string containing it.
193    location: Option<Extent>,
194
195    /// The actual unchecked certificate.
196    c: signed::SignatureGated<timed::TimeRangeBound<AuthCert>>,
197}
198
199impl UncheckedAuthCert {
200    /// If this AuthCert was originally parsed from `haystack`, return its
201    /// text.
202    ///
203    /// TODO: This is a pretty bogus interface; there should be a
204    /// better way to remember where to look for this thing if we want
205    /// it without keeping the input alive forever.  We should
206    /// refactor.
207    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    /// Make an [`AuthCertBuilder`] object that can be used to
216    /// construct authority certificates for testing.
217    #[cfg(feature = "build_docs")]
218    #[deprecated = "use AuthCertConstructor instead"]
219    #[allow(deprecated)]
220    pub fn builder() -> AuthCertBuilder {
221        AuthCertBuilder::new()
222    }
223
224    /// Parse an authority certificate from a string.
225    ///
226    /// This function verifies the certificate's signatures, but doesn't
227    /// check its expiration dates.
228    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    /// Return an iterator yielding authority certificates from a string.
236    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    /*
248        /// Return true if this certificate is expired at a given time, or
249        /// not yet valid at that time.
250        pub fn is_expired_at(&self, when: time::SystemTime) -> bool {
251            when < self.published || when > self.expires
252        }
253    */
254    /// Return the signing key certified by this certificate.
255    pub fn signing_key(&self) -> &rsa::PublicKey {
256        &self.dir_signing_key
257    }
258
259    /// Return an AuthCertKeyIds object describing the keys in this
260    /// certificate.
261    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    /// Return an RsaIdentity for this certificate's identity key.
269    pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
270        &self.fingerprint
271    }
272
273    /// Return the time when this certificate says it was published.
274    pub fn published(&self) -> time::SystemTime {
275        *self.dir_key_published
276    }
277
278    /// Return the time when this certificate says it should expire.
279    pub fn expires(&self) -> time::SystemTime {
280        *self.dir_key_expires
281    }
282
283    /// Parse an authority certificate from a reader.
284    fn from_body(body: &Section<'_, AuthCertKwd>, s: &str) -> Result<UncheckedAuthCert> {
285        use AuthCertKwd::*;
286
287        // Make sure first and last element are correct types.  We can
288        // safely call unwrap() on first and last, since there are required
289        // tokens in the rules, so we know that at least one token will have
290        // been parsed.
291        let start_pos = {
292            // Unwrap should be safe because `.parse()` would have already
293            // returned an Error
294            #[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            // Unwrap should be safe because `.parse()` would have already
305            // returned an Error
306            #[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            // Check fingerprint for consistency with key.
350            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        // check crosscert
364        let dir_key_crosscert;
365        let v_crosscert = {
366            let crosscert = body.required(DIR_KEY_CROSSCERT)?;
367            // Unwrap should be safe because `.parse()` and `required()` would
368            // have already returned an Error
369            #[allow(clippy::unwrap_used)]
370            let mut tag = crosscert.obj_tag().unwrap();
371            // we are required to support both.
372            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            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
379
380            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        // check the signature
390        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            // Unwrap should be safe because `.parse()` would have already
396            // returned an Error
397            #[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            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
408
409            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
450/// Parsing/encoding module for `AuthCertKeyIds` as found in `directory-signature`
451///
452/// Use with `#[deftly(netdoc(with = ...))]` when deriving
453/// `ItemValueParseable` and `ItemValueEncodable`.
454///
455/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:directory-signature>
456//
457// Currently the only use site is `netstatus::Signature`.
458// If we find this is being used in many places, and is therefore a standard thing,
459// we should arrange for the derives to be able to derive from an argument collection,
460// and use that.
461pub(crate) mod keyids_directory_signature_args {
462    use super::*;
463    use std::result::Result;
464
465    /// Parse
466    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    /// Encode
477    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/// Pseudo-Signature of the long-term identity key by the medium-term key.
489///
490/// This type does not implement `SignatureItemParseable` because that trait
491/// is reserved for signatures on *netdocs*, such as [`AuthCertSignature`].
492/// As `CrossCert` does not sign a full document, it implements only
493/// `ItemValueParseable`, instead.
494///
495/// Verification of this signature is done in `AuthCertUnverified::verify`,
496/// and during parsing by the old parser.
497/// So a `CrossCert` in [`AuthCert::dir_key_crosscert`] in a bare `AuthCert` has been validated.
498//
499// TODO SPEC (Diziet): it is far from clear to me that this cert serves any useful purpose.
500// However, we are far too busy now with rewriting the universe to consider transitioning it away.
501#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
502#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
503#[deftly(netdoc(no_extra_args))]
504#[non_exhaustive]
505pub struct CrossCert {
506    /// The bytes of the signature (base64-decoded).
507    #[deftly(netdoc(object))]
508    pub signature: CrossCertObject,
509}
510
511/// Wrapper around [`Vec<u8>`] implementing [`ItemObjectParseable`] properly.
512///
513/// Unfortunately, this wrapper is necessary, because the specification
514/// demands that these certificate objects must accept two labels:
515/// `SIGNATURE` and `ID SIGNATURE`.  Because the deftly template for
516/// `ItemValueParseable` only allows for a single label
517/// (`#[deftly(netdoc(object(label = "LABEL")))]`), we must implement this
518/// trait ourselves in order to allow multiple ones.
519///
520/// TODO: In the future, it might be nice to let the respective fmeta
521/// accept a pattern, as pattern matching would allow trivially for one
522/// to infinity different combinations.
523/// TODO SPEC: Alternatively we could abolish the wrong labels,
524/// or we could abolish Objects completely and just have long lines.
525///
526/// # Specifications
527///
528/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
529#[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    /// Make a `CrossCert`
536    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/// Signatures for [`AuthCert`]
550///
551/// Signed by [`AuthCert::dir_identity_key`] in order to prove ownership.
552/// Can be seen as the opposite of [`AuthCert::dir_key_crosscert`].
553///
554/// # Specifications
555///
556/// * <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certification>
557/// * <https://spec.torproject.org/dir-spec/netdoc.html#signing>
558#[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    /// Contains the actual signature, see [`AuthCertSignatures`].
564    pub dir_key_certification: RsaSha1Signature,
565}
566
567/// RSA signature for data in [`AuthCert`]
568///
569/// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
570///
571/// Compatibility type alias for [`RsaSha1Signature`].
572#[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    /// Verifies the signature of a [`AuthCert`]
612    ///
613    /// # Algorithm
614    ///
615    /// 1. Check whether this comes from a valid authority in `v3idents`.
616    /// 2. Check whether the timestamps are valid (± tolerance).
617    /// 3. Check whether the fingerprint and long-term identity key match.
618    /// 4. Check the cross-certificate (proof-of-ownership of signing key).
619    /// 5. Check the outer certificate (proof-of-ownership of identity key).
620    ///
621    /// TODO: Replace `pre_tolerance` and `post_tolerance` with
622    /// `tor_dircommon::config::DirTolerance` which is not possible at the
623    /// moment due to a circular dependency of `tor-dircommon` depending
624    /// upon `tor-netdoc`.
625    ///
626    /// TODO: Consider whether to try to deduplicate this signature checking
627    /// somehow, wrt to [`UncheckedAuthCert`].
628    pub fn verify(
629        self,
630        v3idents: &[RsaIdentity],
631    ) -> StdResult<TimeRangeBound<AuthCert>, parse2::VerifyFailed> {
632        let (body, sigs) = (self.body, self.sigs);
633
634        // (1) Check whether this comes from a valid authority in `v3idents`.
635        if !v3idents.contains(&body.fingerprint.0) {
636            return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
637        }
638
639        // (2) Check whether the timestamps are valid (± tolerance).
640        let validity = *body.dir_key_published..=*body.dir_key_expires;
641
642        // (3) Check whether the fingerprint and long-term identity key match.
643        if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
644            return Err(parse2::VerifyFailed::Inconsistent);
645        }
646
647        // (4) Check the cross-certificate (proof-of-ownership of signing key).
648        body.dir_signing_key.verify(
649            body.fingerprint.0.as_bytes(),
650            &body.dir_key_crosscert.signature,
651        )?;
652
653        // (5) Check the outer certificate (proof-of-ownership of identity key).
654        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    /// Verify the signatures (and check validity times)
663    ///
664    /// The pre and post tolerance (time check allowances) used are both zero.
665    ///
666    /// # Security considerations
667    ///
668    /// The caller must check that the KP_auth_id is correct/relevant.
669    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    /// Make the base for a new `AuthCert`
677    ///
678    /// This contains only the mandatory fields (the ones in `AuthCertConstructor`).
679    /// This method is an alternative to providing a `AuthCertConstructor` value display,
680    /// and is convenient because an authcert contains much recapitulated information.
681    ///
682    /// # Example
683    ///
684    /// ```no_run
685    /// # fn main() -> Result<(), anyhow::Error> {
686    /// use tor_netdoc::doc::authcert::AuthCert;
687    /// let (k_auth_id_rsa, k_auth_sign_rsa, published, expires) = todo!();
688    /// let authcert = AuthCert {
689    ///     dir_address: Some("192.0.2.17:7000".parse()?),
690    ///     ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
691    /// };
692    /// # Ok(())
693    /// # }
694    /// ```
695    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    /// Encode this `AuthCert` and sign it with `k_auth_id_rsa`
718    ///
719    /// Yields the string representation of the signed, encoded, document,
720    /// as an [`EncodedAuthCert`].
721    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        // This rechecks the invariants which ought to be true by construction.
734        // That is convenient for the code here, and the perf implications are irrelevant.
735        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    // @@ begin test lint list maintained by maint/add_warning @@
745    #![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)] // See arti#2571
757    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
758    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        // Taken from TESTDATA
797        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        // We should recover from the failed case and read the next data fine.
854        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        // We should recover from the failed case and read the next data fine.
867        assert!(res[0].is_err());
868        assert!(res[1].is_ok());
869        assert_eq!(res.len(), 2);
870    }
871
872    // === AUTHCERT D190BF3B00E311A9AEB6D62B51980E9B2109BAD1 ===
873    // These values come from testdata2/keys/authority_certificate.
874    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    /// A system time in the range of [`DIR_KEY_PUBLISHED`] and [`DIR_KEY_EXPIRES`].
913    ///
914    /// Constructed by ourselves to have a time point we can use for testing
915    /// timestamp verification.
916    const VALID_SYSTEM_TIME: &str = "2000-06-01 00:00:00";
917
918    // === AUTHCERT 0B8997614EC647C1C6B6A044E2B5408F0B823FB0 ===
919    // This values come from ../../testdata2/cached-certs--1
920    // A different authority certificate different from the one above.
921    const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
922
923    /// Converts a string in the [`Iso8601TimeSp`] format to [`SystemTime`].
924    ///
925    /// This functions panics in the case the input is malformatted.
926    fn to_system_time(s: &str) -> SystemTime {
927        Iso8601TimeSp::from_str(s).unwrap().0
928    }
929
930    /// Converts a PEM encoded RSA Public key to an [`rsa::PublicKey`].
931    ///
932    /// This function panics in the case the input is malformatted.
933    fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
934        rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
935    }
936
937    /// Converts a hex-encoded RSA identity to an [`RsaIdentity`].
938    ///
939    /// This function panics in the case the input is malformatted.
940    fn to_rsa_id(s: &str) -> RsaIdentity {
941        RsaIdentity::from_hex(s).unwrap()
942    }
943
944    /// Tests whether a [`DirKeyCrossCert`] can be parsed properly.
945    #[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        // "Encodes" a DIR_CROSS_CERT_OBJECT by simply removing the lines
954        // indicating the BEGIN and END, as the purpose is to test multiple
955        // labels.
956        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        // Try with `SIGNATURE`.
967        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        // Try with `ID SIGNATURE`.
981        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        // Try with different label and fail.
995        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        // Try with extra args.
1010        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        // Test a valid signature.
1059        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        // Test with an invalid authority.
1067        assert_eq!(
1068            res.clone().verify(&[],).unwrap_err(),
1069            VerifyFailed::InsufficientTrustedSigners
1070        );
1071
1072        // Test a key too far in the future.
1073        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        // Test an almost too new.
1084        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        // Now fail when we are 1s below ...
1092        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        // ... but succeed again with a clock skew tolerance.
1103        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        // Test a key too old.
1112        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        // Test an almost too old.
1127        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        // Now fail when we are 1s above ...
1135        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        // ... but succeed again with a clock skew tolerance.
1146        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        // Check with non-matching fingerprint and long-term identity key.
1155        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        // Check invalid cross-cert.
1169        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        // Check outer signature.
1178        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}