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, RsaPublic};
14
use crate::util::str::Extent;
15
use crate::{NetdocErrorKind as EK, Result};
16

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

            
21
use once_cell::sync::Lazy;
22

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

            
25
use digest::Digest;
26

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

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

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

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

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

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

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

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

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

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

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

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

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

            
150
    /// Return an iterator yielding authority certificates from a string.
151
102
    pub fn parse_multiple(s: &str) -> Result<impl Iterator<Item = Result<UncheckedAuthCert>> + '_> {
152
        use AuthCertKwd::*;
153
102
        let sections = NetDocReader::new(s)?
154
290
            .batching_split_before_loose(|item| item.is_ok_with_kwd(DIR_KEY_CERTIFICATE_VERSION));
155
102
        Ok(sections
156
120
            .map(|mut section| {
157
24
                let body = AUTHCERT_RULES.parse(&mut section)?;
158
22
                AuthCert::from_body(&body, s)
159
120
            })
160
120
            .map(|r| r.map_err(|e| e.within(s))))
161
102
    }
162
    /*
163
        /// Return true if this certificate is expired at a given time, or
164
        /// not yet valid at that time.
165
        pub fn is_expired_at(&self, when: time::SystemTime) -> bool {
166
            when < self.published || when > self.expires
167
        }
168
    */
169
    /// Return the signing key certified by this certificate.
170
108
    pub fn signing_key(&self) -> &rsa::PublicKey {
171
108
        &self.signing_key
172
108
    }
173

            
174
    /// Return an AuthCertKeyIds object describing the keys in this
175
    /// certificate.
176
956
    pub fn key_ids(&self) -> &AuthCertKeyIds {
177
956
        &self.key_ids
178
956
    }
179

            
180
    /// Return an RsaIdentity for this certificate's identity key.
181
2
    pub fn id_fingerprint(&self) -> &rsa::RsaIdentity {
182
2
        &self.key_ids.id_fingerprint
183
2
    }
184

            
185
    /// Return an RsaIdentity for this certificate's signing key.
186
2
    pub fn sk_fingerprint(&self) -> &rsa::RsaIdentity {
187
2
        &self.key_ids.sk_fingerprint
188
2
    }
189

            
190
    /// Return the time when this certificate says it was published.
191
49
    pub fn published(&self) -> time::SystemTime {
192
49
        self.published
193
49
    }
194

            
195
    /// Return the time when this certificate says it should expire.
196
49
    pub fn expires(&self) -> time::SystemTime {
197
49
        self.expires
198
49
    }
199

            
200
    /// Parse an authority certificate from a reader.
201
173
    fn from_body(body: &Section<'_, AuthCertKwd>, s: &str) -> Result<UncheckedAuthCert> {
202
        use AuthCertKwd::*;
203

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

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

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

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

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

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

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

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

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

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

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

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

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

            
317
161
            rsa::ValidatableRsaSignature::new(&identity_key, &sig, &sha1)
318
161
        };
319
161

            
320
161
        let id_fingerprint = identity_key.to_rsa_identity();
321
161
        let sk_fingerprint = signing_key.to_rsa_identity();
322
161
        let key_ids = AuthCertKeyIds {
323
161
            id_fingerprint,
324
161
            sk_fingerprint,
325
161
        };
326

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

            
336
161
        let authcert = AuthCert {
337
161
            address,
338
161
            identity_key,
339
161
            signing_key,
340
161
            published,
341
161
            expires,
342
161
            key_ids,
343
161
        };
344
161

            
345
161
        let signatures: Vec<Box<dyn pk::ValidatableSignature>> =
346
161
            vec![Box::new(v_crosscert), Box::new(v_sig)];
347
161

            
348
161
        let timed = timed::TimerangeBound::new(authcert, published..expires);
349
161
        let signed = signed::SignatureGated::new(timed, signatures);
350
161
        let unchecked = UncheckedAuthCert {
351
161
            location,
352
161
            c: signed,
353
161
        };
354
161
        Ok(unchecked)
355
173
    }
356
}
357

            
358
impl tor_checkable::SelfSigned<timed::TimerangeBound<AuthCert>> for UncheckedAuthCert {
359
    type Error = signature::Error;
360

            
361
155
    fn dangerously_assume_wellsigned(self) -> timed::TimerangeBound<AuthCert> {
362
155
        self.c.dangerously_assume_wellsigned()
363
155
    }
364
155
    fn is_well_signed(&self) -> std::result::Result<(), Self::Error> {
365
155
        self.c.is_well_signed()
366
155
    }
367
}
368

            
369
#[cfg(test)]
370
mod test {
371
    // @@ begin test lint list maintained by maint/add_warning @@
372
    #![allow(clippy::bool_assert_comparison)]
373
    #![allow(clippy::clone_on_copy)]
374
    #![allow(clippy::dbg_macro)]
375
    #![allow(clippy::mixed_attributes_style)]
376
    #![allow(clippy::print_stderr)]
377
    #![allow(clippy::print_stdout)]
378
    #![allow(clippy::single_char_pattern)]
379
    #![allow(clippy::unwrap_used)]
380
    #![allow(clippy::unchecked_duration_subtraction)]
381
    #![allow(clippy::useless_vec)]
382
    #![allow(clippy::needless_pass_by_value)]
383
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
384
    use super::*;
385
    use crate::{Error, Pos};
386
    const TESTDATA: &str = include_str!("../../testdata/authcert1.txt");
387

            
388
    fn bad_data(fname: &str) -> String {
389
        use std::fs;
390
        use std::path::PathBuf;
391
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
392
        path.push("testdata");
393
        path.push("bad-certs");
394
        path.push(fname);
395

            
396
        fs::read_to_string(path).unwrap()
397
    }
398

            
399
    #[test]
400
    fn parse_one() -> Result<()> {
401
        use tor_checkable::{SelfSigned, Timebound};
402
        let cert = AuthCert::parse(TESTDATA)?
403
            .check_signature()
404
            .unwrap()
405
            .dangerously_assume_timely();
406

            
407
        // Taken from TESTDATA
408
        assert_eq!(
409
            cert.id_fingerprint().to_string(),
410
            "$ed03bb616eb2f60bec80151114bb25cef515b226"
411
        );
412
        assert_eq!(
413
            cert.sk_fingerprint().to_string(),
414
            "$c4f720e2c59f9ddd4867fff465ca04031e35648f"
415
        );
416

            
417
        Ok(())
418
    }
419

            
420
    #[test]
421
    fn parse_bad() {
422
        fn check(fname: &str, err: &Error) {
423
            let contents = bad_data(fname);
424
            let cert = AuthCert::parse(&contents);
425
            assert!(cert.is_err());
426
            assert_eq!(&cert.err().unwrap(), err);
427
        }
428

            
429
        check(
430
            "bad-cc-tag",
431
            &EK::WrongObject.at_pos(Pos::from_line(27, 12)),
432
        );
433
        check(
434
            "bad-fingerprint",
435
            &EK::BadArgument
436
                .at_pos(Pos::from_line(2, 1))
437
                .with_msg("fingerprint does not match RSA identity"),
438
        );
439
        check(
440
            "bad-version",
441
            &EK::BadDocumentVersion.with_msg("unexpected version 4"),
442
        );
443
        check(
444
            "wrong-end",
445
            &EK::WrongEndingToken
446
                .with_msg("dir-key-crosscert")
447
                .at_pos(Pos::from_line(37, 1)),
448
        );
449
        check(
450
            "wrong-start",
451
            &EK::WrongStartingToken
452
                .with_msg("fingerprint")
453
                .at_pos(Pos::from_line(1, 1)),
454
        );
455
    }
456

            
457
    #[test]
458
    fn test_recovery_1() {
459
        let mut data = "<><><<><>\nfingerprint ABC\n".to_string();
460
        data += TESTDATA;
461

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

            
464
        // We should recover from the failed case and read the next data fine.
465
        assert!(res[0].is_err());
466
        assert!(res[1].is_ok());
467
        assert_eq!(res.len(), 2);
468
    }
469

            
470
    #[test]
471
    fn test_recovery_2() {
472
        let mut data = bad_data("bad-version");
473
        data += TESTDATA;
474

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

            
477
        // We should recover from the failed case and read the next data fine.
478
        assert!(res[0].is_err());
479
        assert!(res[1].is_ok());
480
        assert_eq!(res.len(), 2);
481
    }
482
}