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
48#[cfg(feature = "incomplete")]
49mod encoded;
50#[cfg(feature = "incomplete")]
51pub use encoded::EncodedAuthCert;
52
53decl_keyword! {
54    pub(crate) AuthCertKwd {
55        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
56        "dir-address" => DIR_ADDRESS,
57        "fingerprint" => FINGERPRINT,
58        "dir-identity-key" => DIR_IDENTITY_KEY,
59        "dir-key-published" => DIR_KEY_PUBLISHED,
60        "dir-key-expires" => DIR_KEY_EXPIRES,
61        "dir-signing-key" => DIR_SIGNING_KEY,
62        "dir-key-crosscert" => DIR_KEY_CROSSCERT,
63        "dir-key-certification" => DIR_KEY_CERTIFICATION,
64    }
65}
66
67/// Rules about entries that must appear in an AuthCert, and how they must
68/// be formed.
69static AUTHCERT_RULES: LazyLock<SectionRules<AuthCertKwd>> = LazyLock::new(|| {
70    use AuthCertKwd::*;
71
72    let mut rules = SectionRules::builder();
73    rules.add(DIR_KEY_CERTIFICATE_VERSION.rule().required().args(1..));
74    rules.add(DIR_ADDRESS.rule().args(1..));
75    rules.add(FINGERPRINT.rule().required().args(1..));
76    rules.add(DIR_IDENTITY_KEY.rule().required().no_args().obj_required());
77    rules.add(DIR_SIGNING_KEY.rule().required().no_args().obj_required());
78    rules.add(DIR_KEY_PUBLISHED.rule().required());
79    rules.add(DIR_KEY_EXPIRES.rule().required());
80    rules.add(DIR_KEY_CROSSCERT.rule().required().no_args().obj_required());
81    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
82    rules.add(
83        DIR_KEY_CERTIFICATION
84            .rule()
85            .required()
86            .no_args()
87            .obj_required(),
88    );
89    rules.build()
90});
91
92/// A single directory authority key certificate
93///
94/// This is the body, not including signatures.
95///
96/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html>
97///
98/// To make a fresh `AuthCert`, use [`AuthCertConstructor`].
99#[derive(Clone, Debug, Deftly)]
100#[derive_deftly(Constructor)]
101#[derive_deftly(NetdocParseableUnverified, NetdocEncodable)]
102#[cfg_attr(test, derive(PartialEq, Eq))]
103#[allow(clippy::exhaustive_structs)]
104pub struct AuthCert {
105    /// Intro line
106    ///
107    /// Currently must be version 3.
108    ///
109    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
110    #[deftly(constructor(default = AuthCertVersion::V3))]
111    #[deftly(netdoc(single_arg))]
112    pub dir_key_certificate_version: AuthCertVersion,
113
114    /// An IPv4 address for this authority.
115    #[deftly(netdoc(single_arg))]
116    pub dir_address: Option<net::SocketAddrV4>,
117
118    /// H(KP_auth_id_rsa)
119    ///
120    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:fingerprint>
121    #[deftly(constructor)]
122    #[deftly(netdoc(single_arg))]
123    pub fingerprint: Fingerprint,
124
125    /// Declared time when this certificate was published
126    ///
127    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-published>
128    #[deftly(constructor)]
129    #[deftly(netdoc(single_arg))]
130    pub dir_key_published: Iso8601TimeSp,
131
132    /// Declared time when this certificate expires.
133    ///
134    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-expires>
135    #[deftly(constructor)]
136    #[deftly(netdoc(single_arg))]
137    pub dir_key_expires: Iso8601TimeSp,
138
139    /// KP_auth_id_rsa
140    ///
141    /// The long-term RSA identity key for this authority
142    ///
143    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-identity-key>
144    #[deftly(constructor)]
145    pub dir_identity_key: rsa::PublicKey,
146
147    /// KP_auth_sign_rsa
148    ///
149    /// The medium-term RSA signing key for this authority
150    ///
151    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-signing-key>
152    #[deftly(constructor)]
153    pub dir_signing_key: rsa::PublicKey,
154
155    /// SHA1(DER(KP_auth_id_rsa)) signed by KP_auth_sign_rsa
156    ///
157    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
158    #[deftly(constructor)]
159    pub dir_key_crosscert: CrossCert,
160
161    #[doc(hidden)]
162    #[deftly(netdoc(skip))]
163    pub __non_exhaustive: (),
164}
165
166/// Represents the version of an [`AuthCert`].
167///
168/// Single argument.
169///
170/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
171#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
172#[non_exhaustive]
173pub enum AuthCertVersion {
174    /// The current and only version understood.
175    #[strum(serialize = "3")]
176    V3,
177}
178
179impl NormalItemArgument for AuthCertVersion {}
180
181/// A pair of key identities that identifies a certificate.
182#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
183#[allow(clippy::exhaustive_structs)]
184pub struct AuthCertKeyIds {
185    /// Fingerprint of identity key
186    pub id_fingerprint: rsa::RsaIdentity,
187    /// Fingerprint of signing key
188    pub sk_fingerprint: rsa::RsaIdentity,
189}
190
191/// An authority certificate whose signature and validity time we
192/// haven't checked.
193pub struct UncheckedAuthCert {
194    /// Where we found this AuthCert within the string containing it.
195    location: Option<Extent>,
196
197    /// The actual unchecked certificate.
198    c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
199}
200
201impl UncheckedAuthCert {
202    /// If this AuthCert was originally parsed from `haystack`, return its
203    /// text.
204    ///
205    /// TODO: This is a pretty bogus interface; there should be a
206    /// better way to remember where to look for this thing if we want
207    /// it without keeping the input alive forever.  We should
208    /// refactor.
209    pub fn within<'a>(&self, haystack: &'a str) -> Option<&'a str> {
210        self.location
211            .as_ref()
212            .and_then(|ext| ext.reconstruct(haystack))
213    }
214}
215
216impl AuthCert {
217    /// Make an [`AuthCertBuilder`] object that can be used to
218    /// construct authority certificates for testing.
219    #[cfg(feature = "build_docs")]
220    #[deprecated = "use AuthCertConstructor instead"]
221    #[allow(deprecated)]
222    pub fn builder() -> AuthCertBuilder {
223        AuthCertBuilder::new()
224    }
225
226    /// Parse an authority certificate from a string.
227    ///
228    /// This function verifies the certificate's signatures, but doesn't
229    /// check its expiration dates.
230    pub fn parse(s: &str) -> Result<UncheckedAuthCert> {
231        let mut reader = NetDocReader::new(s)?;
232        let body = AUTHCERT_RULES.parse(&mut reader)?;
233        reader.should_be_exhausted()?;
234        AuthCert::from_body(&body, s).map_err(|e| e.within(s))
235    }
236
237    /// Return an iterator yielding authority certificates from a string.
238    pub fn parse_multiple(s: &str) -> Result<impl Iterator<Item = Result<UncheckedAuthCert>> + '_> {
239        use AuthCertKwd::*;
240        let sections = NetDocReader::new(s)?
241            .batching_split_before_loose(|item| item.is_ok_with_kwd(DIR_KEY_CERTIFICATE_VERSION));
242        Ok(sections
243            .map(|mut section| {
244                let body = AUTHCERT_RULES.parse(&mut section)?;
245                AuthCert::from_body(&body, s)
246            })
247            .map(|r| r.map_err(|e| e.within(s))))
248    }
249    /*
250        /// Return true if this certificate is expired at a given time, or
251        /// not yet valid at that time.
252        pub fn is_expired_at(&self, when: time::SystemTime) -> bool {
253            when < self.published || when > self.expires
254        }
255    */
256    /// Return the signing key certified by this certificate.
257    pub fn signing_key(&self) -> &rsa::PublicKey {
258        &self.dir_signing_key
259    }
260
261    /// Return an AuthCertKeyIds object describing the keys in this
262    /// certificate.
263    pub fn key_ids(&self) -> AuthCertKeyIds {
264        AuthCertKeyIds {
265            id_fingerprint: self.fingerprint.0,
266            sk_fingerprint: self.dir_signing_key.to_rsa_identity(),
267        }
268    }
269
270    /// Return an RsaIdentity for this certificate's identity key.
271    pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
272        &self.fingerprint
273    }
274
275    /// Return the time when this certificate says it was published.
276    pub fn published(&self) -> time::SystemTime {
277        *self.dir_key_published
278    }
279
280    /// Return the time when this certificate says it should expire.
281    pub fn expires(&self) -> time::SystemTime {
282        *self.dir_key_expires
283    }
284
285    /// Parse an authority certificate from a reader.
286    fn from_body(body: &Section<'_, AuthCertKwd>, s: &str) -> Result<UncheckedAuthCert> {
287        use AuthCertKwd::*;
288
289        // Make sure first and last element are correct types.  We can
290        // safely call unwrap() on first and last, since there are required
291        // tokens in the rules, so we know that at least one token will have
292        // been parsed.
293        let start_pos = {
294            // Unwrap should be safe because `.parse()` would have already
295            // returned an Error
296            #[allow(clippy::unwrap_used)]
297            let first_item = body.first_item().unwrap();
298            if first_item.kwd() != DIR_KEY_CERTIFICATE_VERSION {
299                return Err(EK::WrongStartingToken
300                    .with_msg(first_item.kwd_str().to_string())
301                    .at_pos(first_item.pos()));
302            }
303            first_item.pos()
304        };
305        let end_pos = {
306            // Unwrap should be safe because `.parse()` would have already
307            // returned an Error
308            #[allow(clippy::unwrap_used)]
309            let last_item = body.last_item().unwrap();
310            if last_item.kwd() != DIR_KEY_CERTIFICATION {
311                return Err(EK::WrongEndingToken
312                    .with_msg(last_item.kwd_str().to_string())
313                    .at_pos(last_item.pos()));
314            }
315            last_item.end_pos()
316        };
317
318        let version = body
319            .required(DIR_KEY_CERTIFICATE_VERSION)?
320            .parse_arg::<u32>(0)?;
321        if version != 3 {
322            return Err(EK::BadDocumentVersion.with_msg(format!("unexpected version {}", version)));
323        }
324        let dir_key_certificate_version = AuthCertVersion::V3;
325
326        let dir_signing_key: rsa::PublicKey = body
327            .required(DIR_SIGNING_KEY)?
328            .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
329            .check_len(1024..)?
330            .check_exponent(65537)?
331            .into();
332
333        let dir_identity_key: rsa::PublicKey = body
334            .required(DIR_IDENTITY_KEY)?
335            .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
336            .check_len(1024..)?
337            .check_exponent(65537)?
338            .into();
339
340        let dir_key_published = body
341            .required(DIR_KEY_PUBLISHED)?
342            .args_as_str()
343            .parse::<Iso8601TimeSp>()?;
344
345        let dir_key_expires = body
346            .required(DIR_KEY_EXPIRES)?
347            .args_as_str()
348            .parse::<Iso8601TimeSp>()?;
349
350        {
351            // Check fingerprint for consistency with key.
352            let fp_tok = body.required(FINGERPRINT)?;
353            let fingerprint: RsaIdentity = fp_tok.args_as_str().parse::<Fingerprint>()?.into();
354            if fingerprint != dir_identity_key.to_rsa_identity() {
355                return Err(EK::BadArgument
356                    .at_pos(fp_tok.pos())
357                    .with_msg("fingerprint does not match RSA identity"));
358            }
359        }
360
361        let dir_address = body
362            .maybe(DIR_ADDRESS)
363            .parse_args_as_str::<net::SocketAddrV4>()?;
364
365        // check crosscert
366        let dir_key_crosscert;
367        let v_crosscert = {
368            let crosscert = body.required(DIR_KEY_CROSSCERT)?;
369            // Unwrap should be safe because `.parse()` and `required()` would
370            // have already returned an Error
371            #[allow(clippy::unwrap_used)]
372            let mut tag = crosscert.obj_tag().unwrap();
373            // we are required to support both.
374            if tag != "ID SIGNATURE" && tag != "SIGNATURE" {
375                tag = "ID SIGNATURE";
376            }
377            let sig = crosscert.obj(tag)?;
378
379            let signed = dir_identity_key.to_rsa_identity();
380            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
381
382            let v = rsa::ValidatableRsaSignature::new(&dir_signing_key, &sig, signed.as_bytes());
383
384            dir_key_crosscert = CrossCert {
385                signature: CrossCertObject(sig),
386            };
387
388            v
389        };
390
391        // check the signature
392        let v_sig = {
393            let signature = body.required(DIR_KEY_CERTIFICATION)?;
394            let sig = signature.obj("SIGNATURE")?;
395
396            let mut sha1 = d::Sha1::new();
397            // Unwrap should be safe because `.parse()` would have already
398            // returned an Error
399            #[allow(clippy::unwrap_used)]
400            let start_offset = body.first_item().unwrap().offset_in(s).unwrap();
401            #[allow(clippy::unwrap_used)]
402            let end_offset = body.last_item().unwrap().offset_in(s).unwrap();
403            let end_offset = end_offset + "dir-key-certification\n".len();
404            sha1.update(
405                s.get(start_offset..end_offset)
406                    .ok_or(internal!("chopped utf8"))?,
407            );
408            let sha1 = sha1.finalize();
409            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
410
411            rsa::ValidatableRsaSignature::new(&dir_identity_key, &sig, &sha1)
412        };
413
414        let id_fingerprint = dir_identity_key.to_rsa_identity();
415
416        let location = {
417            let start_idx = start_pos.offset_within(s);
418            let end_idx = end_pos.offset_within(s);
419            match (start_idx, end_idx) {
420                (Some(a), Some(b)) => {
421                    Extent::new(s, s.get(a..b + 1).ok_or(internal!("chopped utf8"))?)
422                }
423                _ => None,
424            }
425        };
426
427        let authcert = AuthCert {
428            dir_key_certificate_version,
429            dir_address,
430            dir_identity_key,
431            dir_signing_key,
432            dir_key_published,
433            dir_key_expires,
434            dir_key_crosscert,
435            fingerprint: Fingerprint(id_fingerprint),
436            __non_exhaustive: (),
437        };
438
439        let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
440            vec![Box::new(v_crosscert), Box::new(v_sig)];
441
442        let timed = timed::TimerangeBound::new(authcert, *dir_key_published..*dir_key_expires);
443        let signed = signed::SignatureGated::new(timed, signatures);
444        let unchecked = UncheckedAuthCert {
445            location,
446            c: signed,
447        };
448        Ok(unchecked)
449    }
450}
451
452/// Parsing/encoding module for `AuthCertKeyIds` as found in `directory-signature`
453///
454/// Use with `#[deftly(netdoc(with = ...))]` when deriving
455/// `ItemValueParseable` and `ItemValueEncodable`.
456///
457/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:directory-signature>
458//
459// Currently the only use site is `netstatus::Signature`.
460// If we find this is being used in many places, and is therefore a standard thing,
461// we should arrange for the derives to be able to derive from an argument collection,
462// and use that.
463pub(crate) mod keyids_directory_signature_args {
464    use super::*;
465    use std::result::Result;
466
467    /// Parse
468    pub(crate) fn from_args<'s>(
469        args: &mut ArgumentStream<'s>,
470    ) -> Result<AuthCertKeyIds, ArgumentError> {
471        let mut fp = || Ok::<_, ArgumentError>(Fingerprint::from_args(args)?.0);
472        Ok(AuthCertKeyIds {
473            id_fingerprint: fp()?,
474            sk_fingerprint: fp()?,
475        })
476    }
477
478    /// Encode
479    pub(crate) fn write_arg_onto(
480        self_: &AuthCertKeyIds,
481        out: &mut ItemEncoder<'_>,
482    ) -> Result<(), Bug> {
483        let mut fp = |id| Fingerprint(id).write_arg_onto(out);
484        fp(self_.id_fingerprint)?;
485        fp(self_.sk_fingerprint)?;
486        Ok(())
487    }
488}
489
490/// Pseudo-Signature of the long-term identity key by the medium-term key.
491///
492/// This type does not implement `SignatureItemParseable` because that trait
493/// is reserved for signatures on *netdocs*, such as [`AuthCertSignature`].
494/// As `CrossCert` does not sign a full document, it implements only
495/// `ItemValueParseable`, instead.
496///
497/// Verification of this signature is done in `AuthCertUnverified::verify`,
498/// and during parsing by the old parser.
499/// So a `CrossCert` in [`AuthCert::dir_key_crosscert`] in a bare `AuthCert` has been validated.
500//
501// TODO SPEC (Diziet): it is far from clear to me that this cert serves any useful purpose.
502// However, we are far too busy now with rewriting the universe to consider transitioning it away.
503#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
504#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
505#[deftly(netdoc(no_extra_args))]
506#[non_exhaustive]
507pub struct CrossCert {
508    /// The bytes of the signature (base64-decoded).
509    #[deftly(netdoc(object))]
510    pub signature: CrossCertObject,
511}
512
513/// Wrapper around [`Vec<u8>`] implementing [`ItemObjectParseable`] properly.
514///
515/// Unfortunately, this wrapper is necessary, because the specification
516/// demands that these certificate objects must accept two labels:
517/// `SIGNATURE` and `ID SIGNATURE`.  Because the deftly template for
518/// `ItemValueParseable` only allows for a single label
519/// (`#[deftly(netdoc(object(label = "LABEL")))]`), we must implement this
520/// trait ourselves in order to allow multiple ones.
521///
522/// TODO: In the future, it might be nice to let the respective fmeta
523/// accept a pattern, as pattern matching would allow trivially for one
524/// to infinity different combinations.
525/// TODO SPEC: Alternatively we could abolish the wrong labels,
526/// or we could abolish Objects completely and just have long lines.
527///
528/// # Specifications
529///
530/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
531#[derive(Clone, PartialEq, Eq, derive_more::Deref)]
532#[non_exhaustive]
533pub struct CrossCertObject(pub Vec<u8>);
534impl_debug_hex! { CrossCertObject . 0 }
535
536impl CrossCert {
537    /// Make a `CrossCert`
538    pub fn new(
539        k_auth_sign_rsa: &rsa::KeyPair,
540        h_kp_auth_id_rsa: &RsaIdentity,
541    ) -> StdResult<Self, Bug> {
542        let signature = k_auth_sign_rsa
543            .sign(h_kp_auth_id_rsa.as_bytes())
544            .map_err(into_internal!("failed to sign cross-cert"))?;
545        Ok(CrossCert {
546            signature: CrossCertObject(signature),
547        })
548    }
549}
550
551/// Signatures for [`AuthCert`]
552///
553/// Signed by [`AuthCert::dir_identity_key`] in order to prove ownership.
554/// Can be seen as the opposite of [`AuthCert::dir_key_crosscert`].
555///
556/// # Specifications
557///
558/// * <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certification>
559/// * <https://spec.torproject.org/dir-spec/netdoc.html#signing>
560#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
561#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
562#[deftly(netdoc(signatures(hashes_accu = Sha1WholeKeywordLine)))]
563#[non_exhaustive]
564pub struct AuthCertSignatures {
565    /// Contains the actual signature, see [`AuthCertSignatures`].
566    pub dir_key_certification: RsaSha1Signature,
567}
568
569/// RSA signature for data in [`AuthCert`]
570///
571/// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
572///
573/// Compatibility type alias for [`RsaSha1Signature`].
574#[deprecated = "use RsaSha1Signature"]
575pub type AuthCertSignature = RsaSha1Signature;
576
577impl ItemObjectParseable for CrossCertObject {
578    fn check_label(label: &str) -> StdResult<(), parse2::EP> {
579        match label {
580            "SIGNATURE" | "ID SIGNATURE" => Ok(()),
581            _ => Err(parse2::EP::ObjectIncorrectLabel),
582        }
583    }
584
585    fn from_bytes(input: &[u8]) -> StdResult<Self, parse2::EP> {
586        Ok(Self(input.to_vec()))
587    }
588}
589
590impl ItemObjectEncodable for CrossCertObject {
591    fn label(&self) -> &str {
592        "ID SIGNATURE"
593    }
594
595    fn write_object_onto(&self, b: &mut Vec<u8>) -> StdResult<(), Bug> {
596        b.extend(&self.0);
597        Ok(())
598    }
599}
600
601impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
602    type Error = signature::Error;
603
604    fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
605        self.c.dangerously_assume_wellsigned()
606    }
607    fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
608        self.c.is_well_signed()
609    }
610}
611
612impl AuthCertUnverified {
613    /// Verifies the signature of a [`AuthCert`]
614    ///
615    /// # Algorithm
616    ///
617    /// 1. Check whether this comes from a valid authority in `v3idents`.
618    /// 2. Check whether the timestamps are valid (± tolerance).
619    /// 3. Check whether the fingerprint and long-term identity key match.
620    /// 4. Check the cross-certificate (proof-of-ownership of signing key).
621    /// 5. Check the outer certificate (proof-of-ownership of identity key).
622    ///
623    /// TODO: Replace `pre_tolerance` and `post_tolerance` with
624    /// `tor_dircommon::config::DirTolerance` which is not possible at the
625    /// moment due to a circular dependency of `tor-dircommon` depending
626    /// upon `tor-netdoc`.
627    ///
628    /// TODO: Consider whether to try to deduplicate this signature checking
629    /// somehow, wrt to [`UncheckedAuthCert`].
630    pub fn verify(
631        self,
632        v3idents: &[RsaIdentity],
633    ) -> StdResult<TimerangeBound<AuthCert>, parse2::VerifyFailed> {
634        let (body, sigs) = (self.body, self.sigs);
635
636        // (1) Check whether this comes from a valid authority in `v3idents`.
637        if !v3idents.contains(&body.fingerprint.0) {
638            return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
639        }
640
641        // (2) Check whether the timestamps are valid (± tolerance).
642        let validity = *body.dir_key_published..=*body.dir_key_expires;
643
644        // (3) Check whether the fingerprint and long-term identity key match.
645        if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
646            return Err(parse2::VerifyFailed::Inconsistent);
647        }
648
649        // (4) Check the cross-certificate (proof-of-ownership of signing key).
650        body.dir_signing_key.verify(
651            body.fingerprint.0.as_bytes(),
652            &body.dir_key_crosscert.signature,
653        )?;
654
655        // (5) Check the outer certificate (proof-of-ownership of identity key).
656        body.dir_identity_key.verify(
657            &sigs.hashes.0.ok_or(parse2::VerifyFailed::Bug)?,
658            &sigs.sigs.dir_key_certification.signature,
659        )?;
660
661        Ok(TimerangeBound::new(body, validity))
662    }
663
664    /// Verify the signatures (and check validity times)
665    ///
666    /// The pre and post tolerance (time check allowances) used are both zero.
667    ///
668    /// # Security considerations
669    ///
670    /// The caller must check that the KP_auth_id is correct/relevant.
671    pub fn verify_selfcert(self, now: SystemTime) -> StdResult<AuthCert, parse2::VerifyFailed> {
672        let h_kp_auth_id_rsa = self.inspect_unverified().0.fingerprint.0;
673        Ok(self.verify(&[h_kp_auth_id_rsa])?.check_valid_at(&now)?)
674    }
675}
676
677impl AuthCert {
678    /// Make the base for a new `AuthCert`
679    ///
680    /// This contains only the mandatory fields (the ones in `AuthCertConstructor`).
681    /// This method is an alternative to providing a `AuthCertConstructor` value display,
682    /// and is convenient because an authcert contains much recapitulated information.
683    ///
684    /// # Example
685    ///
686    /// ```no_run
687    /// # fn main() -> Result<(), anyhow::Error> {
688    /// use tor_netdoc::doc::authcert::AuthCert;
689    /// let (k_auth_id_rsa, k_auth_sign_rsa, published, expires) = todo!();
690    /// let authcert = AuthCert {
691    ///     dir_address: Some("192.0.2.17:7000".parse()?),
692    ///     ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
693    /// };
694    /// # Ok(())
695    /// # }
696    /// ```
697    pub fn new_base(
698        k_auth_id_rsa: &rsa::KeyPair,
699        k_auth_sign_rsa: &rsa::KeyPair,
700        published: SystemTime,
701        expires: SystemTime,
702    ) -> StdResult<Self, Bug> {
703        let fingerprint = k_auth_id_rsa.to_public_key().to_rsa_identity();
704        let dir_key_crosscert = CrossCert::new(k_auth_sign_rsa, &fingerprint)?;
705
706        let base = AuthCertConstructor {
707            fingerprint: fingerprint.into(),
708            dir_key_published: published.into(),
709            dir_key_expires: expires.into(),
710            dir_identity_key: k_auth_id_rsa.to_public_key(),
711            dir_signing_key: k_auth_sign_rsa.to_public_key(),
712            dir_key_crosscert,
713        }
714        .construct();
715
716        Ok(base)
717    }
718
719    /// Encode this `AuthCert` and sign it with `k_auth_id_rsa`
720    ///
721    /// Yields the string representation of the signed, encoded, document,
722    /// as an [`EncodedAuthCert`].
723    // TODO these features are quite tangled
724    // `EncodedAuthCert` is only available with `parse2` and `plain-consensus`
725    #[cfg(feature = "incomplete")] // Needs EncodedAuthCert
726    pub fn encode_sign(&self, k_auth_id_rsa: &rsa::KeyPair) -> StdResult<EncodedAuthCert, Bug> {
727        let mut encoder = NetdocEncoder::new();
728        self.encode_unsigned(&mut encoder)?;
729
730        let signature =
731            RsaSha1Signature::new_sign_netdoc(k_auth_id_rsa, &encoder, "dir-key-certification")?;
732        let sigs = AuthCertSignatures {
733            dir_key_certification: signature,
734        };
735        sigs.encode_unsigned(&mut encoder)?;
736
737        let encoded = encoder.finish()?;
738        // This rechecks the invariants which ought to be true by construction.
739        // That is convenient for the code here, and the perf implications are irrelevant.
740        let encoded = encoded
741            .try_into()
742            .map_err(into_internal!("generated broken authcert"))?;
743        Ok(encoded)
744    }
745}
746
747#[cfg(test)]
748mod test {
749    // @@ begin test lint list maintained by maint/add_warning @@
750    #![allow(clippy::bool_assert_comparison)]
751    #![allow(clippy::clone_on_copy)]
752    #![allow(clippy::dbg_macro)]
753    #![allow(clippy::mixed_attributes_style)]
754    #![allow(clippy::print_stderr)]
755    #![allow(clippy::print_stdout)]
756    #![allow(clippy::single_char_pattern)]
757    #![allow(clippy::unwrap_used)]
758    #![allow(clippy::unchecked_time_subtraction)]
759    #![allow(clippy::useless_vec)]
760    #![allow(clippy::needless_pass_by_value)]
761    #![allow(clippy::string_slice)] // See arti#2571
762    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
763    use super::*;
764    use crate::{
765        Pos,
766        parse2::{ErrorProblem, ParseError, ParseInput, VerifyFailed, parse_netdoc},
767        types,
768    };
769    use humantime::parse_rfc3339;
770    use std::result::Result;
771    use std::{
772        fs,
773        net::{Ipv4Addr, SocketAddrV4},
774        str::FromStr,
775        time::Duration,
776    };
777    use tor_basic_utils::test_rng;
778
779    const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
780
781    fn bad_data(fname: &str) -> String {
782        use std::fs;
783        use std::path::PathBuf;
784        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
785        path.push("testdata");
786        path.push("bad-certs");
787        path.push(fname);
788
789        fs::read_to_string(path).unwrap()
790    }
791
792    #[test]
793    fn parse_one() -> crate::Result<()> {
794        use tor_checkable::{SelfSigned, Timebound};
795        let cert = AuthCert::parse(TESTDATA)?
796            .check_signature()
797            .unwrap()
798            .dangerously_assume_timely();
799
800        // Taken from TESTDATA
801        assert_eq!(
802            cert.id_fingerprint().to_string(),
803            "$ed03bb616eb2f60bec80151114bb25cef515b226"
804        );
805        assert_eq!(
806            cert.key_ids().sk_fingerprint.to_string(),
807            "$c4f720e2c59f9ddd4867fff465ca04031e35648f"
808        );
809
810        Ok(())
811    }
812
813    #[test]
814    fn parse_bad() {
815        fn check(fname: &str, err: &crate::Error) {
816            let contents = bad_data(fname);
817            let cert = AuthCert::parse(&contents);
818            assert!(cert.is_err());
819            assert_eq!(&cert.err().unwrap(), err);
820        }
821
822        check(
823            "bad-cc-tag",
824            &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
825        );
826        check(
827            "bad-fingerprint",
828            &EK::BadArgument
829                .at_pos(Pos::from_line(2, 1))
830                .with_msg("fingerprint does not match RSA identity"),
831        );
832        check(
833            "bad-version",
834            &EK::BadDocumentVersion.with_msg("unexpected version 4"),
835        );
836        check(
837            "wrong-end",
838            &EK::WrongEndingToken
839                .with_msg("dir-key-crosscert")
840                .at_pos(Pos::from_line(37, 1)),
841        );
842        check(
843            "wrong-start",
844            &EK::WrongStartingToken
845                .with_msg("fingerprint")
846                .at_pos(Pos::from_line(1, 1)),
847        );
848    }
849
850    #[test]
851    fn test_recovery_1() {
852        let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
853        data += TESTDATA;
854
855        let res: Vec<crate::Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
856
857        // We should recover from the failed case and read the next data fine.
858        assert!(res[0].is_err());
859        assert!(res[1].is_ok());
860        assert_eq!(res.len(), 2);
861    }
862
863    #[test]
864    fn test_recovery_2() {
865        let mut data = bad_data("bad-version");
866        data += TESTDATA;
867
868        let res: Vec<crate::Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
869
870        // We should recover from the failed case and read the next data fine.
871        assert!(res[0].is_err());
872        assert!(res[1].is_ok());
873        assert_eq!(res.len(), 2);
874    }
875
876    // === AUTHCERT D190BF3B00E311A9AEB6D62B51980E9B2109BAD1 ===
877    // These values come from testdata2/keys/authority_certificate.
878    const DIR_KEY_PUBLISHED: &str = "2000-01-01 00:00:05";
879    const DIR_KEY_EXPIRES: &str = "2001-01-01 00:00:05";
880    const FINGERPRINT: &str = "D190BF3B00E311A9AEB6D62B51980E9B2109BAD1";
881    const DIR_ADDRESS: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 7100);
882    const DIR_IDENTITY_KEY: &str = "
883-----BEGIN RSA PUBLIC KEY-----
884MIIBigKCAYEAt0rXD+1gYwKFAxrO4uNHQ9dQVUOGx5FxkioYNSct5Z3JU00dTKNJ
885jt4OGkFYwixWwk6KLDOiB+I/q9YIdA1NlQ5R3Hz8jjvFPVl0JQQm2LYzdSzv7/CZ
886U1qq5rYeeoYKx8qMQg4q3WgR251GEnOG+rVqzFSs0oyC+SDfYn9iMt00/pmN3HXf
887wmasY6BescVrYoDbnpkwKATizd4lzx5K8V8aXUXtd8qnYzSyHLlhiO1eufVX07YC
888+AVHV7W7qCTY/4I5Sm0dQ9jF/r04JBHnpH+aae48JOjWDCZj9AINi3rCKS8XClGb
889BB/LJidoQAZraQEEtu3Ql1mjdLreeyWfXpfZFvwKuYn44FtQsOT2TVAVNqNF8N4v
890yfwfiPN6FQWlPyMCEB81HerCn03Zi5WgQLGo7PAeO4LFrLrU16DUC5/oJENeHs0T
89127FZQyrlf0rAxiHh7TJKcjLmzeyxCQVQlr2AXXs28gKHV0AQnEcdrVOpTrquSCQQ
892hWBehR+ct4OJAgMBAAE=
893-----END RSA PUBLIC KEY-----
894    ";
895    const DIR_SIGNING_KEY: &str = "
896-----BEGIN RSA PUBLIC KEY-----
897MIIBCgKCAQEAtPF94+bThLI28kn6e+MmUECMMJ5UBlnQ+Mvwn8Zd85awPQTDz5Wu
89813sZDN3nWnhgSuP5q/WDYc5GPPtQdSWBiG1nJA2XLgEHTHf29iGZ+jAoGfIMJvBV
8991xN8baTnsha5LGx5BQ4UqzlUmoaPzwbjehnPd00FgVkpcCvKZu1HU7fGMVwn4MMh
900zuxJTqTgfcuFWTEu0H0ukOFX+51ih6WO3GWYqRiqgU0Q5/Ets8ccCTq7ND9d2u1P
901d7kQzUHbVP0KmYGK4qYntGDfP4g9SmpBoUUHyP3j9en9S6PMYv8m1YFO7M7JKu6Q
902dQZfGTxj9C/0b/jRklgn5JlKAl9eJQvCdwIDAQAB
903-----END RSA PUBLIC KEY-----
904";
905    const DIR_CROSS_CERT_OBJECT: &str = "
906-----BEGIN ID SIGNATURE-----
907NBaPdBNCNMah6cklrALzj0RdHymF/jPGOv9NmeqaXc0uTN06S/BlVM/xTjilu+dj
908sjPuT0BQL4/ZWyZR+R+gJJojKYILSId4IQ1elzRSxpFN+u2u/ZEmS6SR2SwpA05A
909btOYBKAmYkY6rLsTCbXGx3lAH2kAXfcrltCNKZXV6gqW7X379fiOnSId1OWhKPe1
910/1p3pQGZxgb8FOT1kpHxOMRBClF9Ulm3d9fQZr80Wn73gZ2Bp1RXn9c7c/71HD1c
911mzMT023bleZ574az+117yNAr6XbIgqQfzbySzVLPXM8ZN9BrGR40KDZ2638ZJjRu
9128HK5TzuknWlkRv3hCyRX+g==
913-----END ID SIGNATURE-----
914";
915    const AUTHCERT_RAW: &str = include_str!("../../testdata2/keys/authority_certificate");
916    /// A system time in the range of [`DIR_KEY_PUBLISHED`] and [`DIR_KEY_EXPIRES`].
917    ///
918    /// Constructed by ourselves to have a time point we can use for testing
919    /// timestamp verification.
920    const VALID_SYSTEM_TIME: &str = "2000-06-01 00:00:00";
921
922    // === AUTHCERT 0B8997614EC647C1C6B6A044E2B5408F0B823FB0 ===
923    // This values come from ../../testdata2/cached-certs--1
924    // A different authority certificate different from the one above.
925    const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
926
927    /// Converts a string in the [`Iso8601TimeSp`] format to [`SystemTime`].
928    ///
929    /// This functions panics in the case the input is malformatted.
930    fn to_system_time(s: &str) -> SystemTime {
931        Iso8601TimeSp::from_str(s).unwrap().0
932    }
933
934    /// Converts a PEM encoded RSA Public key to an [`rsa::PublicKey`].
935    ///
936    /// This function panics in the case the input is malformatted.
937    fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
938        rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
939    }
940
941    /// Converts a hex-encoded RSA identity to an [`RsaIdentity`].
942    ///
943    /// This function panics in the case the input is malformatted.
944    fn to_rsa_id(s: &str) -> RsaIdentity {
945        RsaIdentity::from_hex(s).unwrap()
946    }
947
948    /// Tests whether a [`DirKeyCrossCert`] can be parsed properly.
949    #[test]
950    fn dir_auth_cross_cert() {
951        #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
952        #[derive_deftly(NetdocParseable)]
953        struct Dummy {
954            dir_key_crosscert: CrossCert,
955        }
956
957        // "Encodes" a DIR_CROSS_CERT_OBJECT by simply removing the lines
958        // indicating the BEGIN and END, as the purpose is to test multiple
959        // labels.
960        let encoded = DIR_CROSS_CERT_OBJECT
961            .lines()
962            .filter(|line| !line.starts_with("-----"))
963            .collect::<Vec<_>>()
964            .join("\n");
965        let decoded = pem::parse(DIR_CROSS_CERT_OBJECT)
966            .unwrap()
967            .contents()
968            .to_vec();
969
970        // Try with `SIGNATURE`.
971        let cert = format!(
972            "dir-key-crosscert\n-----BEGIN SIGNATURE-----\n{encoded}\n-----END SIGNATURE-----"
973        );
974        let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
975        assert_eq!(
976            res,
977            Dummy {
978                dir_key_crosscert: CrossCert {
979                    signature: CrossCertObject(decoded.clone())
980                }
981            }
982        );
983
984        // Try with `ID SIGNATURE`.
985        let cert = format!(
986            "dir-key-crosscert\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
987        );
988        let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
989        assert_eq!(
990            res,
991            Dummy {
992                dir_key_crosscert: CrossCert {
993                    signature: CrossCertObject(decoded.clone())
994                }
995            }
996        );
997
998        // Try with different label and fail.
999        let cert =
1000            format!("dir-key-crosscert\n-----BEGIN WHAT-----\n{encoded}\n-----END WHAT-----");
1001        let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
1002        match res {
1003            Err(ParseError {
1004                problem: ErrorProblem::ObjectIncorrectLabel,
1005                doctype: "dir-key-crosscert",
1006                file: _,
1007                lno: 1,
1008                column: None,
1009            }) => {}
1010            other => panic!("not expected error {other:#?}"),
1011        }
1012
1013        // Try with extra args.
1014        let cert = format!(
1015            "dir-key-crosscert arg1\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
1016        );
1017        let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
1018        match res {
1019            Err(ParseError {
1020                problem: ErrorProblem::UnexpectedArgument { column: 19 },
1021                doctype: "dir-key-crosscert",
1022                file: _,
1023                lno: 1,
1024                column: Some(19),
1025            }) => {}
1026            other => panic!("not expected error {other:#?}"),
1027        }
1028    }
1029
1030    #[test]
1031    fn dir_auth_cert() {
1032        let res =
1033            parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1034        assert_eq!(
1035            *res.inspect_unverified().0,
1036            AuthCert {
1037                dir_key_certificate_version: AuthCertVersion::V3,
1038                dir_address: Some(DIR_ADDRESS),
1039                fingerprint: types::Fingerprint(to_rsa_id(FINGERPRINT)),
1040                dir_key_published: Iso8601TimeSp(to_system_time(DIR_KEY_PUBLISHED)),
1041                dir_key_expires: Iso8601TimeSp(to_system_time(DIR_KEY_EXPIRES)),
1042                dir_identity_key: pem_to_rsa_pk(DIR_IDENTITY_KEY),
1043                dir_signing_key: pem_to_rsa_pk(DIR_SIGNING_KEY),
1044                dir_key_crosscert: CrossCert {
1045                    signature: CrossCertObject(
1046                        pem::parse(DIR_CROSS_CERT_OBJECT)
1047                            .unwrap()
1048                            .contents()
1049                            .to_vec()
1050                    )
1051                },
1052                __non_exhaustive: (),
1053            }
1054        );
1055    }
1056
1057    #[test]
1058    fn dir_auth_signature() {
1059        let res =
1060            parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1061
1062        // Test a valid signature.
1063        let _: AuthCert = res
1064            .clone()
1065            .verify(&[to_rsa_id(FINGERPRINT)])
1066            .unwrap()
1067            .check_valid_at(&to_system_time(VALID_SYSTEM_TIME))
1068            .unwrap();
1069
1070        // Test with an invalid authority.
1071        assert_eq!(
1072            res.clone().verify(&[],).unwrap_err(),
1073            VerifyFailed::InsufficientTrustedSigners
1074        );
1075
1076        // Test a key too far in the future.
1077        assert_eq!(
1078            res.clone()
1079                .verify(&[to_rsa_id(FINGERPRINT)],)
1080                .unwrap()
1081                .check_valid_at(&SystemTime::UNIX_EPOCH,)
1082                .map_err(VerifyFailed::from)
1083                .unwrap_err(),
1084            VerifyFailed::TooNew
1085        );
1086
1087        // Test an almost too new.
1088        let _: AuthCert = res
1089            .clone()
1090            .verify(&[to_rsa_id(FINGERPRINT)])
1091            .unwrap()
1092            .check_valid_at(&to_system_time(DIR_KEY_PUBLISHED))
1093            .unwrap();
1094
1095        // Now fail when we are 1s below ...
1096        assert_eq!(
1097            res.clone()
1098                .verify(&[to_rsa_id(FINGERPRINT)],)
1099                .unwrap()
1100                .check_valid_at(&(to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1)),)
1101                .map_err(VerifyFailed::from)
1102                .unwrap_err(),
1103            VerifyFailed::TooNew
1104        );
1105
1106        // ... but succeed again with a clock skew tolerance.
1107        let _: AuthCert = res
1108            .clone()
1109            .verify(&[to_rsa_id(FINGERPRINT)])
1110            .unwrap()
1111            .extend_pre_tolerance(Duration::from_secs(1))
1112            .check_valid_at(&(to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1)))
1113            .unwrap();
1114
1115        // Test a key too old.
1116        assert_eq!(
1117            res.clone()
1118                .verify(&[to_rsa_id(FINGERPRINT)],)
1119                .unwrap()
1120                .check_valid_at(
1121                    &SystemTime::UNIX_EPOCH
1122                        .checked_add(Duration::from_secs(2000000000))
1123                        .unwrap(),
1124                )
1125                .map_err(VerifyFailed::from)
1126                .unwrap_err(),
1127            VerifyFailed::TooOld
1128        );
1129
1130        // Test an almost too old.
1131        let _: AuthCert = res
1132            .clone()
1133            .verify(&[to_rsa_id(FINGERPRINT)])
1134            .unwrap()
1135            .check_valid_at(&to_system_time(DIR_KEY_EXPIRES))
1136            .unwrap();
1137
1138        // Now fail when we are 1s above ...
1139        assert_eq!(
1140            res.clone()
1141                .verify(&[to_rsa_id(FINGERPRINT)],)
1142                .unwrap()
1143                .check_valid_at(&(to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1)),)
1144                .map_err(VerifyFailed::from)
1145                .unwrap_err(),
1146            VerifyFailed::TooOld
1147        );
1148
1149        // ... but succeed again with a clock skew tolerance.
1150        let _: AuthCert = res
1151            .clone()
1152            .verify(&[to_rsa_id(FINGERPRINT)])
1153            .unwrap()
1154            .extend_tolerance(Duration::from_secs(1))
1155            .check_valid_at(&(to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1)))
1156            .unwrap();
1157
1158        // Check with non-matching fingerprint and long-term identity key.
1159        let mut cert =
1160            parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1161        let alternative_cert = parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(
1162            ALTERNATIVE_AUTHCERT_RAW,
1163            "",
1164        ))
1165        .unwrap();
1166        cert.body.dir_identity_key = alternative_cert.body.dir_identity_key.clone();
1167        assert_eq!(
1168            cert.verify(&[to_rsa_id(FINGERPRINT)],).unwrap_err(),
1169            VerifyFailed::Inconsistent
1170        );
1171
1172        // Check invalid cross-cert.
1173        let mut cert =
1174            parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1175        cert.body.dir_key_crosscert = alternative_cert.body.dir_key_crosscert.clone();
1176        assert_eq!(
1177            cert.verify(&[to_rsa_id(FINGERPRINT)],).unwrap_err(),
1178            VerifyFailed::VerifyFailed
1179        );
1180
1181        // Check outer signature.
1182        let mut cert =
1183            parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, "")).unwrap();
1184        cert.sigs = alternative_cert.sigs.clone();
1185        assert_eq!(
1186            cert.verify(&[to_rsa_id(FINGERPRINT)],).unwrap_err(),
1187            VerifyFailed::VerifyFailed
1188        );
1189    }
1190
1191    #[test]
1192    fn keyids_for_directory_signature() -> anyhow::Result<()> {
1193        #[derive(Deftly)]
1194        #[derive_deftly(NetdocEncodable, NetdocParseable)]
1195        struct Doc {
1196            intro: (),
1197            ids: Item,
1198        }
1199        #[derive(Deftly)]
1200        #[derive_deftly(ItemValueEncodable, ItemValueParseable)]
1201        struct Item {
1202            #[deftly(netdoc(with = keyids_directory_signature_args))]
1203            ids: AuthCertKeyIds,
1204        }
1205
1206        let text = r#"intro
1207ids 1234567812345678123456781234567812345678 ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
1208"#;
1209        let doc = parse2::parse_netdoc::<Doc>(&ParseInput::new(text, "<text>"))?;
1210        let mut re_encode = NetdocEncoder::new();
1211        doc.encode_unsigned(&mut re_encode)?;
1212        let re_encode = re_encode.finish()?;
1213
1214        assert_eq_or_diff!(text, re_encode);
1215        Ok(())
1216    }
1217
1218    #[test]
1219    #[cfg(feature = "incomplete")]
1220    fn roundtrip() -> Result<(), anyhow::Error> {
1221        let mut rng = test_rng::testing_rng();
1222        let k_auth_id_rsa = rsa::KeyPair::generate(&mut rng)?;
1223        let k_auth_sign_rsa = rsa::KeyPair::generate(&mut rng)?;
1224
1225        let secs = |s| Duration::from_secs(s);
1226        let now = parse_rfc3339("1993-01-01T00:00:00Z")?;
1227        let published = now - secs(1000);
1228        let expires = published + secs(86400);
1229        let tolerance = secs(10);
1230
1231        let input_value = AuthCert {
1232            dir_address: Some("192.0.2.17:7000".parse()?),
1233            ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
1234        };
1235        dbg!(&input_value);
1236
1237        let encoded = input_value.encode_sign(&k_auth_id_rsa)?;
1238
1239        let reparsed_uv: AuthCertUnverified =
1240            parse_netdoc(&ParseInput::new(encoded.as_ref(), "<encoded>"))?;
1241        let reparsed_value = reparsed_uv
1242            .verify(&[k_auth_id_rsa.to_public_key().to_rsa_identity()])?
1243            .extend_pre_tolerance(tolerance)
1244            .extend_tolerance(tolerance)
1245            .check_valid_at(&now)?;
1246        dbg!(&reparsed_value);
1247
1248        assert_eq!(input_value, reparsed_value);
1249        Ok(())
1250    }
1251
1252    #[cfg(feature = "incomplete")]
1253    #[test]
1254    fn parse_authcert() -> anyhow::Result<()> {
1255        let file = "testdata2/cached-certs--1";
1256        let now = parse_rfc3339("2000-06-01T00:00:05Z")?;
1257        let text = fs::read_to_string(file)?;
1258        let input = ParseInput::new(&text, file);
1259        let doc: AuthCertUnverified = parse_netdoc(&input)?;
1260        let doc = doc.verify_selfcert(now)?;
1261        println!("{doc:?}");
1262        assert_eq!(
1263            doc.fingerprint.0.to_string(),
1264            "$0b8997614ec647c1c6b6a044e2b5408f0b823fb0",
1265        );
1266        Ok(())
1267    }
1268}