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::{
11
    Bug, ItemArgument, ItemEncoder, ItemObjectEncodable, NetdocEncodable, NetdocEncoder,
12
};
13
use crate::parse::keyword::Keyword;
14
use crate::parse::parser::{Section, SectionRules};
15
use crate::parse::tokenize::{ItemResult, NetDocReader};
16
use crate::parse2::{
17
    self, ArgumentError, ArgumentStream, ItemArgumentParseable, ItemObjectParseable,
18
    NetdocUnverified as _, sig_hashes::Sha1WholeKeywordLine,
19
};
20
use crate::types::misc::{Fingerprint, Iso8601TimeSp, RsaPublicParse1Helper, RsaSha1Signature};
21
use crate::util::str::Extent;
22
use crate::{NetdocErrorKind as EK, NormalItemArgument, Result};
23

            
24
use tor_basic_utils::impl_debug_hex;
25
use tor_checkable::{signed, timed};
26
use tor_error::into_internal;
27
use tor_llcrypto::pk::rsa;
28
use tor_llcrypto::{d, pk, pk::rsa::RsaIdentity};
29

            
30
use std::sync::LazyLock;
31

            
32
use std::result::Result as StdResult;
33
use std::{net, time, time::Duration, time::SystemTime};
34

            
35
use derive_deftly::Deftly;
36
use digest::Digest;
37

            
38
#[cfg(feature = "build_docs")]
39
mod build;
40

            
41
#[cfg(feature = "build_docs")]
42
#[allow(deprecated)]
43
pub use build::AuthCertBuilder;
44

            
45
#[cfg(feature = "incomplete")]
46
mod encoded;
47
#[cfg(feature = "incomplete")]
48
pub use encoded::EncodedAuthCert;
49

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

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

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

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

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

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

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

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

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

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

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

            
158
    #[doc(hidden)]
159
    #[deftly(netdoc(skip))]
160
    pub __non_exhaustive: (),
161
}
162

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

            
176
impl NormalItemArgument for AuthCertVersion {}
177

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

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

            
194
    /// The actual unchecked certificate.
195
    c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
196
}
197

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

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

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

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

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

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

            
272
    /// Return the time when this certificate says it was published.
273
56
    pub fn published(&self) -> time::SystemTime {
274
56
        *self.dir_key_published
275
56
    }
276

            
277
    /// Return the time when this certificate says it should expire.
278
56
    pub fn expires(&self) -> time::SystemTime {
279
56
        *self.dir_key_expires
280
56
    }
281

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

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

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

            
323
188
        let dir_signing_key: rsa::PublicKey = body
324
188
            .required(DIR_SIGNING_KEY)?
325
188
            .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
326
188
            .check_len(1024..)?
327
188
            .check_exponent(65537)?
328
188
            .into();
329

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

            
337
188
        let dir_key_published = body
338
188
            .required(DIR_KEY_PUBLISHED)?
339
188
            .args_as_str()
340
188
            .parse::<Iso8601TimeSp>()?;
341

            
342
188
        let dir_key_expires = body
343
188
            .required(DIR_KEY_EXPIRES)?
344
188
            .args_as_str()
345
188
            .parse::<Iso8601TimeSp>()?;
346

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

            
358
186
        let dir_address = body
359
186
            .maybe(DIR_ADDRESS)
360
186
            .parse_args_as_str::<net::SocketAddrV4>()?;
361

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

            
376
184
            let signed = dir_identity_key.to_rsa_identity();
377
            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
378

            
379
184
            let v = rsa::ValidatableRsaSignature::new(&dir_signing_key, &sig, signed.as_bytes());
380

            
381
184
            dir_key_crosscert = CrossCert {
382
184
                signature: CrossCertObject(sig),
383
184
            };
384

            
385
184
            v
386
        };
387

            
388
        // check the signature
389
184
        let v_sig = {
390
184
            let signature = body.required(DIR_KEY_CERTIFICATION)?;
391
184
            let sig = signature.obj("SIGNATURE")?;
392

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

            
405
184
            rsa::ValidatableRsaSignature::new(&dir_identity_key, &sig, &sha1)
406
        };
407

            
408
184
        let id_fingerprint = dir_identity_key.to_rsa_identity();
409

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

            
419
184
        let authcert = AuthCert {
420
184
            dir_key_certificate_version,
421
184
            dir_address,
422
184
            dir_identity_key,
423
184
            dir_signing_key,
424
184
            dir_key_published,
425
184
            dir_key_expires,
426
184
            dir_key_crosscert,
427
184
            fingerprint: Fingerprint(id_fingerprint),
428
184
            __non_exhaustive: (),
429
184
        };
430

            
431
184
        let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
432
184
            vec![Box::new(v_crosscert), Box::new(v_sig)];
433

            
434
184
        let timed = timed::TimerangeBound::new(authcert, *dir_key_published..*dir_key_expires);
435
184
        let signed = signed::SignatureGated::new(timed, signatures);
436
184
        let unchecked = UncheckedAuthCert {
437
184
            location,
438
184
            c: signed,
439
184
        };
440
184
        Ok(unchecked)
441
196
    }
442
}
443

            
444
/// Parsing/encoding module for `AuthCertKeyIds` as found in `directory-signature`
445
///
446
/// Use with `#[deftly(netdoc(with = ...))]` when deriving
447
/// `ItemValueParseable` and `ItemValueEncodable`.
448
///
449
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:directory-signature>
450
//
451
// Currently the only use site is `netstatus::Signature`.
452
// If we find this is being used in many places, and is therefore a standard thing,
453
// we should arrange for the derives to be able to derive from an argument collection,
454
// and use that.
455
pub(crate) mod keyids_directory_signature_args {
456
    use super::*;
457
    use std::result::Result;
458

            
459
    /// Parse
460
1964
    pub(crate) fn from_args<'s>(
461
1964
        args: &mut ArgumentStream<'s>,
462
1964
    ) -> Result<AuthCertKeyIds, ArgumentError> {
463
3974
        let mut fp = || Ok::<_, ArgumentError>(Fingerprint::from_args(args)?.0);
464
        Ok(AuthCertKeyIds {
465
1964
            id_fingerprint: fp()?,
466
1964
            sk_fingerprint: fp()?,
467
        })
468
1964
    }
469

            
470
    /// Encode
471
2
    pub(crate) fn write_arg_onto(
472
2
        self_: &AuthCertKeyIds,
473
2
        out: &mut ItemEncoder<'_>,
474
2
    ) -> Result<(), Bug> {
475
5
        let mut fp = |id| Fingerprint(id).write_arg_onto(out);
476
2
        fp(self_.id_fingerprint)?;
477
2
        fp(self_.sk_fingerprint)?;
478
2
        Ok(())
479
2
    }
480
}
481

            
482
/// Pseudo-Signature of the long-term identity key by the medium-term key.
483
///
484
/// This type does not implement `SignatureItemParseable` because that trait
485
/// is reserved for signatures on *netdocs*, such as [`AuthCertSignature`].
486
/// As `CrossCert` does not sign a full document, it implements only
487
/// `ItemValueParseable`, instead.
488
///
489
/// Verification of this signature is done in `AuthCertUnverified::verify`,
490
/// and during parsing by the old parser.
491
/// So a `CrossCert` in [`AuthCert::dir_key_crosscert`] in a bare `AuthCert` has been validated.
492
//
493
// TODO SPEC (Diziet): it is far from clear to me that this cert serves any useful purpose.
494
// However, we are far too busy now with rewriting the universe to consider transitioning it away.
495
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
496
#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
497
#[deftly(netdoc(no_extra_args))]
498
#[non_exhaustive]
499
pub struct CrossCert {
500
    /// The bytes of the signature (base64-decoded).
501
    #[deftly(netdoc(object))]
502
    pub signature: CrossCertObject,
503
}
504

            
505
/// Wrapper around [`Vec<u8>`] implementing [`ItemObjectParseable`] properly.
506
///
507
/// Unfortunately, this wrapper is necessary, because the specification
508
/// demands that these certificate objects must accept two labels:
509
/// `SIGNATURE` and `ID SIGNATURE`.  Because the deftly template for
510
/// `ItemValueParseable` only allows for a single label
511
/// (`#[deftly(netdoc(object(label = "LABEL")))]`), we must implement this
512
/// trait ourselves in order to allow multiple ones.
513
///
514
/// TODO: In the future, it might be nice to let the respective fmeta
515
/// accept a pattern, as pattern matching would allow trivially for one
516
/// to infinity different combinations.
517
/// TODO SPEC: Alternatively we could abolish the wrong labels,
518
/// or we could abolish Objects completely and just have long lines.
519
///
520
/// # Specifications
521
///
522
/// <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-crosscert>
523
#[derive(Clone, PartialEq, Eq, derive_more::Deref)]
524
#[non_exhaustive]
525
pub struct CrossCertObject(pub Vec<u8>);
526
impl_debug_hex! { CrossCertObject . 0 }
527

            
528
impl CrossCert {
529
    /// Make a `CrossCert`
530
2
    pub fn new(
531
2
        k_auth_sign_rsa: &rsa::KeyPair,
532
2
        h_kp_auth_id_rsa: &RsaIdentity,
533
2
    ) -> StdResult<Self, Bug> {
534
2
        let signature = k_auth_sign_rsa
535
2
            .sign(h_kp_auth_id_rsa.as_bytes())
536
2
            .map_err(into_internal!("failed to sign cross-cert"))?;
537
2
        Ok(CrossCert {
538
2
            signature: CrossCertObject(signature),
539
2
        })
540
2
    }
541
}
542

            
543
/// Signatures for [`AuthCert`]
544
///
545
/// Signed by [`AuthCert::dir_identity_key`] in order to prove ownership.
546
/// Can be seen as the opposite of [`AuthCert::dir_key_crosscert`].
547
///
548
/// # Specifications
549
///
550
/// * <https://spec.torproject.org/dir-spec/creating-key-certificates.html#item:dir-key-certification>
551
/// * <https://spec.torproject.org/dir-spec/netdoc.html#signing>
552
#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
553
#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
554
#[deftly(netdoc(signatures(hashes_accu = Sha1WholeKeywordLine)))]
555
#[non_exhaustive]
556
pub struct AuthCertSignatures {
557
    /// Contains the actual signature, see [`AuthCertSignatures`].
558
    pub dir_key_certification: RsaSha1Signature,
559
}
560

            
561
/// RSA signature for data in [`AuthCert`]
562
///
563
/// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
564
///
565
/// Compatibility type alias for [`RsaSha1Signature`].
566
#[deprecated = "use RsaSha1Signature"]
567
pub type AuthCertSignature = RsaSha1Signature;
568

            
569
impl ItemObjectParseable for CrossCertObject {
570
950
    fn check_label(label: &str) -> StdResult<(), parse2::EP> {
571
950
        match label {
572
950
            "SIGNATURE" | "ID SIGNATURE" => Ok(()),
573
2
            _ => Err(parse2::EP::ObjectIncorrectLabel),
574
        }
575
950
    }
576

            
577
948
    fn from_bytes(input: &[u8]) -> StdResult<Self, parse2::EP> {
578
948
        Ok(Self(input.to_vec()))
579
948
    }
580
}
581

            
582
impl ItemObjectEncodable for CrossCertObject {
583
2
    fn label(&self) -> &str {
584
2
        "ID SIGNATURE"
585
2
    }
586

            
587
2
    fn write_object_onto(&self, b: &mut Vec<u8>) -> StdResult<(), Bug> {
588
2
        b.extend(&self.0);
589
2
        Ok(())
590
2
    }
591
}
592

            
593
impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
594
    type Error = signature::Error;
595

            
596
178
    fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
597
178
        self.c.dangerously_assume_wellsigned()
598
178
    }
599
178
    fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
600
178
        self.c.is_well_signed()
601
178
    }
602
}
603

            
604
impl AuthCertUnverified {
605
    /// Verifies the signature of a [`AuthCert`]
606
    ///
607
    /// # Algorithm
608
    ///
609
    /// 1. Check whether this comes from a valid authority in `v3idents`.
610
    /// 2. Check whether the timestamps are valid (± tolerance).
611
    /// 3. Check whether the fingerprint and long-term identity key match.
612
    /// 4. Check the cross-certificate (proof-of-ownership of signing key).
613
    /// 5. Check the outer certificate (proof-of-ownership of identity key).
614
    ///
615
    /// TODO: Replace `pre_tolerance` and `post_tolerance` with
616
    /// `tor_dircommon::config::DirTolerance` which is not possible at the
617
    /// moment due to a circular dependency of `tor-dircommon` depending
618
    /// upon `tor-netdoc`.
619
    ///
620
    /// TODO: Consider whether to try to deduplicate this signature checking
621
    /// somehow, wrt to [`UncheckedAuthCert`].
622
524
    pub fn verify(
623
524
        self,
624
524
        v3idents: &[RsaIdentity],
625
524
        pre_tolerance: Duration,
626
524
        post_tolerance: Duration,
627
524
        now: SystemTime,
628
524
    ) -> StdResult<AuthCert, parse2::VerifyFailed> {
629
524
        let (body, sigs) = (self.body, self.sigs);
630

            
631
        // (1) Check whether this comes from a valid authority in `v3idents`.
632
524
        if !v3idents.contains(&body.fingerprint.0) {
633
2
            return Err(parse2::VerifyFailed::InsufficientTrustedSigners);
634
522
        }
635

            
636
        // (2) Check whether the timestamps are valid (± tolerance).
637
522
        let validity = *body.dir_key_published..=*body.dir_key_expires;
638
522
        parse2::check_validity_time_tolerance(now, validity, pre_tolerance, post_tolerance)?;
639

            
640
        // (3) Check whether the fingerprint and long-term identity key match.
641
514
        if body.dir_identity_key.to_rsa_identity() != *body.fingerprint {
642
2
            return Err(parse2::VerifyFailed::Inconsistent);
643
512
        }
644

            
645
        // (4) Check the cross-certificate (proof-of-ownership of signing key).
646
512
        body.dir_signing_key.verify(
647
512
            body.fingerprint.0.as_bytes(),
648
512
            &body.dir_key_crosscert.signature,
649
2
        )?;
650

            
651
        // (5) Check the outer certificate (proof-of-ownership of identity key).
652
510
        body.dir_identity_key.verify(
653
510
            &sigs.hashes.0.ok_or(parse2::VerifyFailed::Bug)?,
654
510
            &sigs.sigs.dir_key_certification.signature,
655
2
        )?;
656

            
657
508
        Ok(body)
658
524
    }
659

            
660
    /// Verify the signatures (and check validity times)
661
    ///
662
    /// The pre and post tolerance (time check allowances) used are both zero.
663
    ///
664
    /// # Security considerations
665
    ///
666
    /// The caller must check that the KP_auth_id is correct/relevant.
667
10
    pub fn verify_selfcert(self, now: SystemTime) -> StdResult<AuthCert, parse2::VerifyFailed> {
668
10
        let h_kp_auth_id_rsa = self.inspect_unverified().0.fingerprint.0;
669
10
        self.verify(&[h_kp_auth_id_rsa], Duration::ZERO, Duration::ZERO, now)
670
10
    }
671
}
672

            
673
impl AuthCert {
674
    /// Make the base for a new `AuthCert`
675
    ///
676
    /// This contains only the mandatory fields (the ones in `AuthCertConstructor`).
677
    /// This method is an alternative to providing a `AuthCertConstructor` value display,
678
    /// and is convenient because an authcert contains much recapitulated information.
679
    ///
680
    /// # Example
681
    ///
682
    /// ```no_run
683
    /// # fn main() -> Result<(), anyhow::Error> {
684
    /// use tor_netdoc::doc::authcert::AuthCert;
685
    /// let (k_auth_id_rsa, k_auth_sign_rsa, published, expires) = todo!();
686
    /// let authcert = AuthCert {
687
    ///     dir_address: Some("192.0.2.17:7000".parse()?),
688
    ///     ..AuthCert::new_base(&k_auth_id_rsa, &k_auth_sign_rsa, published, expires)?
689
    /// };
690
    /// # Ok(())
691
    /// # }
692
    /// ```
693
2
    pub fn new_base(
694
2
        k_auth_id_rsa: &rsa::KeyPair,
695
2
        k_auth_sign_rsa: &rsa::KeyPair,
696
2
        published: SystemTime,
697
2
        expires: SystemTime,
698
2
    ) -> StdResult<Self, Bug> {
699
2
        let fingerprint = k_auth_id_rsa.to_public_key().to_rsa_identity();
700
2
        let dir_key_crosscert = CrossCert::new(k_auth_sign_rsa, &fingerprint)?;
701

            
702
2
        let base = AuthCertConstructor {
703
2
            fingerprint: fingerprint.into(),
704
2
            dir_key_published: published.into(),
705
2
            dir_key_expires: expires.into(),
706
2
            dir_identity_key: k_auth_id_rsa.to_public_key(),
707
2
            dir_signing_key: k_auth_sign_rsa.to_public_key(),
708
2
            dir_key_crosscert,
709
2
        }
710
2
        .construct();
711

            
712
2
        Ok(base)
713
2
    }
714

            
715
    /// Encode this `AuthCert` and sign it with `k_auth_id_rsa`
716
    ///
717
    /// Yields the string representation of the signed, encoded, document,
718
    /// as an [`EncodedAuthCert`].
719
    // TODO these features are quite tangled
720
    // `EncodedAuthCert` is only available with `parse2` and `plain-consensus`
721
    #[cfg(feature = "incomplete")] // Needs EncodedAuthCert
722
2
    pub fn encode_sign(&self, k_auth_id_rsa: &rsa::KeyPair) -> StdResult<EncodedAuthCert, Bug> {
723
2
        let mut encoder = NetdocEncoder::new();
724
2
        self.encode_unsigned(&mut encoder)?;
725

            
726
2
        let signature =
727
2
            RsaSha1Signature::new_sign_netdoc(k_auth_id_rsa, &encoder, "dir-key-certification")?;
728
2
        let sigs = AuthCertSignatures {
729
2
            dir_key_certification: signature,
730
2
        };
731
2
        sigs.encode_unsigned(&mut encoder)?;
732

            
733
2
        let encoded = encoder.finish()?;
734
        // This rechecks the invariants which ought to be true by construction.
735
        // That is convenient for the code here, and the perf implications are irrelevant.
736
2
        let encoded = encoded
737
2
            .try_into()
738
2
            .map_err(into_internal!("generated broken authcert"))?;
739
2
        Ok(encoded)
740
2
    }
741
}
742

            
743
#[cfg(test)]
744
mod test {
745
    // @@ begin test lint list maintained by maint/add_warning @@
746
    #![allow(clippy::bool_assert_comparison)]
747
    #![allow(clippy::clone_on_copy)]
748
    #![allow(clippy::dbg_macro)]
749
    #![allow(clippy::mixed_attributes_style)]
750
    #![allow(clippy::print_stderr)]
751
    #![allow(clippy::print_stdout)]
752
    #![allow(clippy::single_char_pattern)]
753
    #![allow(clippy::unwrap_used)]
754
    #![allow(clippy::unchecked_time_subtraction)]
755
    #![allow(clippy::useless_vec)]
756
    #![allow(clippy::needless_pass_by_value)]
757
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
758
    use super::*;
759
    use crate::{
760
        Pos,
761
        parse2::{ErrorProblem, ParseError, ParseInput, VerifyFailed, parse_netdoc},
762
        types,
763
    };
764
    use humantime::parse_rfc3339;
765
    use std::result::Result;
766
    use std::{
767
        net::{Ipv4Addr, SocketAddrV4},
768
        str::FromStr,
769
    };
770
    use tor_basic_utils::test_rng;
771

            
772
    const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
773

            
774
    fn bad_data(fname: &str) -> String {
775
        use std::fs;
776
        use std::path::PathBuf;
777
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
778
        path.push("testdata");
779
        path.push("bad-certs");
780
        path.push(fname);
781

            
782
        fs::read_to_string(path).unwrap()
783
    }
784

            
785
    #[test]
786
    fn parse_one() -> crate::Result<()> {
787
        use tor_checkable::{SelfSigned, Timebound};
788
        let cert = AuthCert::parse(TESTDATA)?
789
            .check_signature()
790
            .unwrap()
791
            .dangerously_assume_timely();
792

            
793
        // Taken from TESTDATA
794
        assert_eq!(
795
            cert.id_fingerprint().to_string(),
796
            "$ed03bb616eb2f60bec80151114bb25cef515b226"
797
        );
798
        assert_eq!(
799
            cert.key_ids().sk_fingerprint.to_string(),
800
            "$c4f720e2c59f9ddd4867fff465ca04031e35648f"
801
        );
802

            
803
        Ok(())
804
    }
805

            
806
    #[test]
807
    fn parse_bad() {
808
        fn check(fname: &str, err: &crate::Error) {
809
            let contents = bad_data(fname);
810
            let cert = AuthCert::parse(&contents);
811
            assert!(cert.is_err());
812
            assert_eq!(&cert.err().unwrap(), err);
813
        }
814

            
815
        check(
816
            "bad-cc-tag",
817
            &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
818
        );
819
        check(
820
            "bad-fingerprint",
821
            &EK::BadArgument
822
                .at_pos(Pos::from_line(2, 1))
823
                .with_msg("fingerprint does not match RSA identity"),
824
        );
825
        check(
826
            "bad-version",
827
            &EK::BadDocumentVersion.with_msg("unexpected version 4"),
828
        );
829
        check(
830
            "wrong-end",
831
            &EK::WrongEndingToken
832
                .with_msg("dir-key-crosscert")
833
                .at_pos(Pos::from_line(37, 1)),
834
        );
835
        check(
836
            "wrong-start",
837
            &EK::WrongStartingToken
838
                .with_msg("fingerprint")
839
                .at_pos(Pos::from_line(1, 1)),
840
        );
841
    }
842

            
843
    #[test]
844
    fn test_recovery_1() {
845
        let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
846
        data += TESTDATA;
847

            
848
        let res: Vec<crate::Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
849

            
850
        // We should recover from the failed case and read the next data fine.
851
        assert!(res[0].is_err());
852
        assert!(res[1].is_ok());
853
        assert_eq!(res.len(), 2);
854
    }
855

            
856
    #[test]
857
    fn test_recovery_2() {
858
        let mut data = bad_data("bad-version");
859
        data += TESTDATA;
860

            
861
        let res: Vec<crate::Result<_>> = AuthCert::parse_multiple(&data).unwrap().collect();
862

            
863
        // We should recover from the failed case and read the next data fine.
864
        assert!(res[0].is_err());
865
        assert!(res[1].is_ok());
866
        assert_eq!(res.len(), 2);
867
    }
868

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

            
915
    // === AUTHCERT 0B8997614EC647C1C6B6A044E2B5408F0B823FB0 ===
916
    // This values come from ../../testdata2/cached-certs--1
917
    // A different authority certificate different from the one above.
918
    const ALTERNATIVE_AUTHCERT_RAW: &str = include_str!("../../testdata2/cached-certs--1");
919

            
920
    /// Converts a string in the [`Iso8601TimeSp`] format to [`SystemTime`].
921
    ///
922
    /// This functions panics in the case the input is malformatted.
923
    fn to_system_time(s: &str) -> SystemTime {
924
        Iso8601TimeSp::from_str(s).unwrap().0
925
    }
926

            
927
    /// Converts a PEM encoded RSA Public key to an [`rsa::PublicKey`].
928
    ///
929
    /// This function panics in the case the input is malformatted.
930
    fn pem_to_rsa_pk(s: &str) -> rsa::PublicKey {
931
        rsa::PublicKey::from_der(pem::parse(s).unwrap().contents()).unwrap()
932
    }
933

            
934
    /// Converts a hex-encoded RSA identity to an [`RsaIdentity`].
935
    ///
936
    /// This function panics in the case the input is malformatted.
937
    fn to_rsa_id(s: &str) -> RsaIdentity {
938
        RsaIdentity::from_hex(s).unwrap()
939
    }
940

            
941
    /// Tests whether a [`DirKeyCrossCert`] can be parsed properly.
942
    #[test]
943
    fn dir_auth_cross_cert() {
944
        #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
945
        #[derive_deftly(NetdocParseable)]
946
        struct Dummy {
947
            dir_key_crosscert: CrossCert,
948
        }
949

            
950
        // "Encodes" a DIR_CROSS_CERT_OBJECT by simply removing the lines
951
        // indicating the BEGIN and END, as the purpose is to test multiple
952
        // labels.
953
        let encoded = DIR_CROSS_CERT_OBJECT
954
            .lines()
955
            .filter(|line| !line.starts_with("-----"))
956
            .collect::<Vec<_>>()
957
            .join("\n");
958
        let decoded = pem::parse(DIR_CROSS_CERT_OBJECT)
959
            .unwrap()
960
            .contents()
961
            .to_vec();
962

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

            
977
        // Try with `ID SIGNATURE`.
978
        let cert = format!(
979
            "dir-key-crosscert\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
980
        );
981
        let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, "")).unwrap();
982
        assert_eq!(
983
            res,
984
            Dummy {
985
                dir_key_crosscert: CrossCert {
986
                    signature: CrossCertObject(decoded.clone())
987
                }
988
            }
989
        );
990

            
991
        // Try with different label and fail.
992
        let cert =
993
            format!("dir-key-crosscert\n-----BEGIN WHAT-----\n{encoded}\n-----END WHAT-----");
994
        let res = parse2::parse_netdoc::<Dummy>(&ParseInput::new(&cert, ""));
995
        match res {
996
            Err(ParseError {
997
                problem: ErrorProblem::ObjectIncorrectLabel,
998
                doctype: "dir-key-crosscert",
999
                file: _,
                lno: 1,
                column: None,
            }) => {}
            other => panic!("not expected error {other:#?}"),
        }
        // Try with extra args.
        let cert = format!(
            "dir-key-crosscert arg1\n-----BEGIN ID SIGNATURE-----\n{encoded}\n-----END ID SIGNATURE-----"
        );
        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
        );
    }
    #[test]
    fn keyids_for_directory_signature() -> anyhow::Result<()> {
        #[derive(Deftly)]
        #[derive_deftly(NetdocEncodable, NetdocParseable)]
        struct Doc {
            intro: (),
            ids: Item,
        }
        #[derive(Deftly)]
        #[derive_deftly(ItemValueEncodable, ItemValueParseable)]
        struct Item {
            #[deftly(netdoc(with = keyids_directory_signature_args))]
            ids: AuthCertKeyIds,
        }
        let text = r#"intro
ids 1234567812345678123456781234567812345678 ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
"#;
        let doc = parse2::parse_netdoc::<Doc>(&ParseInput::new(text, "<text>"))?;
        let mut re_encode = NetdocEncoder::new();
        doc.encode_unsigned(&mut re_encode)?;
        let re_encode = re_encode.finish()?;
        assert_eq!(text, re_encode);
        Ok(())
    }
    #[test]
    #[cfg(feature = "incomplete")]
    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(())
    }
}