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

            
16
use tor_checkable::{signed, timed};
17
use tor_llcrypto::pk::rsa;
18
use tor_llcrypto::{d, pk, pk::rsa::RsaIdentity};
19

            
20
use once_cell::sync::Lazy;
21

            
22
use std::{net, time};
23

            
24
use digest::Digest;
25

            
26
#[cfg(feature = "build_docs")]
27
mod build;
28

            
29
#[cfg(feature = "build_docs")]
30
pub use build::AuthCertBuilder;
31

            
32
decl_keyword! {
33
    AuthCertKwd {
34
        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
35
        "dir-address" => DIR_ADDRESS,
36
        "fingerprint" => FINGERPRINT,
37
        "dir-identity-key" => DIR_IDENTITY_KEY,
38
        "dir-key-published" => DIR_KEY_PUBLISHED,
39
        "dir-key-expires" => DIR_KEY_EXPIRES,
40
        "dir-signing-key" => DIR_SIGNING_KEY,
41
        "dir-key-crosscert" => DIR_KEY_CROSSCERT,
42
        "dir-key-certification" => DIR_KEY_CERTIFICATION,
43
    }
44
}
45

            
46
/// Rules about entries that must appear in an AuthCert, and how they must
47
/// be formed.
48
18
static AUTHCERT_RULES: Lazy<SectionRules<AuthCertKwd>> = Lazy::new(|| {
49
18
    use AuthCertKwd::*;
50
18

            
51
18
    let mut rules = SectionRules::new();
52
18
    rules.add(DIR_KEY_CERTIFICATE_VERSION.rule().required().args(1..));
53
18
    rules.add(DIR_ADDRESS.rule().args(1..));
54
18
    rules.add(FINGERPRINT.rule().required().args(1..));
55
18
    rules.add(DIR_IDENTITY_KEY.rule().required().no_args().obj_required());
56
18
    rules.add(DIR_SIGNING_KEY.rule().required().no_args().obj_required());
57
18
    rules.add(DIR_KEY_PUBLISHED.rule().required());
58
18
    rules.add(DIR_KEY_EXPIRES.rule().required());
59
18
    rules.add(DIR_KEY_CROSSCERT.rule().required().no_args().obj_required());
60
18
    rules.add(
61
18
        DIR_KEY_CERTIFICATION
62
18
            .rule()
63
18
            .required()
64
18
            .no_args()
65
18
            .obj_required(),
66
18
    );
67
18
    rules
68
18
});
69

            
70
/// A single authority certificate.
71
///
72
/// Authority certificates bind a long-term RSA identity key from a
73
/// directory authority to a medium-term signing key.  The signing
74
/// keys are the ones used to sign votes and consensuses; the identity
75
/// keys can be kept offline.
76
#[allow(dead_code)]
77
3
#[derive(Clone, Debug)]
78
pub struct AuthCert {
79
    /// An IPv4 address for this authority.
80
    address: Option<net::SocketAddrV4>,
81
    /// The long-term RSA identity key for this authority
82
    identity_key: rsa::PublicKey,
83
    /// The medium-term RSA signing key for this authority
84
    signing_key: rsa::PublicKey,
85
    /// Declared time when this certificate was published
86
    published: time::SystemTime,
87
    /// Declared time when this certificate expires.
88
    expires: time::SystemTime,
89

            
90
    /// Derived field: fingerprints of the certificate's keys
91
    key_ids: AuthCertKeyIds,
92
}
93

            
94
/// A pair of key identities that identifies a certificate.
95
352250
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
96
#[allow(clippy::exhaustive_structs)]
97
pub struct AuthCertKeyIds {
98
    /// Fingerprint of identity key
99
    pub id_fingerprint: rsa::RsaIdentity,
100
    /// Fingerprint of signing key
101
    pub sk_fingerprint: rsa::RsaIdentity,
102
}
103

            
104
/// An authority certificate whose signature and validity time we
105
/// haven't checked.
106
pub struct UncheckedAuthCert {
107
    /// Where we found this AuthCert within the string containing it.
108
    location: Option<Extent>,
109

            
110
    /// The actual unchecked certificate.
111
    c: signed::SignatureGated<timed::TimerangeBound<AuthCert>>,
112
}
113

            
114
impl UncheckedAuthCert {
115
    /// If this AuthCert was originally parsed from `haystack`, return its
116
    /// text.
117
    ///
118
    /// TODO: This is a pretty bogus interface; there should be a
119
    /// better way to remember where to look for this thing if we want
120
    /// it without keeping the input alive forever.  We should
121
    /// refactor.
122
34
    pub fn within<'a>(&self, haystack: &'a str) -> Option<&'a str> {
123
34
        self.location
124
34
            .as_ref()
125
34
            .and_then(|ext| ext.reconstruct(haystack))
126
34
    }
127
}
128

            
129
impl AuthCert {
130
    /// Make an [`AuthCertBuilder`] object that can be used to
131
    /// construct authority certificates for testing.
132
    #[cfg(feature = "build_docs")]
133
5
    pub fn builder() -> AuthCertBuilder {
134
5
        AuthCertBuilder::new()
135
5
    }
136

            
137
    /// Parse an authority certificate from a string.
138
    ///
139
    /// This function verifies the certificate's signatures, but doesn't
140
    /// check its expiration dates.
141
23
    pub fn parse(s: &str) -> Result<UncheckedAuthCert> {
142
23
        let mut reader = NetDocReader::new(s);
143
23
        let result = AuthCert::take_from_reader(&mut reader).map_err(|e| e.within(s));
144
23
        reader.should_be_exhausted()?;
145
23
        result
146
23
    }
147

            
148
    /// Return an iterator yielding authority certificates from a string.
149
38
    pub fn parse_multiple(s: &str) -> impl Iterator<Item = Result<UncheckedAuthCert>> + '_ {
150
38
        AuthCertIterator(NetDocReader::new(s))
151
38
    }
152
    /*
153
        /// Return true if this certificate is expired at a given time, or
154
        /// not yet valid at that time.
155
        pub fn is_expired_at(&self, when: time::SystemTime) -> bool {
156
            when < self.published || when > self.expires
157
        }
158
    */
159
    /// Return the signing key certified by this certificate.
160
41
    pub fn signing_key(&self) -> &rsa::PublicKey {
161
41
        &self.signing_key
162
41
    }
163

            
164
    /// Return an AuthCertKeyIds object describing the keys in this
165
    /// certificate.
166
446
    pub fn key_ids(&self) -> &AuthCertKeyIds {
167
446
        &self.key_ids
168
446
    }
169

            
170
    /// Return an RsaIdentity for this certificate's identity key.
171
    pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
172
        &self.key_ids.id_fingerprint
173
    }
174

            
175
    /// Return an RsaIdentity for this certificate's signing key.
176
    pub fn sk_fingerprint(&self) -> &rsa::RsaIdentity {
177
        &self.key_ids.sk_fingerprint
178
    }
179

            
180
    /// Return the time when this certificate says it was published.
181
18
    pub fn published(&self) -> time::SystemTime {
182
18
        self.published
183
18
    }
184

            
185
    /// Return the time when this certificate says it should expire.
186
18
    pub fn expires(&self) -> time::SystemTime {
187
18
        self.expires
188
18
    }
189

            
190
    /// Parse an authority certificate from a reader.
191
67
    fn take_from_reader(reader: &mut NetDocReader<'_, AuthCertKwd>) -> Result<UncheckedAuthCert> {
192
67
        use AuthCertKwd::*;
193
67

            
194
67
        let mut start_found = false;
195
591
        let mut iter = reader.pause_at(|item| {
196
591
            let is_start = item.is_ok_with_kwd(DIR_KEY_CERTIFICATE_VERSION);
197
591
            let pause = is_start && start_found;
198
591
            if is_start {
199
71
                start_found = true;
200
520
            }
201
591
            pause
202
591
        });
203
67
        let body = AUTHCERT_RULES.parse(&mut iter)?;
204

            
205
        // Make sure first and last element are correct types.  We can
206
        // safely call unwrap() on first and last, since there are required
207
        // tokens in the rules, so we know that at least one token will have
208
        // been parsed.
209
65
        let start_pos = {
210
            // Unwrap should be safe because `.parse()` would have already
211
            // returned an Error
212
            #[allow(clippy::unwrap_used)]
213
66
            let first_item = body.first_item().unwrap();
214
66
            if first_item.kwd() != DIR_KEY_CERTIFICATE_VERSION {
215
1
                return Err(EK::WrongStartingToken
216
1
                    .with_msg(first_item.kwd_str().to_string())
217
1
                    .at_pos(first_item.pos()));
218
65
            }
219
65
            first_item.pos()
220
        };
221
64
        let end_pos = {
222
            // Unwrap should be safe because `.parse()` would have already
223
            // returned an Error
224
            #[allow(clippy::unwrap_used)]
225
65
            let last_item = body.last_item().unwrap();
226
65
            if last_item.kwd() != DIR_KEY_CERTIFICATION {
227
1
                return Err(EK::WrongEndingToken
228
1
                    .with_msg(last_item.kwd_str().to_string())
229
1
                    .at_pos(last_item.pos()));
230
64
            }
231
64
            last_item.end_pos()
232
        };
233

            
234
64
        let version = body
235
64
            .required(DIR_KEY_CERTIFICATE_VERSION)?
236
64
            .parse_arg::<u32>(0)?;
237
64
        if version != 3 {
238
2
            return Err(EK::BadDocumentVersion.with_msg(format!("unexpected version {}", version)));
239
62
        }
240

            
241
62
        let signing_key: rsa::PublicKey = body
242
62
            .required(DIR_SIGNING_KEY)?
243
62
            .parse_obj::<RsaPublic>("RSA PUBLIC KEY")?
244
62
            .check_len(1024..)?
245
62
            .check_exponent(65537)?
246
62
            .into();
247

            
248
62
        let identity_key: rsa::PublicKey = body
249
62
            .required(DIR_IDENTITY_KEY)?
250
62
            .parse_obj::<RsaPublic>("RSA PUBLIC KEY")?
251
62
            .check_len(1024..)?
252
62
            .check_exponent(65537)?
253
62
            .into();
254

            
255
62
        let published = body
256
62
            .required(DIR_KEY_PUBLISHED)?
257
62
            .args_as_str()
258
62
            .parse::<Iso8601TimeSp>()?
259
62
            .into();
260

            
261
62
        let expires = body
262
62
            .required(DIR_KEY_EXPIRES)?
263
62
            .args_as_str()
264
62
            .parse::<Iso8601TimeSp>()?
265
62
            .into();
266

            
267
        {
268
            // Check fingerprint for consistency with key.
269
62
            let fp_tok = body.required(FINGERPRINT)?;
270
62
            let fingerprint: RsaIdentity = fp_tok.args_as_str().parse::<Fingerprint>()?.into();
271
62
            if fingerprint != identity_key.to_rsa_identity() {
272
1
                return Err(EK::BadArgument
273
1
                    .at_pos(fp_tok.pos())
274
1
                    .with_msg("fingerprint does not match RSA identity"));
275
61
            }
276
        }
277

            
278
61
        let address = body
279
61
            .maybe(DIR_ADDRESS)
280
61
            .parse_args_as_str::<net::SocketAddrV4>()?;
281

            
282
        // check crosscert
283
60
        let v_crosscert = {
284
61
            let crosscert = body.required(DIR_KEY_CROSSCERT)?;
285
            // Unwrap should be safe because `.parse()` and `required()` would
286
            // have already returned an Error
287
            #[allow(clippy::unwrap_used)]
288
61
            let mut tag = crosscert.obj_tag().unwrap();
289
61
            // we are required to support both.
290
61
            if tag != "ID SIGNATURE" && tag != "SIGNATURE" {
291
1
                tag = "ID SIGNATURE";
292
60
            }
293
61
            let sig = crosscert.obj(tag)?;
294

            
295
60
            let signed = identity_key.to_rsa_identity();
296
60
            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
297
60

            
298
60
            rsa::ValidatableRsaSignature::new(&signing_key, &sig, signed.as_bytes())
299
        };
300

            
301
        // check the signature
302
60
        let v_sig = {
303
60
            let signature = body.required(DIR_KEY_CERTIFICATION)?;
304
60
            let sig = signature.obj("SIGNATURE")?;
305

            
306
60
            let mut sha1 = d::Sha1::new();
307
60
            let s = reader.str();
308
60
            // Unwrap should be safe because `.parse()` would have already
309
60
            // returned an Error
310
60
            #[allow(clippy::unwrap_used)]
311
60
            let start_offset = body.first_item().unwrap().offset_in(s).unwrap();
312
60
            #[allow(clippy::unwrap_used)]
313
60
            let end_offset = body.last_item().unwrap().offset_in(s).unwrap();
314
60
            let end_offset = end_offset + "dir-key-certification\n".len();
315
60
            sha1.update(&s[start_offset..end_offset]);
316
60
            let sha1 = sha1.finalize();
317
60
            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
318
60

            
319
60
            rsa::ValidatableRsaSignature::new(&identity_key, &sig, &sha1)
320
60
        };
321
60

            
322
60
        let id_fingerprint = identity_key.to_rsa_identity();
323
60
        let sk_fingerprint = signing_key.to_rsa_identity();
324
60
        let key_ids = AuthCertKeyIds {
325
60
            id_fingerprint,
326
60
            sk_fingerprint,
327
60
        };
328

            
329
60
        let location = {
330
60
            let s = reader.str();
331
60
            let start_idx = start_pos.offset_within(s);
332
60
            let end_idx = end_pos.offset_within(s);
333
60
            match (start_idx, end_idx) {
334
60
                (Some(a), Some(b)) => Extent::new(s, &s[a..b + 1]),
335
                _ => None,
336
            }
337
        };
338

            
339
60
        let authcert = AuthCert {
340
60
            address,
341
60
            identity_key,
342
60
            signing_key,
343
60
            published,
344
60
            expires,
345
60
            key_ids,
346
60
        };
347
60

            
348
60
        let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
349
60
            vec![Box::new(v_crosscert), Box::new(v_sig)];
350
60

            
351
60
        let timed = timed::TimerangeBound::new(authcert, published..expires);
352
60
        let signed = signed::SignatureGated::new(timed, signatures);
353
60
        let unchecked = UncheckedAuthCert {
354
60
            location,
355
60
            c: signed,
356
60
        };
357
60
        Ok(unchecked)
358
67
    }
359

            
360
    /// Skip tokens from the reader until the next token (if any) is
361
    /// the start of cert.
362
2
    fn advance_reader_to_next(reader: &mut NetDocReader<'_, AuthCertKwd>) {
363
2
        use AuthCertKwd::*;
364
2
        let iter = reader.iter();
365
3
        while let Some(Ok(item)) = iter.peek() {
366
3
            if item.kwd() == DIR_KEY_CERTIFICATE_VERSION {
367
2
                return;
368
1
            }
369
1
            iter.next();
370
        }
371
2
    }
372
}
373

            
374
/// Iterator type to read a series of concatenated certificates from a
375
/// string.
376
struct AuthCertIterator<'a>(NetDocReader<'a, AuthCertKwd>);
377

            
378
impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
379
    type Error = signature::Error;
380

            
381
58
    fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
382
58
        self.c.dangerously_assume_wellsigned()
383
58
    }
384
58
    fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
385
58
        self.c.is_well_signed()
386
58
    }
387
}
388

            
389
impl<'a> Iterator for AuthCertIterator<'a> {
390
    type Item = Result<UncheckedAuthCert>;
391
82
    fn next(&mut self) -> Option<Result<UncheckedAuthCert>> {
392
82
        if self.0.is_exhausted() {
393
38
            return None;
394
44
        }
395
44

            
396
44
        let pos_orig = self.0.pos();
397
44
        let result = AuthCert::take_from_reader(&mut self.0);
398
44
        if result.is_err() {
399
2
            if self.0.pos() == pos_orig {
400
                // No tokens were consumed from the reader.  We need
401
                // to drop at least one token to ensure we aren't in
402
                // an infinite loop.
403
                //
404
                // (This might not be able to happen, but it's easier to
405
                // explicitly catch this case than it is to prove that
406
                // it's impossible.)
407
                let _ = self.0.iter().next();
408
2
            }
409
2
            AuthCert::advance_reader_to_next(&mut self.0);
410
42
        }
411
44
        Some(result.map_err(|e| e.within(self.0.str())))
412
82
    }
413
}
414

            
415
#[cfg(test)]
416
mod test {
417
    #![allow(clippy::unwrap_used)]
418
    use super::*;
419
    use crate::{Error, Pos};
420
    const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
421

            
422
    fn bad_data(fname: &str) -> String {
423
        use std::fs;
424
        use std::path::PathBuf;
425
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
426
        path.push("testdata");
427
        path.push("bad-certs");
428
        path.push(fname);
429

            
430
        fs::read_to_string(path).unwrap()
431
    }
432

            
433
    #[test]
434
    fn parse_one() -> Result<()> {
435
        use tor_checkable::{SelfSigned, Timebound};
436
        let _rd = AuthCert::parse(TESTDATA)?
437
            .check_signature()
438
            .unwrap()
439
            .dangerously_assume_timely();
440

            
441
        Ok(())
442
    }
443

            
444
    #[test]
445
    fn parse_bad() {
446
        fn check(fname: &str, err: &Error) {
447
            let contents = bad_data(fname);
448
            let cert = AuthCert::parse(&contents);
449
            assert!(cert.is_err());
450
            assert_eq!(&cert.err().unwrap(), err);
451
        }
452

            
453
        check(
454
            "bad-cc-tag",
455
            &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
456
        );
457
        check(
458
            "bad-fingerprint",
459
            &EK::BadArgument
460
                .at_pos(Pos::from_line(2, 1))
461
                .with_msg("fingerprint does not match RSA identity"),
462
        );
463
        check(
464
            "bad-version",
465
            &EK::BadDocumentVersion.with_msg("unexpected version 4"),
466
        );
467
        check(
468
            "wrong-end",
469
            &EK::WrongEndingToken
470
                .with_msg("dir-key-crosscert")
471
                .at_pos(Pos::from_line(37, 1)),
472
        );
473
        check(
474
            "wrong-start",
475
            &EK::WrongStartingToken
476
                .with_msg("fingerprint")
477
                .at_pos(Pos::from_line(1, 1)),
478
        );
479
    }
480

            
481
    #[test]
482
    fn test_recovery_1() {
483
        let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
484
        data += TESTDATA;
485

            
486
        let res: Vec<Result<_>> = AuthCert::parse_multiple(&data).collect();
487

            
488
        // We should recover from the failed case and read the next data fine.
489
        assert!(res[0].is_err());
490
        assert!(res[1].is_ok());
491
        assert_eq!(res.len(), 2);
492
    }
493

            
494
    #[test]
495
    fn test_recovery_2() {
496
        let mut data = bad_data("bad-version");
497
        data += TESTDATA;
498

            
499
        let res: Vec<Result<_>> = AuthCert::parse_multiple(&data).collect();
500

            
501
        // We should recover from the failed case and read the next data fine.
502
        assert!(res[0].is_err());
503
        assert!(res[1].is_ok());
504
        assert_eq!(res.len(), 2);
505
    }
506
}