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