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::parse::keyword::Keyword;
11
use crate::parse::parser::{Section, SectionRules};
12
use crate::parse::tokenize::{ItemResult, NetDocReader};
13
use crate::types::misc::{Fingerprint, Iso8601TimeSp, RsaPublicParse1Helper, RsaSha1Signature};
14
use crate::util::str::Extent;
15
use crate::{NetdocErrorKind as EK, NormalItemArgument, Result};
16

            
17
use tor_basic_utils::impl_debug_hex;
18
use tor_checkable::{signed, timed};
19
use tor_llcrypto::pk::rsa;
20
use tor_llcrypto::{d, pk, pk::rsa::RsaIdentity};
21

            
22
use std::sync::LazyLock;
23

            
24
use std::result::Result as StdResult;
25
use std::{net, time, time::Duration, time::SystemTime};
26

            
27
use derive_deftly::Deftly;
28
use digest::Digest;
29

            
30
#[cfg(feature = "build_docs")]
31
mod build;
32

            
33
#[cfg(feature = "build_docs")]
34
#[allow(deprecated)]
35
pub use build::AuthCertBuilder;
36

            
37
#[cfg(feature = "parse2")]
38
use crate::parse2::{
39
    self, ItemObjectParseable, NetdocUnverified as _, sig_hashes::Sha1WholeKeywordLine,
40
};
41

            
42
#[cfg(feature = "encode")]
43
use {
44
    crate::encode::{Bug, ItemObjectEncodable, NetdocEncodable, NetdocEncoder},
45
    tor_error::into_internal,
46
};
47

            
48
// TODO DIRAUTH untangle these feature(s)
49
#[cfg(all(feature = "parse2", feature = "plain-consensus"))]
50
mod encoded;
51
#[cfg(all(feature = "parse2", feature = "plain-consensus"))]
52
pub use encoded::EncodedAuthCert;
53

            
54
decl_keyword! {
55
    pub(crate) AuthCertKwd {
56
        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
57
        "dir-address" => DIR_ADDRESS,
58
        "fingerprint" => FINGERPRINT,
59
        "dir-identity-key" => DIR_IDENTITY_KEY,
60
        "dir-key-published" => DIR_KEY_PUBLISHED,
61
        "dir-key-expires" => DIR_KEY_EXPIRES,
62
        "dir-signing-key" => DIR_SIGNING_KEY,
63
        "dir-key-crosscert" => DIR_KEY_CROSSCERT,
64
        "dir-key-certification" => DIR_KEY_CERTIFICATION,
65
    }
66
}
67

            
68
/// Rules about entries that must appear in an AuthCert, and how they must
69
/// be formed.
70
55
static AUTHCERT_RULES: LazyLock<SectionRules<AuthCertKwd>> = LazyLock::new(|| {
71
    use AuthCertKwd::*;
72

            
73
55
    let mut rules = SectionRules::builder();
74
55
    rules.add(DIR_KEY_CERTIFICATE_VERSION.rule().required().args(1..));
75
55
    rules.add(DIR_ADDRESS.rule().args(1..));
76
55
    rules.add(FINGERPRINT.rule().required().args(1..));
77
55
    rules.add(DIR_IDENTITY_KEY.rule().required().no_args().obj_required());
78
55
    rules.add(DIR_SIGNING_KEY.rule().required().no_args().obj_required());
79
55
    rules.add(DIR_KEY_PUBLISHED.rule().required());
80
55
    rules.add(DIR_KEY_EXPIRES.rule().required());
81
55
    rules.add(DIR_KEY_CROSSCERT.rule().required().no_args().obj_required());
82
55
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
83
55
    rules.add(
84
55
        DIR_KEY_CERTIFICATION
85
55
            .rule()
86
55
            .required()
87
55
            .no_args()
88
55
            .obj_required(),
89
    );
90
55
    rules.build()
91
55
});
92

            
93
/// A single directory authority key certificate
94
///
95
/// This is the body, not including signatures.
96
///
97
/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html>
98
///
99
/// To make a fresh `AuthCert`, use [`AuthCertConstructor`].
100
#[derive(Clone, Debug, Deftly)]
101
#[derive_deftly(Constructor)]
102
#[cfg_attr(feature = "parse2", derive_deftly(NetdocParseableUnverified))]
103
#[cfg_attr(feature = "encode", derive_deftly(NetdocEncodable))]
104
// derive_deftly_adhoc disables unused deftly attribute checking, so we needn't cfg_attr them all
105
#[cfg_attr(not(any(feature = "parse2", feature = "encode")), derive_deftly_adhoc)]
106
#[cfg_attr(test, derive(PartialEq, Eq))]
107
#[allow(clippy::exhaustive_structs)]
108
pub struct AuthCert {
109
    /// Intro line
110
    ///
111
    /// Currently must be version 3.
112
    ///
113
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
114
    #[deftly(constructor(default = AuthCertVersion::V3))]
115
    #[deftly(netdoc(single_arg))]
116
    pub dir_key_certificate_version: AuthCertVersion,
117

            
118
    /// An IPv4 address for this authority.
119
    #[deftly(netdoc(single_arg))]
120
    pub dir_address: Option<net::SocketAddrV4>,
121

            
122
    /// H(KP_auth_id_rsa)
123
    ///
124
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:fingerprint>
125
    #[deftly(constructor)]
126
    #[deftly(netdoc(single_arg))]
127
    pub fingerprint: Fingerprint,
128

            
129
    /// Declared time when this certificate was published
130
    ///
131
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-published>
132
    #[deftly(constructor)]
133
    #[deftly(netdoc(single_arg))]
134
    pub dir_key_published: Iso8601TimeSp,
135

            
136
    /// Declared time when this certificate expires.
137
    ///
138
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-expires>
139
    #[deftly(constructor)]
140
    #[deftly(netdoc(single_arg))]
141
    pub dir_key_expires: Iso8601TimeSp,
142

            
143
    /// KP_auth_id_rsa
144
    ///
145
    /// The long-term RSA identity key for this authority
146
    ///
147
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-identity-key>
148
    #[deftly(constructor)]
149
    pub dir_identity_key: rsa::PublicKey,
150

            
151
    /// KP_auth_sign_rsa
152
    ///
153
    /// The medium-term RSA signing key for this authority
154
    ///
155
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-signing-key>
156
    #[deftly(constructor)]
157
    pub dir_signing_key: rsa::PublicKey,
158

            
159
    /// SHA1(DER(KP_auth_id_rsa)) signed by KP_auth_sign_rsa
160
    ///
161
    /// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
162
    #[deftly(constructor)]
163
    pub dir_key_crosscert: CrossCert,
164

            
165
    #[doc(hidden)]
166
    #[deftly(netdoc(skip))]
167
    pub __non_exhaustive: (),
168
}
169

            
170
/// Represents the version of an [`AuthCert`].
171
///
172
/// Single argument.
173
///
174
/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certificate-version>
175
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
176
#[non_exhaustive]
177
pub enum AuthCertVersion {
178
    /// The current and only version understood.
179
    #[strum(serialize = "3")]
180
    V3,
181
}
182

            
183
impl NormalItemArgument for AuthCertVersion {}
184

            
185
/// A pair of key identities that identifies a certificate.
186
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
187
#[allow(clippy::exhaustive_structs)]
188
pub struct AuthCertKeyIds {
189
    /// Fingerprint of identity key
190
    pub id_fingerprint: rsa::RsaIdentity,
191
    /// Fingerprint of signing key
192
    pub sk_fingerprint: rsa::RsaIdentity,
193
}
194

            
195
/// An authority certificate whose signature and validity time we
196
/// haven't checked.
197
pub struct UncheckedAuthCert {
198
    /// Where we found this AuthCert within the string containing it.
199
    location: Option<Extent>,
200

            
201
    /// The actual unchecked certificate.
202
    c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
203
}
204

            
205
impl UncheckedAuthCert {
206
    /// If this AuthCert was originally parsed from `haystack`, return its
207
    /// text.
208
    ///
209
    /// TODO: This is a pretty bogus interface; there should be a
210
    /// better way to remember where to look for this thing if we want
211
    /// it without keeping the input alive forever.  We should
212
    /// refactor.
213
159
    pub fn within<'a>(&self, haystack: &'a str) -> Option<&'a str> {
214
159
        self.location
215
159
            .as_ref()
216
162
            .and_then(|ext| ext.reconstruct(haystack))
217
159
    }
218
}
219

            
220
impl AuthCert {
221
    /// Make an [`AuthCertBuilder`] object that can be used to
222
    /// construct authority certificates for testing.
223
    #[cfg(feature = "build_docs")]
224
    #[deprecated = "use AuthCertConstructor instead"]
225
    #[allow(deprecated)]
226
10
    pub fn builder() -> AuthCertBuilder {
227
10
        AuthCertBuilder::new()
228
10
    }
229

            
230
    /// Parse an authority certificate from a string.
231
    ///
232
    /// This function verifies the certificate's signatures, but doesn't
233
    /// check its expiration dates.
234
67
    pub fn parse(s: &str) -> Result<UncheckedAuthCert> {
235
67
        let mut reader = NetDocReader::new(s)?;
236
67
        let body = AUTHCERT_RULES.parse(&mut reader)?;
237
67
        reader.should_be_exhausted()?;
238
72
        AuthCert::from_body(&body, s).map_err(|e| e.within(s))
239
67
    }
240

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

            
265
    /// Return an AuthCertKeyIds object describing the keys in this
266
    /// certificate.
267
1070
    pub fn key_ids(&self) -> AuthCertKeyIds {
268
1070
        AuthCertKeyIds {
269
1070
            id_fingerprint: self.fingerprint.0,
270
1070
            sk_fingerprint: self.dir_signing_key.to_rsa_identity(),
271
1070
        }
272
1070
    }
273

            
274
    /// Return an RsaIdentity for this certificate's identity key.
275
16
    pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
276
16
        &self.fingerprint
277
16
    }
278

            
279
    /// Return the time when this certificate says it was published.
280
55
    pub fn published(&self) -> time::SystemTime {
281
55
        *self.dir_key_published
282
55
    }
283

            
284
    /// Return the time when this certificate says it should expire.
285
55
    pub fn expires(&self) -> time::SystemTime {
286
55
        *self.dir_key_expires
287
55
    }
288

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

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

            
322
189
        let version = body
323
189
            .required(DIR_KEY_CERTIFICATE_VERSION)?
324
189
            .parse_arg::<u32>(0)?;
325
189
        if version != 3 {
326
4
            return Err(EK::BadDocumentVersion.with_msg(format!("unexpected version {}", version)));
327
185
        }
328
185
        let dir_key_certificate_version = AuthCertVersion::V3;
329

            
330
185
        let dir_signing_key: rsa::PublicKey = body
331
185
            .required(DIR_SIGNING_KEY)?
332
185
            .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
333
185
            .check_len(1024..)?
334
185
            .check_exponent(65537)?
335
185
            .into();
336

            
337
185
        let dir_identity_key: rsa::PublicKey = body
338
185
            .required(DIR_IDENTITY_KEY)?
339
185
            .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
340
185
            .check_len(1024..)?
341
185
            .check_exponent(65537)?
342
185
            .into();
343

            
344
185
        let dir_key_published = body
345
185
            .required(DIR_KEY_PUBLISHED)?
346
185
            .args_as_str()
347
185
            .parse::<Iso8601TimeSp>()?;
348

            
349
185
        let dir_key_expires = body
350
185
            .required(DIR_KEY_EXPIRES)?
351
185
            .args_as_str()
352
185
            .parse::<Iso8601TimeSp>()?;
353

            
354
        {
355
            // Check fingerprint for consistency with key.
356
185
            let fp_tok = body.required(FINGERPRINT)?;
357
185
            let fingerprint: RsaIdentity = fp_tok.args_as_str().parse::<Fingerprint>()?.into();
358
185
            if fingerprint != dir_identity_key.to_rsa_identity() {
359
2
                return Err(EK::BadArgument
360
2
                    .at_pos(fp_tok.pos())
361
2
                    .with_msg("fingerprint does not match RSA identity"));
362
183
            }
363
        }
364

            
365
183
        let dir_address = body
366
183
            .maybe(DIR_ADDRESS)
367
183
            .parse_args_as_str::<net::SocketAddrV4>()?;
368

            
369
        // check crosscert
370
        let dir_key_crosscert;
371
181
        let v_crosscert = {
372
183
            let crosscert = body.required(DIR_KEY_CROSSCERT)?;
373
            // Unwrap should be safe because `.parse()` and `required()` would
374
            // have already returned an Error
375
            #[allow(clippy::unwrap_used)]
376
183
            let mut tag = crosscert.obj_tag().unwrap();
377
            // we are required to support both.
378
183
            if tag != "ID SIGNATURE" && tag != "SIGNATURE" {
379
2
                tag = "ID SIGNATURE";
380
181
            }
381
183
            let sig = crosscert.obj(tag)?;
382

            
383
181
            let signed = dir_identity_key.to_rsa_identity();
384
            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
385

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

            
388
181
            dir_key_crosscert = CrossCert {
389
181
                signature: CrossCertObject(sig),
390
181
            };
391

            
392
181
            v
393
        };
394

            
395
        // check the signature
396
181
        let v_sig = {
397
181
            let signature = body.required(DIR_KEY_CERTIFICATION)?;
398
181
            let sig = signature.obj("SIGNATURE")?;
399

            
400
181
            let mut sha1 = d::Sha1::new();
401
            // Unwrap should be safe because `.parse()` would have already
402
            // returned an Error
403
            #[allow(clippy::unwrap_used)]
404
181
            let start_offset = body.first_item().unwrap().offset_in(s).unwrap();
405
            #[allow(clippy::unwrap_used)]
406
181
            let end_offset = body.last_item().unwrap().offset_in(s).unwrap();
407
181
            let end_offset = end_offset + "dir-key-certification\n".len();
408
181
            sha1.update(&s[start_offset..end_offset]);
409
181
            let sha1 = sha1.finalize();
410
            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
411

            
412
181
            rsa::ValidatableRsaSignature::new(&dir_identity_key, &sig, &sha1)
413
        };
414

            
415
181
        let id_fingerprint = dir_identity_key.to_rsa_identity();
416

            
417
181
        let location = {
418
181
            let start_idx = start_pos.offset_within(s);
419
181
            let end_idx = end_pos.offset_within(s);
420
181
            match (start_idx, end_idx) {
421
181
                (Some(a), Some(b)) => Extent::new(s, &s[a..b + 1]),
422
                _ => None,
423
            }
424
        };
425

            
426
181
        let authcert = AuthCert {
427
181
            dir_key_certificate_version,
428
181
            dir_address,
429
181
            dir_identity_key,
430
181
            dir_signing_key,
431
181
            dir_key_published,
432
181
            dir_key_expires,
433
181
            dir_key_crosscert,
434
181
            fingerprint: Fingerprint(id_fingerprint),
435
181
            __non_exhaustive: (),
436
181
        };
437

            
438
181
        let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
439
181
            vec![Box::new(v_crosscert), Box::new(v_sig)];
440

            
441
181
        let timed = timed::TimerangeBound::new(authcert, *dir_key_published..*dir_key_expires);
442
181
        let signed = signed::SignatureGated::new(timed, signatures);
443
181
        let unchecked = UncheckedAuthCert {
444
181
            location,
445
181
            c: signed,
446
181
        };
447
181
        Ok(unchecked)
448
193
    }
449
}
450

            
451
/// Pseudo-Signature of the long-term identity key by the medium-term key.
452
///
453
/// This type does not implement `SignatureItemParseable` because that trait
454
/// is reserved for signatures on *netdocs*, such as [`AuthCertSignature`].
455
/// As `CrossCert` does not sign a full document, it implements only
456
/// `ItemValueParseable`, instead.
457
///
458
/// Verification of this signature is done in `AuthCertUnverified::verify`,
459
/// and during parsing by the old parser.
460
/// So a `CrossCert` in [`AuthCert::dir_key_crosscert`] in a bare `AuthCert` has been validated.
461
//
462
// TODO SPEC (Diziet): it is far from clear to me that this cert serves any useful purpose.
463
// However, we are far too busy now with rewriting the universe to consider transitioning it away.
464
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
465
#[cfg_attr(
466
    feature = "parse2",
467
    derive_deftly(ItemValueParseable),
468
    deftly(netdoc(no_extra_args))
469
)]
470
#[cfg_attr(feature = "encode", derive_deftly(ItemValueEncodable))]
471
// derive_deftly_adhoc disables unused deftly attribute checking, so we needn't cfg_attr them all
472
#[cfg_attr(not(any(feature = "parse2", feature = "encode")), derive_deftly_adhoc)]
473
#[non_exhaustive]
474
pub struct CrossCert {
475
    /// The bytes of the signature (base64-decoded).
476
    #[deftly(netdoc(object))]
477
    pub signature: CrossCertObject,
478
}
479

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

            
503
#[cfg(feature = "encode")]
504
impl CrossCert {
505
    /// Make a `CrossCert`
506
2
    pub fn new(
507
2
        k_auth_sign_rsa: &rsa::KeyPair,
508
2
        h_kp_auth_id_rsa: &RsaIdentity,
509
2
    ) -> StdResult<Self, Bug> {
510
2
        let signature = k_auth_sign_rsa
511
2
            .sign(h_kp_auth_id_rsa.as_bytes())
512
2
            .map_err(into_internal!("failed to sign cross-cert"))?;
513
2
        Ok(CrossCert {
514
2
            signature: CrossCertObject(signature),
515
2
        })
516
2
    }
517
}
518

            
519
/// Signatures for [`AuthCert`]
520
///
521
/// Signed by [`AuthCert::dir_identity_key`] in order to prove ownership.
522
/// Can be seen as the opposite of [`AuthCert::dir_key_crosscert`].
523
///
524
/// # Specifications
525
///
526
/// * <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certification>
527
/// * <https://spec.torproject.org/dir-spec/netdoc.html#signing>
528
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
529
#[cfg_attr(
530
    feature = "parse2",
531
    derive_deftly(NetdocParseableSignatures),
532
    deftly(netdoc(signatures(hashes_accu = Sha1WholeKeywordLine)))
533
)]
534
#[cfg_attr(feature = "encode", derive_deftly(NetdocEncodable))]
535
#[non_exhaustive]
536
pub struct AuthCertSignatures {
537
    /// Contains the actual signature, see [`AuthCertSignatures`].
538
    pub dir_key_certification: RsaSha1Signature,
539
}
540

            
541
/// RSA signature for data in [`AuthCert`]
542
///
543
/// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
544
///
545
/// Compatibility type alias for [`RsaSha1Signature`].
546
#[deprecated = "use RsaSha1Signature"]
547
pub type AuthCertSignature = RsaSha1Signature;
548

            
549
#[cfg(feature = "parse2")]
550
impl ItemObjectParseable for CrossCertObject {
551
933
    fn check_label(label: &str) -> StdResult<(), parse2::EP> {
552
933
        match label {
553
933
            "SIGNATURE" | "ID SIGNATURE" => Ok(()),
554
2
            _ => Err(parse2::EP::ObjectIncorrectLabel),
555
        }
556
933
    }
557

            
558
931
    fn from_bytes(input: &[u8]) -> StdResult<Self, parse2::EP> {
559
931
        Ok(Self(input.to_vec()))
560
931
    }
561
}
562

            
563
#[cfg(feature = "encode")]
564
impl ItemObjectEncodable for CrossCertObject {
565
2
    fn label(&self) -> &str {
566
2
        "ID SIGNATURE"
567
2
    }
568

            
569
2
    fn write_object_onto(&self, b: &mut Vec<u8>) -> StdResult<(), Bug> {
570
2
        b.extend(&self.0);
571
2
        Ok(())
572
2
    }
573
}
574

            
575
impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
576
    type Error = signature::Error;
577

            
578
175
    fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
579
175
        self.c.dangerously_assume_wellsigned()
580
175
    }
581
175
    fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
582
175
        self.c.is_well_signed()
583
175
    }
584
}
585

            
586
#[cfg(feature = "parse2")]
587
impl AuthCertUnverified {
588
    /// Verifies the signature of a [`AuthCert`]
589
    ///
590
    /// # Algorithm
591
    ///
592
    /// 1. Check whether this comes from a valid authority in `v3idents`.
593
    /// 2. Check whether the timestamps are valid (± tolerance).
594
    /// 3. Check whether the fingerprint and long-term identity key match.
595
    /// 4. Check the cross-certificate (proof-of-ownership of signing key).
596
    /// 5. Check the outer certificate (proof-of-ownership of identity key).
597
    ///
598
    /// TODO: Replace `pre_tolerance` and `post_tolerance` with
599
    /// `tor_dircommon::config::DirTolerance` which is not possible at the
600
    /// moment due to a circular dependency of `tor-dircommon` depending
601
    /// upon `tor-netdoc`.
602
    ///
603
    /// TODO: Consider whether to try to deduplicate this signature checking
604
    /// somehow, wrt to [`UncheckedAuthCert`].
605
515
    pub fn verify(
606
515
        self,
607
515
        v3idents: &[RsaIdentity],
608
515
        pre_tolerance: Duration,
609
515
        post_tolerance: Duration,
610
515
        now: SystemTime,
611
515
    ) -> StdResult<AuthCert, parse2::VerifyFailed> {
612
515
        let (body, sigs) = (self.body, self.sigs);
613

            
614
        // (1) Check whether this comes from a valid authority in `v3idents`.
615
515
        if !v3idents.contains(&body.fingerprint.0) {
616
2
            return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
617
513
        }
618

            
619
        // (2) Check whether the timestamps are valid (± tolerance).
620
513
        let validity = *body.dir_key_published..=*body.dir_key_expires;
621
513
        parse2::check_validity_time_tolerance(now, validity, pre_tolerance, post_tolerance)?;
622

            
623
        // (3) Check whether the fingerprint and long-term identity key match.
624
505
        if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
625
2
            return Err(parse2::VerifyFailed::Inconsistent);
626
503
        }
627

            
628
        // (4) Check the cross-certificate (proof-of-ownership of signing key).
629
503
        body.dir_signing_key.verify(
630
503
            body.fingerprint.0.as_bytes(),
631
503
            &body.dir_key_crosscert.signature,
632
2
        )?;
633

            
634
        // (5) Check the outer certificate (proof-of-ownership of identity key).
635
501
        body.dir_identity_key.verify(
636
501
            &sigs.hashes.0.ok_or(parse2::VerifyFailed::Bug)?,
637
501
            &sigs.sigs.dir_key_certification.signature,
638
2
        )?;
639

            
640
499
        Ok(body)
641
515
    }
642

            
643
    /// Verify the signatures (and check validity times)
644
    ///
645
    /// The pre and post tolerance (time check allowances) used are both zero.
646
    ///
647
    /// # Security considerations
648
    ///
649
    /// The caller must check that the KP_auth_id is correct/relevant.
650
10
    pub fn verify_selfcert(self, now: SystemTime) -> StdResult<AuthCert, parse2::VerifyFailed> {
651
10
        let h_kp_auth_id_rsa = self.inspect_unverified().0.fingerprint.0;
652
10
        self.verify(&[h_kp_auth_id_rsa], Duration::ZERO, Duration::ZERO, now)
653
10
    }
654
}
655

            
656
#[cfg(feature = "encode")]
657
impl AuthCert {
658
    /// Make the base for a new `AuthCert`
659
    ///
660
    /// This contains only the mandatory fields (the ones in `AuthCertConstructor`).
661
    /// This method is an alternative to providing a `AuthCertConstructor` value display,
662
    /// and is convenient because an authcert contains much recapitulated information.
663
    ///
664
    /// # Example
665
    ///
666
    /// ```no_run
667
    /// # fn main() -> Result<(), anyhow::Error> {
668
    /// use tor_netdoc::doc::authcert::AuthCert;
669
    /// let (k_auth_id_rsa, k_auth_sign_rsa, published, expires) = todo!();
670
    /// let authcert = AuthCert {
671
    ///     dir_address: Some("192.0.2.17:7000".parse()?),
672
    ///     ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
673
    /// };
674
    /// # Ok(())
675
    /// # }
676
    /// ```
677
2
    pub fn new_base(
678
2
        k_auth_id_rsa: &rsa::KeyPair,
679
2
        k_auth_sign_rsa: &rsa::KeyPair,
680
2
        published: SystemTime,
681
2
        expires: SystemTime,
682
2
    ) -> StdResult<Self, Bug> {
683
2
        let fingerprint = k_auth_id_rsa.to_public_key().to_rsa_identity();
684
2
        let dir_key_crosscert = CrossCert::new(k_auth_sign_rsa, &fingerprint)?;
685

            
686
2
        let base = AuthCertConstructor {
687
2
            fingerprint: fingerprint.into(),
688
2
            dir_key_published: published.into(),
689
2
            dir_key_expires: expires.into(),
690
2
            dir_identity_key: k_auth_id_rsa.to_public_key(),
691
2
            dir_signing_key: k_auth_sign_rsa.to_public_key(),
692
2
            dir_key_crosscert,
693
2
        }
694
2
        .construct();
695

            
696
2
        Ok(base)
697
2
    }
698

            
699
    /// Encode this `AuthCert` and sign it with `k_auth_id_rsa`
700
    ///
701
    /// Yields the string representation of the signed, encoded, document,
702
    /// as an [`EncodedAuthCert`].
703
    // TODO these features are quite tangled
704
    // `EncodedAuthCert` is only available with `parse2` and `plain-consensus`
705
    #[cfg(all(feature = "parse2", feature = "plain-consensus"))]
706
2
    pub fn encode_sign(&self, k_auth_id_rsa: &rsa::KeyPair) -> StdResult<EncodedAuthCert, Bug> {
707
2
        let mut encoder = NetdocEncoder::new();
708
2
        self.encode_unsigned(&mut encoder)?;
709

            
710
2
        let signature =
711
2
            RsaSha1Signature::new_sign_netdoc(k_auth_id_rsa, &encoder, "dir-key-certification")?;
712
2
        let sigs = AuthCertSignatures {
713
2
            dir_key_certification: signature,
714
2
        };
715
2
        sigs.encode_unsigned(&mut encoder)?;
716

            
717
2
        let encoded = encoder.finish()?;
718
        // This rechecks the invariants which ought to be true by construction.
719
        // That is convenient for the code here, and the perf implications are irrelevant.
720
2
        let encoded = encoded
721
2
            .try_into()
722
2
            .map_err(into_internal!("generated broken authcert"))?;
723
2
        Ok(encoded)
724
2
    }
725
}
726

            
727
#[cfg(test)]
728
mod test {
729
    // @@ begin test lint list maintained by maint/add_warning @@
730
    #![allow(clippy::bool_assert_comparison)]
731
    #![allow(clippy::clone_on_copy)]
732
    #![allow(clippy::dbg_macro)]
733
    #![allow(clippy::mixed_attributes_style)]
734
    #![allow(clippy::print_stderr)]
735
    #![allow(clippy::print_stdout)]
736
    #![allow(clippy::single_char_pattern)]
737
    #![allow(clippy::unwrap_used)]
738
    #![allow(clippy::unchecked_time_subtraction)]
739
    #![allow(clippy::useless_vec)]
740
    #![allow(clippy::needless_pass_by_value)]
741
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
742
    use super::*;
743
    use crate::{Error, Pos};
744
    const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
745

            
746
    fn bad_data(fname: &str) -> String {
747
        use std::fs;
748
        use std::path::PathBuf;
749
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
750
        path.push("testdata");
751
        path.push("bad-certs");
752
        path.push(fname);
753

            
754
        fs::read_to_string(path).unwrap()
755
    }
756

            
757
    #[test]
758
    fn parse_one() -> Result<()> {
759
        use tor_checkable::{SelfSigned, Timebound};
760
        let cert = AuthCert::parse(TESTDATA)?
761
            .check_signature()
762
            .unwrap()
763
            .dangerously_assume_timely();
764

            
765
        // Taken from TESTDATA
766
        assert_eq!(
767
            cert.id_fingerprint().to_string(),
768
            "$ed03bb616eb2f60bec80151114bb25cef515b226"
769
        );
770
        assert_eq!(
771
            cert.key_ids().sk_fingerprint.to_string(),
772
            "$c4f720e2c59f9ddd4867fff465ca04031e35648f"
773
        );
774

            
775
        Ok(())
776
    }
777

            
778
    #[test]
779
    fn parse_bad() {
780
        fn check(fname: &str, err: &Error) {
781
            let contents = bad_data(fname);
782
            let cert = AuthCert::parse(&contents);
783
            assert!(cert.is_err());
784
            assert_eq!(&cert.err().unwrap(), err);
785
        }
786

            
787
        check(
788
            "bad-cc-tag",
789
            &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
790
        );
791
        check(
792
            "bad-fingerprint",
793
            &EK::BadArgument
794
                .at_pos(Pos::from_line(2, 1))
795
                .with_msg("fingerprint does not match RSA identity"),
796
        );
797
        check(
798
            "bad-version",
799
            &EK::BadDocumentVersion.with_msg("unexpected version 4"),
800
        );
801
        check(
802
            "wrong-end",
803
            &EK::WrongEndingToken
804
                .with_msg("dir-key-crosscert")
805
                .at_pos(Pos::from_line(37, 1)),
806
        );
807
        check(
808
            "wrong-start",
809
            &EK::WrongStartingToken
810
                .with_msg("fingerprint")
811
                .at_pos(Pos::from_line(1, 1)),
812
        );
813
    }
814

            
815
    #[test]
816
    fn test_recovery_1() {
817
        let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
818
        data += TESTDATA;
819

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

            
822
        // We should recover from the failed case and read the next data fine.
823
        assert!(res[0].is_err());
824
        assert!(res[1].is_ok());
825
        assert_eq!(res.len(), 2);
826
    }
827

            
828
    #[test]
829
    fn test_recovery_2() {
830
        let mut data = bad_data("bad-version");
831
        data += TESTDATA;
832

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

            
835
        // We should recover from the failed case and read the next data fine.
836
        assert!(res[0].is_err());
837
        assert!(res[1].is_ok());
838
        assert_eq!(res.len(), 2);
839
    }
840

            
841
    #[cfg(feature = "parse2")]
842
    mod parse2_test {
843
        use super::{AuthCert, AuthCertUnverified, AuthCertVersion, CrossCert, CrossCertObject};
844

            
845
        use std::{
846
            net::{Ipv4Addr, SocketAddrV4},
847
            str::FromStr,
848
            time::{Duration, SystemTime},
849
        };
850

            
851
        use crate::{
852
            parse2::{self, ErrorProblem, NetdocUnverified, ParseError, ParseInput, VerifyFailed},
853
            types::{self, Iso8601TimeSp},
854
        };
855

            
856
        use derive_deftly::Deftly;
857
        use tor_llcrypto::pk::rsa::{self, RsaIdentity};
858

            
859
        // === AUTHCERT D190BF3B00E311A9AEB6D62B51980E9B2109BAD1 ===
860
        // These values come from testdata2/keys/authority_certificate.
861
        const DIR_KEY_PUBLISHED: &str = "2000-01-01 00:00:05";
862
        const DIR_KEY_EXPIRES: &str = "2001-01-01 00:00:05";
863
        const FINGERPRINT: &str = "D190BF3B00E311A9AEB6D62B51980E9B2109BAD1";
864
        const DIR_ADDRESS: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 7100);
865
        const DIR_IDENTITY_KEY: &str = "
866
-----BEGIN RSA PUBLIC KEY-----
867
MIIBigKCAYEAt0rXD+1gYwKFAxrO4uNHQ9dQVUOGx5FxkioYNSct5Z3JU00dTKNJ
868
jt4OGkFYwixWwk6KLDOiB+I/q9YIdA1NlQ5R3Hz8jjvFPVl0JQQm2LYzdSzv7/CZ
869
U1qq5rYeeoYKx8qMQg4q3WgR251GEnOG+rVqzFSs0oyC+SDfYn9iMt00/pmN3HXf
870
wmasY6BescVrYoDbnpkwKATizd4lzx5K8V8aXUXtd8qnYzSyHLlhiO1eufVX07YC
871
+AVHV7W7qCTY/4I5Sm0dQ9jF/r04JBHnpH+aae48JOjWDCZj9AINi3rCKS8XClGb
872
BB/LJidoQAZraQEEtu3Ql1mjdLreeyWfXpfZFvwKuYn44FtQsOT2TVAVNqNF8N4v
873
yfwfiPN6FQWlPyMCEB81HerCn03Zi5WgQLGo7PAeO4LFrLrU16DUC5/oJENeHs0T
874
27FZQyrlf0rAxiHh7TJKcjLmzeyxCQVQlr2AXXs28gKHV0AQnEcdrVOpTrquSCQQ
875
hWBehR+ct4OJAgMBAAE=
876
-----END RSA PUBLIC KEY-----
877
    ";
878
        const DIR_SIGNING_KEY: &str = "
879
-----BEGIN RSA PUBLIC KEY-----
880
MIIBCgKCAQEAtPF94+bThLI28kn6e+MmUECMMJ5UBlnQ+Mvwn8Zd85awPQTDz5Wu
881
13sZDN3nWnhgSuP5q/WDYc5GPPtQdSWBiG1nJA2XLgEHTHf29iGZ+jAoGfIMJvBV
882
1xN8baTnsha5LGx5BQ4UqzlUmoaPzwbjehnPd00FgVkpcCvKZu1HU7fGMVwn4MMh
883
zuxJTqTgfcuFWTEu0H0ukOFX+51ih6WO3GWYqRiqgU0Q5/Ets8ccCTq7ND9d2u1P
884
d7kQzUHbVP0KmYGK4qYntGDfP4g9SmpBoUUHyP3j9en9S6PMYv8m1YFO7M7JKu6Q
885
dQZfGTxj9C/0b/jRklgn5JlKAl9eJQvCdwIDAQAB
886
-----END RSA PUBLIC KEY-----
887
";
888
        const DIR_CROSS_CERT_OBJECT: &str = "
889
-----BEGIN ID SIGNATURE-----
890
NBaPdBNCNMah6cklrALzj0RdHymF/jPGOv9NmeqaXc0uTN06S/BlVM/xTjilu+dj
891
sjPuT0BQL4/ZWyZR+R+gJJojKYILSId4IQ1elzRSxpFN+u2u/ZEmS6SR2SwpA05A
892
btOYBKAmYkY6rLsTCbXGx3lAH2kAXfcrltCNKZXV6gqW7X379fiOnSId1OWhKPe1
893
/1p3pQGZxgb8FOT1kpHxOMRBClF9Ulm3d9fQZr80Wn73gZ2Bp1RXn9c7c/71HD1c
894
mzMT023bleZ574az+117yNAr6XbIgqQfzbySzVLPXM8ZN9BrGR40KDZ2638ZJjRu
895
8HK5TzuknWlkRv3hCyRX+g==
896
-----END ID SIGNATURE-----
897
";
898
        const AUTHCERT_RAW: &str = include_str!("../../testdata2/keys/authority_certificate");
899
        /// A system time in the range of [`DIR_KEY_PUBLISHED`] and [`DIR_KEY_EXPIRES`].
900
        ///
901
        /// Constructed by ourselves to have a time point we can use for testing
902
        /// timestamp verification.
903
        const VALID_SYSTEM_TIME: &str = "2000-06-01 00:00:00";
904

            
905
        // === AUTHCERT 0B8997614EC647C1C6B6A044E2B5408F0B823FB0 ===
906
        // This values come from ../../testdata2/cached-certs--1
907
        // A different authority certificate different from the one above.
908
        const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
909

            
910
        /// Converts a string in the [`Iso8601TimeSp`] format to [`SystemTime`].
911
        ///
912
        /// This functions panics in the case the input is malformatted.
913
        fn to_system_time(s: &str) -> SystemTime {
914
            Iso8601TimeSp::from_str(s).unwrap().0
915
        }
916

            
917
        /// Converts a PEM encoded RSA Public key to an [`rsa::PublicKey`].
918
        ///
919
        /// This function panics in the case the input is malformatted.
920
        fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
921
            rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
922
        }
923

            
924
        /// Converts a hex-encoded RSA identity to an [`RsaIdentity`].
925
        ///
926
        /// This function panics in the case the input is malformatted.
927
        fn to_rsa_id(s: &str) -> RsaIdentity {
928
            RsaIdentity::from_hex(s).unwrap()
929
        }
930

            
931
        /// Tests whether a [`DirKeyCrossCert`] can be parsed properly.
932
        #[test]
933
        fn dir_auth_cross_cert() {
934
            #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
935
            #[derive_deftly(NetdocParseable)]
936
            struct Dummy {
937
                dir_key_crosscert: CrossCert,
938
            }
939

            
940
            // "Encodes" a DIR_CROSS_CERT_OBJECT by simply removing the lines
941
            // indicating the BEGIN and END, as the purpose is to test multiple
942
            // labels.
943
            let encoded = DIR_CROSS_CERT_OBJECT
944
                .lines()
945
                .filter(|line| !line.starts_with("-----"))
946
                .collect::<Vec<_>>()
947
                .join("\n");
948
            let decoded = pem::parse(DIR_CROSS_CERT_OBJECT)
949
                .unwrap()
950
                .contents()
951
                .to_vec();
952

            
953
            // Try with `SIGNATURE`.
954
            let cert = format!(
955
                "dir-key-crosscert\n-----BEGIN SIGNATURE-----\n{encoded}\n-----END SIGNATURE-----"
956
            );
957
            let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
958
            assert_eq!(
959
                res,
960
                Dummy {
961
                    dir_key_crosscert: CrossCert {
962
                        signature: CrossCertObject(decoded.clone())
963
                    }
964
                }
965
            );
966

            
967
            // Try with `ID SIGNATURE`.
968
            let cert = format!(
969
                "dir-key-crosscert\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
970
            );
971
            let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
972
            assert_eq!(
973
                res,
974
                Dummy {
975
                    dir_key_crosscert: CrossCert {
976
                        signature: CrossCertObject(decoded.clone())
977
                    }
978
                }
979
            );
980

            
981
            // Try with different label and fail.
982
            let cert =
983
                format!("dir-key-crosscert\n-----BEGIN WHAT-----\n{encoded}\n-----END WHAT-----");
984
            let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
985
            match res {
986
                Err(ParseError {
987
                    problem: ErrorProblem::ObjectIncorrectLabel,
988
                    doctype: "dir-key-crosscert",
989
                    file: _,
990
                    lno: 1,
991
                    column: None,
992
                }) => {}
993
                other => panic!("not expected error {other:#?}"),
994
            }
995

            
996
            // Try with extra args.
997
            let cert = format!(
998
                "dir-key-crosscert arg1\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
999
            );
            let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
            match res {
                Err(ParseError {
                    problem: ErrorProblem::UnexpectedArgument { column: 19 },
                    doctype: "dir-key-crosscert",
                    file: _,
                    lno: 1,
                    column: Some(19),
                }) => {}
                other => panic!("not expected error {other:#?}"),
            }
        }
        #[test]
        fn dir_auth_cert() {
            let res =
                parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(AUTHCERT_RAW, ""))
                    .unwrap();
            assert_eq!(
                *res.inspect_unverified().0,
                AuthCert {
                    dir_key_certificate_version: AuthCertVersion::V3,
                    dir_address: Some(DIR_ADDRESS),
                    fingerprint: types::Fingerprint(to_rsa_id(FINGERPRINT)),
                    dir_key_published: Iso8601TimeSp(to_system_time(DIR_KEY_PUBLISHED)),
                    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 = "encode", feature = "parse2", feature = "plain-consensus"))]
    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(())
        }
    }
}