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

            
9
use crate::batching_split_before::IteratorExt as _;
10
use crate::encode::{Bug, ItemObjectEncodable, NetdocEncodable, NetdocEncoder};
11
use crate::parse::keyword::Keyword;
12
use crate::parse::parser::{Section, SectionRules};
13
use crate::parse::tokenize::{ItemResult, NetDocReader};
14
use crate::parse2::{
15
    self, ItemObjectParseable, NetdocUnverified as _, sig_hashes::Sha1WholeKeywordLine,
16
};
17
use crate::types::misc::{Fingerprint, Iso8601TimeSp, RsaPublicParse1Helper, RsaSha1Signature};
18
use crate::util::str::Extent;
19
use crate::{NetdocErrorKind as EK, NormalItemArgument, Result};
20

            
21
use tor_basic_utils::impl_debug_hex;
22
use tor_checkable::{signed, timed};
23
use tor_error::into_internal;
24
use tor_llcrypto::pk::rsa;
25
use tor_llcrypto::{d, pk, pk::rsa::RsaIdentity};
26

            
27
use std::sync::LazyLock;
28

            
29
use std::result::Result as StdResult;
30
use std::{net, time, time::Duration, time::SystemTime};
31

            
32
use derive_deftly::Deftly;
33
use digest::Digest;
34

            
35
#[cfg(feature = "build_docs")]
36
mod build;
37

            
38
#[cfg(feature = "build_docs")]
39
#[allow(deprecated)]
40
pub use build::AuthCertBuilder;
41

            
42
#[cfg(all(feature = "plain-consensus", feature = "incomplete"))]
43
mod encoded;
44
#[cfg(all(feature = "plain-consensus", feature = "incomplete"))]
45
pub use encoded::EncodedAuthCert;
46

            
47
decl_keyword! {
48
    pub(crate) AuthCertKwd {
49
        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
50
        "dir-address" => DIR_ADDRESS,
51
        "fingerprint" => FINGERPRINT,
52
        "dir-identity-key" => DIR_IDENTITY_KEY,
53
        "dir-key-published" => DIR_KEY_PUBLISHED,
54
        "dir-key-expires" => DIR_KEY_EXPIRES,
55
        "dir-signing-key" => DIR_SIGNING_KEY,
56
        "dir-key-crosscert" => DIR_KEY_CROSSCERT,
57
        "dir-key-certification" => DIR_KEY_CERTIFICATION,
58
    }
59
}
60

            
61
/// Rules about entries that must appear in an AuthCert, and how they must
62
/// be formed.
63
55
static AUTHCERT_RULES: LazyLock<SectionRules<AuthCertKwd>> = LazyLock::new(|| {
64
    use AuthCertKwd::*;
65

            
66
55
    let mut rules = SectionRules::builder();
67
55
    rules.add(DIR_KEY_CERTIFICATE_VERSION.rule().required().args(1..));
68
55
    rules.add(DIR_ADDRESS.rule().args(1..));
69
55
    rules.add(FINGERPRINT.rule().required().args(1..));
70
55
    rules.add(DIR_IDENTITY_KEY.rule().required().no_args().obj_required());
71
55
    rules.add(DIR_SIGNING_KEY.rule().required().no_args().obj_required());
72
55
    rules.add(DIR_KEY_PUBLISHED.rule().required());
73
55
    rules.add(DIR_KEY_EXPIRES.rule().required());
74
55
    rules.add(DIR_KEY_CROSSCERT.rule().required().no_args().obj_required());
75
55
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
76
55
    rules.add(
77
55
        DIR_KEY_CERTIFICATION
78
55
            .rule()
79
55
            .required()
80
55
            .no_args()
81
55
            .obj_required(),
82
    );
83
55
    rules.build()
84
55
});
85

            
86
/// A single directory authority key certificate
87
///
88
/// This is the body, not including signatures.
89
///
90
/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html>
91
///
92
/// To make a fresh `AuthCert`, use [`AuthCertConstructor`].
93
#[derive(Clone, Debug, Deftly)]
94
#[derive_deftly(Constructor)]
95
#[derive_deftly(NetdocParseableUnverified, NetdocEncodable)]
96
#[cfg_attr(test, derive(PartialEq, Eq))]
97
#[allow(clippy::exhaustive_structs)]
98
pub struct AuthCert {
99
    /// Intro line
100
    ///
101
    /// Currently must be version 3.
102
    ///
103
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
104
    #[deftly(constructor(default = AuthCertVersion::V3))]
105
    #[deftly(netdoc(single_arg))]
106
    pub dir_key_certificate_version: AuthCertVersion,
107

            
108
    /// An IPv4 address for this authority.
109
    #[deftly(netdoc(single_arg))]
110
    pub dir_address: Option<net::SocketAddrV4>,
111

            
112
    /// H(KP_auth_id_rsa)
113
    ///
114
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:fingerprint>
115
    #[deftly(constructor)]
116
    #[deftly(netdoc(single_arg))]
117
    pub fingerprint: Fingerprint,
118

            
119
    /// Declared time when this certificate was published
120
    ///
121
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-published>
122
    #[deftly(constructor)]
123
    #[deftly(netdoc(single_arg))]
124
    pub dir_key_published: Iso8601TimeSp,
125

            
126
    /// Declared time when this certificate expires.
127
    ///
128
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-expires>
129
    #[deftly(constructor)]
130
    #[deftly(netdoc(single_arg))]
131
    pub dir_key_expires: Iso8601TimeSp,
132

            
133
    /// KP_auth_id_rsa
134
    ///
135
    /// The long-term RSA identity key for this authority
136
    ///
137
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-identity-key>
138
    #[deftly(constructor)]
139
    pub dir_identity_key: rsa::PublicKey,
140

            
141
    /// KP_auth_sign_rsa
142
    ///
143
    /// The medium-term RSA signing key for this authority
144
    ///
145
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-signing-key>
146
    #[deftly(constructor)]
147
    pub dir_signing_key: rsa::PublicKey,
148

            
149
    /// SHA1(DER(KP_auth_id_rsa)) signed by KP_auth_sign_rsa
150
    ///
151
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
152
    #[deftly(constructor)]
153
    pub dir_key_crosscert: CrossCert,
154

            
155
    #[doc(hidden)]
156
    #[deftly(netdoc(skip))]
157
    pub __non_exhaustive: (),
158
}
159

            
160
/// Represents the version of an [`AuthCert`].
161
///
162
/// Single argument.
163
///
164
/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
165
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
166
#[non_exhaustive]
167
pub enum AuthCertVersion {
168
    /// The current and only version understood.
169
    #[strum(serialize = "3")]
170
    V3,
171
}
172

            
173
impl NormalItemArgument for AuthCertVersion {}
174

            
175
/// A pair of key identities that identifies a certificate.
176
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
177
#[allow(clippy::exhaustive_structs)]
178
pub struct AuthCertKeyIds {
179
    /// Fingerprint of identity key
180
    pub id_fingerprint: rsa::RsaIdentity,
181
    /// Fingerprint of signing key
182
    pub sk_fingerprint: rsa::RsaIdentity,
183
}
184

            
185
/// An authority certificate whose signature and validity time we
186
/// haven't checked.
187
pub struct UncheckedAuthCert {
188
    /// Where we found this AuthCert within the string containing it.
189
    location: Option<Extent>,
190

            
191
    /// The actual unchecked certificate.
192
    c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
193
}
194

            
195
impl UncheckedAuthCert {
196
    /// If this AuthCert was originally parsed from `haystack`, return its
197
    /// text.
198
    ///
199
    /// TODO: This is a pretty bogus interface; there should be a
200
    /// better way to remember where to look for this thing if we want
201
    /// it without keeping the input alive forever.  We should
202
    /// refactor.
203
159
    pub fn within<'a>(&self, haystack: &'a str) -> Option<&'a str> {
204
159
        self.location
205
159
            .as_ref()
206
162
            .and_then(|ext| ext.reconstruct(haystack))
207
159
    }
208
}
209

            
210
impl AuthCert {
211
    /// Make an [`AuthCertBuilder`] object that can be used to
212
    /// construct authority certificates for testing.
213
    #[cfg(feature = "build_docs")]
214
    #[deprecated = "use AuthCertConstructor instead"]
215
    #[allow(deprecated)]
216
10
    pub fn builder() -> AuthCertBuilder {
217
10
        AuthCertBuilder::new()
218
10
    }
219

            
220
    /// Parse an authority certificate from a string.
221
    ///
222
    /// This function verifies the certificate's signatures, but doesn't
223
    /// check its expiration dates.
224
67
    pub fn parse(s: &str) -> Result<UncheckedAuthCert> {
225
67
        let mut reader = NetDocReader::new(s)?;
226
67
        let body = AUTHCERT_RULES.parse(&mut reader)?;
227
67
        reader.should_be_exhausted()?;
228
72
        AuthCert::from_body(&body, s).map_err(|e| e.within(s))
229
67
    }
230

            
231
    /// Return an iterator yielding authority certificates from a string.
232
114
    pub fn parse_multiple(s: &str) -> Result<impl Iterator<Item = Result<UncheckedAuthCert>> + '_> {
233
        use AuthCertKwd::*;
234
114
        let sections = NetDocReader::new(s)?
235
322
            .batching_split_before_loose(|item| item.is_ok_with_kwd(DIR_KEY_CERTIFICATE_VERSION));
236
114
        Ok(sections
237
134
            .map(|mut section| {
238
26
                let body = AUTHCERT_RULES.parse(&mut section)?;
239
24
                AuthCert::from_body(&body, s)
240
26
            })
241
134
            .map(|r| r.map_err(|e| e.within(s))))
242
114
    }
243
    /*
244
        /// Return true if this certificate is expired at a given time, or
245
        /// not yet valid at that time.
246
        pub fn is_expired_at(&self, when: time::SystemTime) -> bool {
247
            when < self.published || when > self.expires
248
        }
249
    */
250
    /// Return the signing key certified by this certificate.
251
122
    pub fn signing_key(&self) -> &rsa::PublicKey {
252
122
        &self.dir_signing_key
253
122
    }
254

            
255
    /// Return an AuthCertKeyIds object describing the keys in this
256
    /// certificate.
257
1070
    pub fn key_ids(&self) -> AuthCertKeyIds {
258
1070
        AuthCertKeyIds {
259
1070
            id_fingerprint: self.fingerprint.0,
260
1070
            sk_fingerprint: self.dir_signing_key.to_rsa_identity(),
261
1070
        }
262
1070
    }
263

            
264
    /// Return an RsaIdentity for this certificate's identity key.
265
16
    pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
266
16
        &self.fingerprint
267
16
    }
268

            
269
    /// Return the time when this certificate says it was published.
270
55
    pub fn published(&self) -> time::SystemTime {
271
55
        *self.dir_key_published
272
55
    }
273

            
274
    /// Return the time when this certificate says it should expire.
275
55
    pub fn expires(&self) -> time::SystemTime {
276
55
        *self.dir_key_expires
277
55
    }
278

            
279
    /// Parse an authority certificate from a reader.
280
193
    fn from_body(body: &Section<'_, AuthCertKwd>, s: &str) -> Result<UncheckedAuthCert> {
281
        use AuthCertKwd::*;
282

            
283
        // Make sure first and last element are correct types.  We can
284
        // safely call unwrap() on first and last, since there are required
285
        // tokens in the rules, so we know that at least one token will have
286
        // been parsed.
287
191
        let start_pos = {
288
            // Unwrap should be safe because `.parse()` would have already
289
            // returned an Error
290
            #[allow(clippy::unwrap_used)]
291
193
            let first_item = body.first_item().unwrap();
292
193
            if first_item.kwd() != DIR_KEY_CERTIFICATE_VERSION {
293
2
                return Err(EK::WrongStartingToken
294
2
                    .with_msg(first_item.kwd_str().to_string())
295
2
                    .at_pos(first_item.pos()));
296
191
            }
297
191
            first_item.pos()
298
        };
299
189
        let end_pos = {
300
            // Unwrap should be safe because `.parse()` would have already
301
            // returned an Error
302
            #[allow(clippy::unwrap_used)]
303
191
            let last_item = body.last_item().unwrap();
304
191
            if last_item.kwd() != DIR_KEY_CERTIFICATION {
305
2
                return Err(EK::WrongEndingToken
306
2
                    .with_msg(last_item.kwd_str().to_string())
307
2
                    .at_pos(last_item.pos()));
308
189
            }
309
189
            last_item.end_pos()
310
        };
311

            
312
189
        let version = body
313
189
            .required(DIR_KEY_CERTIFICATE_VERSION)?
314
189
            .parse_arg::<u32>(0)?;
315
189
        if version != 3 {
316
4
            return Err(EK::BadDocumentVersion.with_msg(format!("unexpected version {}", version)));
317
185
        }
318
185
        let dir_key_certificate_version = AuthCertVersion::V3;
319

            
320
185
        let dir_signing_key: rsa::PublicKey = body
321
185
            .required(DIR_SIGNING_KEY)?
322
185
            .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
323
185
            .check_len(1024..)?
324
185
            .check_exponent(65537)?
325
185
            .into();
326

            
327
185
        let dir_identity_key: rsa::PublicKey = body
328
185
            .required(DIR_IDENTITY_KEY)?
329
185
            .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
330
185
            .check_len(1024..)?
331
185
            .check_exponent(65537)?
332
185
            .into();
333

            
334
185
        let dir_key_published = body
335
185
            .required(DIR_KEY_PUBLISHED)?
336
185
            .args_as_str()
337
185
            .parse::<Iso8601TimeSp>()?;
338

            
339
185
        let dir_key_expires = body
340
185
            .required(DIR_KEY_EXPIRES)?
341
185
            .args_as_str()
342
185
            .parse::<Iso8601TimeSp>()?;
343

            
344
        {
345
            // Check fingerprint for consistency with key.
346
185
            let fp_tok = body.required(FINGERPRINT)?;
347
185
            let fingerprint: RsaIdentity = fp_tok.args_as_str().parse::<Fingerprint>()?.into();
348
185
            if fingerprint != dir_identity_key.to_rsa_identity() {
349
2
                return Err(EK::BadArgument
350
2
                    .at_pos(fp_tok.pos())
351
2
                    .with_msg("fingerprint does not match RSA identity"));
352
183
            }
353
        }
354

            
355
183
        let dir_address = body
356
183
            .maybe(DIR_ADDRESS)
357
183
            .parse_args_as_str::<net::SocketAddrV4>()?;
358

            
359
        // check crosscert
360
        let dir_key_crosscert;
361
181
        let v_crosscert = {
362
183
            let crosscert = body.required(DIR_KEY_CROSSCERT)?;
363
            // Unwrap should be safe because `.parse()` and `required()` would
364
            // have already returned an Error
365
            #[allow(clippy::unwrap_used)]
366
183
            let mut tag = crosscert.obj_tag().unwrap();
367
            // we are required to support both.
368
183
            if tag != "ID SIGNATURE" && tag != "SIGNATURE" {
369
2
                tag = "ID SIGNATURE";
370
181
            }
371
183
            let sig = crosscert.obj(tag)?;
372

            
373
181
            let signed = dir_identity_key.to_rsa_identity();
374
            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
375

            
376
181
            let v = rsa::ValidatableRsaSignature::new(&dir_signing_key, &sig, signed.as_bytes());
377

            
378
181
            dir_key_crosscert = CrossCert {
379
181
                signature: CrossCertObject(sig),
380
181
            };
381

            
382
181
            v
383
        };
384

            
385
        // check the signature
386
181
        let v_sig = {
387
181
            let signature = body.required(DIR_KEY_CERTIFICATION)?;
388
181
            let sig = signature.obj("SIGNATURE")?;
389

            
390
181
            let mut sha1 = d::Sha1::new();
391
            // Unwrap should be safe because `.parse()` would have already
392
            // returned an Error
393
            #[allow(clippy::unwrap_used)]
394
181
            let start_offset = body.first_item().unwrap().offset_in(s).unwrap();
395
            #[allow(clippy::unwrap_used)]
396
181
            let end_offset = body.last_item().unwrap().offset_in(s).unwrap();
397
181
            let end_offset = end_offset + "dir-key-certification\n".len();
398
181
            sha1.update(&s[start_offset..end_offset]);
399
181
            let sha1 = sha1.finalize();
400
            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
401

            
402
181
            rsa::ValidatableRsaSignature::new(&dir_identity_key, &sig, &sha1)
403
        };
404

            
405
181
        let id_fingerprint = dir_identity_key.to_rsa_identity();
406

            
407
181
        let location = {
408
181
            let start_idx = start_pos.offset_within(s);
409
181
            let end_idx = end_pos.offset_within(s);
410
181
            match (start_idx, end_idx) {
411
181
                (Some(a), Some(b)) => Extent::new(s, &s[a..b + 1]),
412
                _ => None,
413
            }
414
        };
415

            
416
181
        let authcert = AuthCert {
417
181
            dir_key_certificate_version,
418
181
            dir_address,
419
181
            dir_identity_key,
420
181
            dir_signing_key,
421
181
            dir_key_published,
422
181
            dir_key_expires,
423
181
            dir_key_crosscert,
424
181
            fingerprint: Fingerprint(id_fingerprint),
425
181
            __non_exhaustive: (),
426
181
        };
427

            
428
181
        let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
429
181
            vec![Box::new(v_crosscert), Box::new(v_sig)];
430

            
431
181
        let timed = timed::TimerangeBound::new(authcert, *dir_key_published..*dir_key_expires);
432
181
        let signed = signed::SignatureGated::new(timed, signatures);
433
181
        let unchecked = UncheckedAuthCert {
434
181
            location,
435
181
            c: signed,
436
181
        };
437
181
        Ok(unchecked)
438
193
    }
439
}
440

            
441
/// Pseudo-Signature of the long-term identity key by the medium-term key.
442
///
443
/// This type does not implement `SignatureItemParseable` because that trait
444
/// is reserved for signatures on *netdocs*, such as [`AuthCertSignature`].
445
/// As `CrossCert` does not sign a full document, it implements only
446
/// `ItemValueParseable`, instead.
447
///
448
/// Verification of this signature is done in `AuthCertUnverified::verify`,
449
/// and during parsing by the old parser.
450
/// So a `CrossCert` in [`AuthCert::dir_key_crosscert`] in a bare `AuthCert` has been validated.
451
//
452
// TODO SPEC (Diziet): it is far from clear to me that this cert serves any useful purpose.
453
// However, we are far too busy now with rewriting the universe to consider transitioning it away.
454
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
455
#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
456
#[deftly(netdoc(no_extra_args))]
457
#[non_exhaustive]
458
pub struct CrossCert {
459
    /// The bytes of the signature (base64-decoded).
460
    #[deftly(netdoc(object))]
461
    pub signature: CrossCertObject,
462
}
463

            
464
/// Wrapper around [`Vec<u8>`] implementing [`ItemObjectParseable`] properly.
465
///
466
/// Unfortunately, this wrapper is necessary, because the specification
467
/// demands that these certificate objects must accept two labels:
468
/// `SIGNATURE` and `ID SIGNATURE`.  Because the deftly template for
469
/// `ItemValueParseable` only allows for a single label
470
/// (`#[deftly(netdoc(object(label = "LABEL")))]`), we must implement this
471
/// trait ourselves in order to allow multiple ones.
472
///
473
/// TODO: In the future, it might be nice to let the respective fmeta
474
/// accept a pattern, as pattern matching would allow trivially for one
475
/// to infinity different combinations.
476
/// TODO SPEC: Alternatively we could abolish the wrong labels,
477
/// or we could abolish Objects completely and just have long lines.
478
///
479
/// # Specifications
480
///
481
/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
482
#[derive(Clone, PartialEq, Eq, derive_more::Deref)]
483
#[non_exhaustive]
484
pub struct CrossCertObject(pub Vec<u8>);
485
impl_debug_hex! { CrossCertObject . 0 }
486

            
487
impl CrossCert {
488
    /// Make a `CrossCert`
489
2
    pub fn new(
490
2
        k_auth_sign_rsa: &rsa::KeyPair,
491
2
        h_kp_auth_id_rsa: &RsaIdentity,
492
2
    ) -> StdResult<Self, Bug> {
493
2
        let signature = k_auth_sign_rsa
494
2
            .sign(h_kp_auth_id_rsa.as_bytes())
495
2
            .map_err(into_internal!("failed to sign cross-cert"))?;
496
2
        Ok(CrossCert {
497
2
            signature: CrossCertObject(signature),
498
2
        })
499
2
    }
500
}
501

            
502
/// Signatures for [`AuthCert`]
503
///
504
/// Signed by [`AuthCert::dir_identity_key`] in order to prove ownership.
505
/// Can be seen as the opposite of [`AuthCert::dir_key_crosscert`].
506
///
507
/// # Specifications
508
///
509
/// * <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certification>
510
/// * <https://spec.torproject.org/dir-spec/netdoc.html#signing>
511
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
512
#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
513
#[deftly(netdoc(signatures(hashes_accu = Sha1WholeKeywordLine)))]
514
#[non_exhaustive]
515
pub struct AuthCertSignatures {
516
    /// Contains the actual signature, see [`AuthCertSignatures`].
517
    pub dir_key_certification: RsaSha1Signature,
518
}
519

            
520
/// RSA signature for data in [`AuthCert`]
521
///
522
/// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
523
///
524
/// Compatibility type alias for [`RsaSha1Signature`].
525
#[deprecated = "use RsaSha1Signature"]
526
pub type AuthCertSignature = RsaSha1Signature;
527

            
528
impl ItemObjectParseable for CrossCertObject {
529
933
    fn check_label(label: &str) -> StdResult<(), parse2::EP> {
530
933
        match label {
531
933
            "SIGNATURE" | "ID SIGNATURE" => Ok(()),
532
2
            _ => Err(parse2::EP::ObjectIncorrectLabel),
533
        }
534
933
    }
535

            
536
931
    fn from_bytes(input: &[u8]) -> StdResult<Self, parse2::EP> {
537
931
        Ok(Self(input.to_vec()))
538
931
    }
539
}
540

            
541
impl ItemObjectEncodable for CrossCertObject {
542
2
    fn label(&self) -> &str {
543
2
        "ID SIGNATURE"
544
2
    }
545

            
546
2
    fn write_object_onto(&self, b: &mut Vec<u8>) -> StdResult<(), Bug> {
547
2
        b.extend(&self.0);
548
2
        Ok(())
549
2
    }
550
}
551

            
552
impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
553
    type Error = signature::Error;
554

            
555
175
    fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
556
175
        self.c.dangerously_assume_wellsigned()
557
175
    }
558
175
    fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
559
175
        self.c.is_well_signed()
560
175
    }
561
}
562

            
563
impl AuthCertUnverified {
564
    /// Verifies the signature of a [`AuthCert`]
565
    ///
566
    /// # Algorithm
567
    ///
568
    /// 1. Check whether this comes from a valid authority in `v3idents`.
569
    /// 2. Check whether the timestamps are valid (± tolerance).
570
    /// 3. Check whether the fingerprint and long-term identity key match.
571
    /// 4. Check the cross-certificate (proof-of-ownership of signing key).
572
    /// 5. Check the outer certificate (proof-of-ownership of identity key).
573
    ///
574
    /// TODO: Replace `pre_tolerance` and `post_tolerance` with
575
    /// `tor_dircommon::config::DirTolerance` which is not possible at the
576
    /// moment due to a circular dependency of `tor-dircommon` depending
577
    /// upon `tor-netdoc`.
578
    ///
579
    /// TODO: Consider whether to try to deduplicate this signature checking
580
    /// somehow, wrt to [`UncheckedAuthCert`].
581
515
    pub fn verify(
582
515
        self,
583
515
        v3idents: &[RsaIdentity],
584
515
        pre_tolerance: Duration,
585
515
        post_tolerance: Duration,
586
515
        now: SystemTime,
587
515
    ) -> StdResult<AuthCert, parse2::VerifyFailed> {
588
515
        let (body, sigs) = (self.body, self.sigs);
589

            
590
        // (1) Check whether this comes from a valid authority in `v3idents`.
591
515
        if !v3idents.contains(&body.fingerprint.0) {
592
2
            return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
593
513
        }
594

            
595
        // (2) Check whether the timestamps are valid (± tolerance).
596
513
        let validity = *body.dir_key_published..=*body.dir_key_expires;
597
513
        parse2::check_validity_time_tolerance(now, validity, pre_tolerance, post_tolerance)?;
598

            
599
        // (3) Check whether the fingerprint and long-term identity key match.
600
505
        if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
601
2
            return Err(parse2::VerifyFailed::Inconsistent);
602
503
        }
603

            
604
        // (4) Check the cross-certificate (proof-of-ownership of signing key).
605
503
        body.dir_signing_key.verify(
606
503
            body.fingerprint.0.as_bytes(),
607
503
            &body.dir_key_crosscert.signature,
608
2
        )?;
609

            
610
        // (5) Check the outer certificate (proof-of-ownership of identity key).
611
501
        body.dir_identity_key.verify(
612
501
            &sigs.hashes.0.ok_or(parse2::VerifyFailed::Bug)?,
613
501
            &sigs.sigs.dir_key_certification.signature,
614
2
        )?;
615

            
616
499
        Ok(body)
617
515
    }
618

            
619
    /// Verify the signatures (and check validity times)
620
    ///
621
    /// The pre and post tolerance (time check allowances) used are both zero.
622
    ///
623
    /// # Security considerations
624
    ///
625
    /// The caller must check that the KP_auth_id is correct/relevant.
626
10
    pub fn verify_selfcert(self, now: SystemTime) -> StdResult<AuthCert, parse2::VerifyFailed> {
627
10
        let h_kp_auth_id_rsa = self.inspect_unverified().0.fingerprint.0;
628
10
        self.verify(&[h_kp_auth_id_rsa], Duration::ZERO, Duration::ZERO, now)
629
10
    }
630
}
631

            
632
impl AuthCert {
633
    /// Make the base for a new `AuthCert`
634
    ///
635
    /// This contains only the mandatory fields (the ones in `AuthCertConstructor`).
636
    /// This method is an alternative to providing a `AuthCertConstructor` value display,
637
    /// and is convenient because an authcert contains much recapitulated information.
638
    ///
639
    /// # Example
640
    ///
641
    /// ```no_run
642
    /// # fn main() -> Result<(), anyhow::Error> {
643
    /// use tor_netdoc::doc::authcert::AuthCert;
644
    /// let (k_auth_id_rsa, k_auth_sign_rsa, published, expires) = todo!();
645
    /// let authcert = AuthCert {
646
    ///     dir_address: Some("192.0.2.17:7000".parse()?),
647
    ///     ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
648
    /// };
649
    /// # Ok(())
650
    /// # }
651
    /// ```
652
2
    pub fn new_base(
653
2
        k_auth_id_rsa: &rsa::KeyPair,
654
2
        k_auth_sign_rsa: &rsa::KeyPair,
655
2
        published: SystemTime,
656
2
        expires: SystemTime,
657
2
    ) -> StdResult<Self, Bug> {
658
2
        let fingerprint = k_auth_id_rsa.to_public_key().to_rsa_identity();
659
2
        let dir_key_crosscert = CrossCert::new(k_auth_sign_rsa, &fingerprint)?;
660

            
661
2
        let base = AuthCertConstructor {
662
2
            fingerprint: fingerprint.into(),
663
2
            dir_key_published: published.into(),
664
2
            dir_key_expires: expires.into(),
665
2
            dir_identity_key: k_auth_id_rsa.to_public_key(),
666
2
            dir_signing_key: k_auth_sign_rsa.to_public_key(),
667
2
            dir_key_crosscert,
668
2
        }
669
2
        .construct();
670

            
671
2
        Ok(base)
672
2
    }
673

            
674
    /// Encode this `AuthCert` and sign it with `k_auth_id_rsa`
675
    ///
676
    /// Yields the string representation of the signed, encoded, document,
677
    /// as an [`EncodedAuthCert`].
678
    // TODO these features are quite tangled
679
    // `EncodedAuthCert` is only available with `parse2` and `plain-consensus`
680
    #[cfg(all(feature = "plain-consensus", feature = "incomplete"))]
681
2
    pub fn encode_sign(&self, k_auth_id_rsa: &rsa::KeyPair) -> StdResult<EncodedAuthCert, Bug> {
682
2
        let mut encoder = NetdocEncoder::new();
683
2
        self.encode_unsigned(&mut encoder)?;
684

            
685
2
        let signature =
686
2
            RsaSha1Signature::new_sign_netdoc(k_auth_id_rsa, &encoder, "dir-key-certification")?;
687
2
        let sigs = AuthCertSignatures {
688
2
            dir_key_certification: signature,
689
2
        };
690
2
        sigs.encode_unsigned(&mut encoder)?;
691

            
692
2
        let encoded = encoder.finish()?;
693
        // This rechecks the invariants which ought to be true by construction.
694
        // That is convenient for the code here, and the perf implications are irrelevant.
695
2
        let encoded = encoded
696
2
            .try_into()
697
2
            .map_err(into_internal!("generated broken authcert"))?;
698
2
        Ok(encoded)
699
2
    }
700
}
701

            
702
#[cfg(test)]
703
mod test {
704
    // @@ begin test lint list maintained by maint/add_warning @@
705
    #![allow(clippy::bool_assert_comparison)]
706
    #![allow(clippy::clone_on_copy)]
707
    #![allow(clippy::dbg_macro)]
708
    #![allow(clippy::mixed_attributes_style)]
709
    #![allow(clippy::print_stderr)]
710
    #![allow(clippy::print_stdout)]
711
    #![allow(clippy::single_char_pattern)]
712
    #![allow(clippy::unwrap_used)]
713
    #![allow(clippy::unchecked_time_subtraction)]
714
    #![allow(clippy::useless_vec)]
715
    #![allow(clippy::needless_pass_by_value)]
716
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
717
    use super::*;
718
    use crate::{Error, Pos};
719
    const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
720

            
721
    fn bad_data(fname: &str) -> String {
722
        use std::fs;
723
        use std::path::PathBuf;
724
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
725
        path.push("testdata");
726
        path.push("bad-certs");
727
        path.push(fname);
728

            
729
        fs::read_to_string(path).unwrap()
730
    }
731

            
732
    #[test]
733
    fn parse_one() -> Result<()> {
734
        use tor_checkable::{SelfSigned, Timebound};
735
        let cert = AuthCert::parse(TESTDATA)?
736
            .check_signature()
737
            .unwrap()
738
            .dangerously_assume_timely();
739

            
740
        // Taken from TESTDATA
741
        assert_eq!(
742
            cert.id_fingerprint().to_string(),
743
            "$ed03bb616eb2f60bec80151114bb25cef515b226"
744
        );
745
        assert_eq!(
746
            cert.key_ids().sk_fingerprint.to_string(),
747
            "$c4f720e2c59f9ddd4867fff465ca04031e35648f"
748
        );
749

            
750
        Ok(())
751
    }
752

            
753
    #[test]
754
    fn parse_bad() {
755
        fn check(fname: &str, err: &Error) {
756
            let contents = bad_data(fname);
757
            let cert = AuthCert::parse(&contents);
758
            assert!(cert.is_err());
759
            assert_eq!(&cert.err().unwrap(), err);
760
        }
761

            
762
        check(
763
            "bad-cc-tag",
764
            &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
765
        );
766
        check(
767
            "bad-fingerprint",
768
            &EK::BadArgument
769
                .at_pos(Pos::from_line(2, 1))
770
                .with_msg("fingerprint does not match RSA identity"),
771
        );
772
        check(
773
            "bad-version",
774
            &EK::BadDocumentVersion.with_msg("unexpected version 4"),
775
        );
776
        check(
777
            "wrong-end",
778
            &EK::WrongEndingToken
779
                .with_msg("dir-key-crosscert")
780
                .at_pos(Pos::from_line(37, 1)),
781
        );
782
        check(
783
            "wrong-start",
784
            &EK::WrongStartingToken
785
                .with_msg("fingerprint")
786
                .at_pos(Pos::from_line(1, 1)),
787
        );
788
    }
789

            
790
    #[test]
791
    fn test_recovery_1() {
792
        let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
793
        data += TESTDATA;
794

            
795
        let res: Vec<Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
796

            
797
        // We should recover from the failed case and read the next data fine.
798
        assert!(res[0].is_err());
799
        assert!(res[1].is_ok());
800
        assert_eq!(res.len(), 2);
801
    }
802

            
803
    #[test]
804
    fn test_recovery_2() {
805
        let mut data = bad_data("bad-version");
806
        data += TESTDATA;
807

            
808
        let res: Vec<Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
809

            
810
        // We should recover from the failed case and read the next data fine.
811
        assert!(res[0].is_err());
812
        assert!(res[1].is_ok());
813
        assert_eq!(res.len(), 2);
814
    }
815

            
816
    mod parse2_test {
817
        use super::{AuthCert, AuthCertUnverified, AuthCertVersion, CrossCert, CrossCertObject};
818

            
819
        use std::{
820
            net::{Ipv4Addr, SocketAddrV4},
821
            str::FromStr,
822
            time::{Duration, SystemTime},
823
        };
824

            
825
        use crate::{
826
            parse2::{self, ErrorProblem, NetdocUnverified, ParseError, ParseInput, VerifyFailed},
827
            types::{self, Iso8601TimeSp},
828
        };
829

            
830
        use derive_deftly::Deftly;
831
        use tor_llcrypto::pk::rsa::{self, RsaIdentity};
832

            
833
        // === AUTHCERT D190BF3B00E311A9AEB6D62B51980E9B2109BAD1 ===
834
        // These values come from testdata2/keys/authority_certificate.
835
        const DIR_KEY_PUBLISHED: &str = "2000-01-01 00:00:05";
836
        const DIR_KEY_EXPIRES: &str = "2001-01-01 00:00:05";
837
        const FINGERPRINT: &str = "D190BF3B00E311A9AEB6D62B51980E9B2109BAD1";
838
        const DIR_ADDRESS: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 7100);
839
        const DIR_IDENTITY_KEY: &str = "
840
-----BEGIN RSA PUBLIC KEY-----
841
MIIBigKCAYEAt0rXD+1gYwKFAxrO4uNHQ9dQVUOGx5FxkioYNSct5Z3JU00dTKNJ
842
jt4OGkFYwixWwk6KLDOiB+I/q9YIdA1NlQ5R3Hz8jjvFPVl0JQQm2LYzdSzv7/CZ
843
U1qq5rYeeoYKx8qMQg4q3WgR251GEnOG+rVqzFSs0oyC+SDfYn9iMt00/pmN3HXf
844
wmasY6BescVrYoDbnpkwKATizd4lzx5K8V8aXUXtd8qnYzSyHLlhiO1eufVX07YC
845
+AVHV7W7qCTY/4I5Sm0dQ9jF/r04JBHnpH+aae48JOjWDCZj9AINi3rCKS8XClGb
846
BB/LJidoQAZraQEEtu3Ql1mjdLreeyWfXpfZFvwKuYn44FtQsOT2TVAVNqNF8N4v
847
yfwfiPN6FQWlPyMCEB81HerCn03Zi5WgQLGo7PAeO4LFrLrU16DUC5/oJENeHs0T
848
27FZQyrlf0rAxiHh7TJKcjLmzeyxCQVQlr2AXXs28gKHV0AQnEcdrVOpTrquSCQQ
849
hWBehR+ct4OJAgMBAAE=
850
-----END RSA PUBLIC KEY-----
851
    ";
852
        const DIR_SIGNING_KEY: &str = "
853
-----BEGIN RSA PUBLIC KEY-----
854
MIIBCgKCAQEAtPF94+bThLI28kn6e+MmUECMMJ5UBlnQ+Mvwn8Zd85awPQTDz5Wu
855
13sZDN3nWnhgSuP5q/WDYc5GPPtQdSWBiG1nJA2XLgEHTHf29iGZ+jAoGfIMJvBV
856
1xN8baTnsha5LGx5BQ4UqzlUmoaPzwbjehnPd00FgVkpcCvKZu1HU7fGMVwn4MMh
857
zuxJTqTgfcuFWTEu0H0ukOFX+51ih6WO3GWYqRiqgU0Q5/Ets8ccCTq7ND9d2u1P
858
d7kQzUHbVP0KmYGK4qYntGDfP4g9SmpBoUUHyP3j9en9S6PMYv8m1YFO7M7JKu6Q
859
dQZfGTxj9C/0b/jRklgn5JlKAl9eJQvCdwIDAQAB
860
-----END RSA PUBLIC KEY-----
861
";
862
        const DIR_CROSS_CERT_OBJECT: &str = "
863
-----BEGIN ID SIGNATURE-----
864
NBaPdBNCNMah6cklrALzj0RdHymF/jPGOv9NmeqaXc0uTN06S/BlVM/xTjilu+dj
865
sjPuT0BQL4/ZWyZR+R+gJJojKYILSId4IQ1elzRSxpFN+u2u/ZEmS6SR2SwpA05A
866
btOYBKAmYkY6rLsTCbXGx3lAH2kAXfcrltCNKZXV6gqW7X379fiOnSId1OWhKPe1
867
/1p3pQGZxgb8FOT1kpHxOMRBClF9Ulm3d9fQZr80Wn73gZ2Bp1RXn9c7c/71HD1c
868
mzMT023bleZ574az+117yNAr6XbIgqQfzbySzVLPXM8ZN9BrGR40KDZ2638ZJjRu
869
8HK5TzuknWlkRv3hCyRX+g==
870
-----END ID SIGNATURE-----
871
";
872
        const AUTHCERT_RAW: &str = include_str!("../../testdata2/keys/authority_certificate");
873
        /// A system time in the range of [`DIR_KEY_PUBLISHED`] and [`DIR_KEY_EXPIRES`].
874
        ///
875
        /// Constructed by ourselves to have a time point we can use for testing
876
        /// timestamp verification.
877
        const VALID_SYSTEM_TIME: &str = "2000-06-01 00:00:00";
878

            
879
        // === AUTHCERT 0B8997614EC647C1C6B6A044E2B5408F0B823FB0 ===
880
        // This values come from ../../testdata2/cached-certs--1
881
        // A different authority certificate different from the one above.
882
        const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
883

            
884
        /// Converts a string in the [`Iso8601TimeSp`] format to [`SystemTime`].
885
        ///
886
        /// This functions panics in the case the input is malformatted.
887
        fn to_system_time(s: &str) -> SystemTime {
888
            Iso8601TimeSp::from_str(s).unwrap().0
889
        }
890

            
891
        /// Converts a PEM encoded RSA Public key to an [`rsa::PublicKey`].
892
        ///
893
        /// This function panics in the case the input is malformatted.
894
        fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
895
            rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
896
        }
897

            
898
        /// Converts a hex-encoded RSA identity to an [`RsaIdentity`].
899
        ///
900
        /// This function panics in the case the input is malformatted.
901
        fn to_rsa_id(s: &str) -> RsaIdentity {
902
            RsaIdentity::from_hex(s).unwrap()
903
        }
904

            
905
        /// Tests whether a [`DirKeyCrossCert`] can be parsed properly.
906
        #[test]
907
        fn dir_auth_cross_cert() {
908
            #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
909
            #[derive_deftly(NetdocParseable)]
910
            struct Dummy {
911
                dir_key_crosscert: CrossCert,
912
            }
913

            
914
            // "Encodes" a DIR_CROSS_CERT_OBJECT by simply removing the lines
915
            // indicating the BEGIN and END, as the purpose is to test multiple
916
            // labels.
917
            let encoded = DIR_CROSS_CERT_OBJECT
918
                .lines()
919
                .filter(|line| !line.starts_with("-----"))
920
                .collect::<Vec<_>>()
921
                .join("\n");
922
            let decoded = pem::parse(DIR_CROSS_CERT_OBJECT)
923
                .unwrap()
924
                .contents()
925
                .to_vec();
926

            
927
            // Try with `SIGNATURE`.
928
            let cert = format!(
929
                "dir-key-crosscert\n-----BEGIN SIGNATURE-----\n{encoded}\n-----END SIGNATURE-----"
930
            );
931
            let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
932
            assert_eq!(
933
                res,
934
                Dummy {
935
                    dir_key_crosscert: CrossCert {
936
                        signature: CrossCertObject(decoded.clone())
937
                    }
938
                }
939
            );
940

            
941
            // Try with `ID SIGNATURE`.
942
            let cert = format!(
943
                "dir-key-crosscert\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
944
            );
945
            let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
946
            assert_eq!(
947
                res,
948
                Dummy {
949
                    dir_key_crosscert: CrossCert {
950
                        signature: CrossCertObject(decoded.clone())
951
                    }
952
                }
953
            );
954

            
955
            // Try with different label and fail.
956
            let cert =
957
                format!("dir-key-crosscert\n-----BEGIN WHAT-----\n{encoded}\n-----END WHAT-----");
958
            let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
959
            match res {
960
                Err(ParseError {
961
                    problem: ErrorProblem::ObjectIncorrectLabel,
962
                    doctype: "dir-key-crosscert",
963
                    file: _,
964
                    lno: 1,
965
                    column: None,
966
                }) => {}
967
                other => panic!("not expected error {other:#?}"),
968
            }
969

            
970
            // Try with extra args.
971
            let cert = format!(
972
                "dir-key-crosscert arg1\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
973
            );
974
            let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
975
            match res {
976
                Err(ParseError {
977
                    problem: ErrorProblem::UnexpectedArgument { column: 19 },
978
                    doctype: "dir-key-crosscert",
979
                    file: _,
980
                    lno: 1,
981
                    column: Some(19),
982
                }) => {}
983
                other => panic!("not expected error {other:#?}"),
984
            }
985
        }
986

            
987
        #[test]
988
        fn dir_auth_cert() {
989
            let res =
990
                parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
991
                    .unwrap();
992
            assert_eq!(
993
                *res.inspect_unverified().0,
994
                AuthCert {
995
                    dir_key_certificate_version: AuthCertVersion::V3,
996
                    dir_address: Some(DIR_ADDRESS),
997
                    fingerprint: types::Fingerprint(to_rsa_id(FINGERPRINT)),
998
                    dir_key_published: Iso8601TimeSp(to_system_time(DIR_KEY_PUBLISHED)),
999
                    dir_key_expires: Iso8601TimeSp(to_system_time(DIR_KEY_EXPIRES)),
                    dir_identity_key: pem_to_rsa_pk(DIR_IDENTITY_KEY),
                    dir_signing_key: pem_to_rsa_pk(DIR_SIGNING_KEY),
                    dir_key_crosscert: CrossCert {
                        signature: CrossCertObject(
                            pem::parse(DIR_CROSS_CERT_OBJECT)
                                .unwrap()
                                .contents()
                                .to_vec()
                        )
                    },
                    __non_exhaustive: (),
                }
            );
        }
        #[test]
        fn dir_auth_signature() {
            let res =
                parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
                    .unwrap();
            // Test a valid signature.
            res.clone()
                .verify(
                    &[to_rsa_id(FINGERPRINT)],
                    Duration::ZERO,
                    Duration::ZERO,
                    to_system_time(VALID_SYSTEM_TIME),
                )
                .unwrap();
            // Test with an invalid authority.
            assert_eq!(
                res.clone()
                    .verify(
                        &[],
                        Duration::ZERO,
                        Duration::ZERO,
                        to_system_time(VALID_SYSTEM_TIME),
                    )
                    .unwrap_err(),
                VerifyFailed::InsufficientTrustedSigners
            );
            // Test a key too far in the future.
            assert_eq!(
                res.clone()
                    .verify(
                        &[to_rsa_id(FINGERPRINT)],
                        Duration::ZERO,
                        Duration::ZERO,
                        SystemTime::UNIX_EPOCH,
                    )
                    .unwrap_err(),
                VerifyFailed::TooNew
            );
            // Test an almost too new.
            res.clone()
                .verify(
                    &[to_rsa_id(FINGERPRINT)],
                    Duration::ZERO,
                    Duration::ZERO,
                    to_system_time(DIR_KEY_PUBLISHED),
                )
                .unwrap();
            // Now fail when we are 1s below ...
            assert_eq!(
                res.clone()
                    .verify(
                        &[to_rsa_id(FINGERPRINT)],
                        Duration::ZERO,
                        Duration::ZERO,
                        to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1),
                    )
                    .unwrap_err(),
                VerifyFailed::TooNew
            );
            // ... but succeed again with a clock skew tolerance.
            res.clone()
                .verify(
                    &[to_rsa_id(FINGERPRINT)],
                    Duration::from_secs(1),
                    Duration::ZERO,
                    to_system_time(DIR_KEY_PUBLISHED) - Duration::from_secs(1),
                )
                .unwrap();
            // Test a key too old.
            assert_eq!(
                res.clone()
                    .verify(
                        &[to_rsa_id(FINGERPRINT)],
                        Duration::ZERO,
                        Duration::ZERO,
                        SystemTime::UNIX_EPOCH
                            .checked_add(Duration::from_secs(2000000000))
                            .unwrap(),
                    )
                    .unwrap_err(),
                VerifyFailed::TooOld
            );
            // Test an almost too old.
            res.clone()
                .verify(
                    &[to_rsa_id(FINGERPRINT)],
                    Duration::ZERO,
                    Duration::ZERO,
                    to_system_time(DIR_KEY_EXPIRES),
                )
                .unwrap();
            // Now fail when we are 1s above ...
            assert_eq!(
                res.clone()
                    .verify(
                        &[to_rsa_id(FINGERPRINT)],
                        Duration::ZERO,
                        Duration::ZERO,
                        to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1),
                    )
                    .unwrap_err(),
                VerifyFailed::TooOld
            );
            // ... but succeed again with a clock skew tolerance.
            res.clone()
                .verify(
                    &[to_rsa_id(FINGERPRINT)],
                    Duration::ZERO,
                    Duration::from_secs(1),
                    to_system_time(DIR_KEY_EXPIRES) + Duration::from_secs(1),
                )
                .unwrap();
            // Check with non-matching fingerprint and long-term identity key.
            let mut cert =
                parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
                    .unwrap();
            let alternative_cert = parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(
                ALTERNATIVE_AUTHCERT_RAW,
                "",
            ))
            .unwrap();
            cert.body.dir_identity_key = alternative_cert.body.dir_identity_key.clone();
            assert_eq!(
                cert.verify(
                    &[to_rsa_id(FINGERPRINT)],
                    Duration::ZERO,
                    Duration::ZERO,
                    to_system_time(VALID_SYSTEM_TIME),
                )
                .unwrap_err(),
                VerifyFailed::Inconsistent
            );
            // Check invalid cross-cert.
            let mut cert =
                parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
                    .unwrap();
            cert.body.dir_key_crosscert = alternative_cert.body.dir_key_crosscert.clone();
            assert_eq!(
                cert.verify(
                    &[to_rsa_id(FINGERPRINT)],
                    Duration::ZERO,
                    Duration::ZERO,
                    to_system_time(VALID_SYSTEM_TIME),
                )
                .unwrap_err(),
                VerifyFailed::VerifyFailed
            );
            // Check outer signature.
            let mut cert =
                parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
                    .unwrap();
            cert.sigs = alternative_cert.sigs.clone();
            assert_eq!(
                cert.verify(
                    &[to_rsa_id(FINGERPRINT)],
                    Duration::ZERO,
                    Duration::ZERO,
                    to_system_time(VALID_SYSTEM_TIME),
                )
                .unwrap_err(),
                VerifyFailed::VerifyFailed
            );
        }
    }
    #[cfg(all(feature = "plain-consensus", feature = "incomplete"))]
    mod encode_test {
        use super::*;
        use crate::parse2::{ParseInput, parse_netdoc};
        use humantime::parse_rfc3339;
        use std::result::Result;
        use tor_basic_utils::test_rng;
        #[test]
        fn roundtrip() -> Result<(), anyhow::Error> {
            let mut rng = test_rng::testing_rng();
            let k_auth_id_rsa = rsa::KeyPair::generate(&mut rng)?;
            let k_auth_sign_rsa = rsa::KeyPair::generate(&mut rng)?;
            let secs = |s| Duration::from_secs(s);
            let now = parse_rfc3339("1993-01-01T00:00:00Z")?;
            let published = now - secs(1000);
            let expires = published + secs(86400);
            let tolerance = secs(10);
            let input_value = AuthCert {
                dir_address: Some("192.0.2.17:7000".parse()?),
                ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
            };
            dbg!(&input_value);
            let encoded = input_value.encode_sign(&k_auth_id_rsa)?;
            let reparsed_uv: AuthCertUnverified =
                parse_netdoc(&ParseInput::new(encoded.as_ref(), "<encoded>"))?;
            let reparsed_value = reparsed_uv.verify(
                &[k_auth_id_rsa.to_public_key().to_rsa_identity()],
                tolerance,
                tolerance,
                now,
            )?;
            dbg!(&reparsed_value);
            assert_eq!(input_value, reparsed_value);
            Ok(())
        }
    }
}