1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_duration_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![allow(mismatched_lifetime_syntaxes)] mod err;
48pub mod rsa;
49
50use caret::caret_int;
51use tor_bytes::{Error as BytesError, Result as BytesResult};
52use tor_bytes::{Readable, Reader};
53use tor_llcrypto::pk::*;
54
55use std::time;
56
57pub use err::CertError;
58
59#[cfg(feature = "encode")]
60mod encode;
61#[cfg(feature = "encode")]
62pub use encode::EncodedEd25519Cert;
63#[cfg(feature = "encode")]
64pub use err::CertEncodeError;
65
66type CertResult<T> = std::result::Result<T, CertError>;
68
69caret_int! {
70 pub struct CertType(u8) {
81 TLS_LINK_X509 = 0x01,
83 RSA_ID_X509 = 0x02,
85 LINK_AUTH_X509 = 0x03,
88
89 IDENTITY_V_SIGNING = 0x04,
91
92 SIGNING_V_TLS_CERT = 0x05,
94
95 SIGNING_V_LINK_AUTH = 0x06,
97
98 RSA_ID_V_IDENTITY = 0x07,
101
102 HS_BLINDED_ID_V_SIGNING = 0x08,
106
107 HS_IP_V_SIGNING = 0x09,
122
123 NTOR_CC_IDENTITY = 0x0A,
126
127 HS_IP_CC_SIGNING = 0x0B,
136
137 FAMILY_V_IDENTITY = 0x0C,
140 }
141}
142
143caret_int! {
144 pub struct ExtType(u8) {
146 SIGNED_WITH_ED25519_KEY = 0x04,
150 }
151}
152
153caret_int! {
154 pub struct KeyType(u8) {
156 ED25519_KEY = 0x01,
158 SHA256_OF_RSA = 0x02,
160 SHA256_OF_X509 = 0x03,
162 }
163}
164
165#[derive(Debug, Clone)]
168#[cfg_attr(feature = "encode", derive(derive_builder::Builder))]
169#[cfg_attr(
170 feature = "encode",
171 builder(name = "Ed25519CertConstructor", build_fn(skip))
172)]
173pub struct Ed25519Cert {
174 #[cfg_attr(feature = "encode", builder(setter(custom)))]
176 exp_hours: u32,
177 cert_type: CertType,
179 cert_key: CertifiedKey,
181 #[allow(unused)]
183 #[cfg_attr(feature = "encode", builder(setter(custom)))]
184 extensions: Vec<CertExt>,
185 #[cfg_attr(feature = "encode", builder(setter(custom)))]
191 signed_with: Option<ed25519::Ed25519Identity>,
192}
193
194#[derive(Debug, Clone)]
196#[non_exhaustive]
197pub enum CertifiedKey {
198 Ed25519(ed25519::Ed25519Identity),
200 RsaSha256Digest([u8; 32]),
202 X509Sha256Digest([u8; 32]),
204 Unrecognized(UnrecognizedKey),
206}
207
208#[derive(Debug, Clone)]
210pub struct UnrecognizedKey {
211 key_type: KeyType,
213 key_digest: [u8; 32],
215}
216
217impl CertifiedKey {
218 pub fn key_type(&self) -> KeyType {
220 match self {
221 CertifiedKey::Ed25519(_) => KeyType::ED25519_KEY,
222 CertifiedKey::RsaSha256Digest(_) => KeyType::SHA256_OF_RSA,
223 CertifiedKey::X509Sha256Digest(_) => KeyType::SHA256_OF_X509,
224
225 CertifiedKey::Unrecognized(u) => u.key_type,
226 }
227 }
228 pub fn as_bytes(&self) -> &[u8] {
231 match self {
232 CertifiedKey::Ed25519(k) => k.as_bytes(),
233 CertifiedKey::RsaSha256Digest(k) => &k[..],
234 CertifiedKey::X509Sha256Digest(k) => &k[..],
235 CertifiedKey::Unrecognized(u) => &u.key_digest[..],
236 }
237 }
238 pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
241 match self {
242 CertifiedKey::Ed25519(k) => Some(k),
243 _ => None,
244 }
245 }
246 fn from_reader(key_type: KeyType, r: &mut Reader<'_>) -> BytesResult<Self> {
249 Ok(match key_type {
250 KeyType::ED25519_KEY => CertifiedKey::Ed25519(r.extract()?),
251 KeyType::SHA256_OF_RSA => CertifiedKey::RsaSha256Digest(r.extract()?),
252 KeyType::SHA256_OF_X509 => CertifiedKey::X509Sha256Digest(r.extract()?),
253 _ => CertifiedKey::Unrecognized(UnrecognizedKey {
254 key_type,
255 key_digest: r.extract()?,
256 }),
257 })
258 }
259}
260
261#[derive(Debug, Clone)]
263enum CertExt {
264 SignedWithEd25519(SignedWithEd25519Ext),
266 Unrecognized(UnrecognizedExt),
268}
269
270#[derive(Debug, Clone)]
272#[allow(unused)]
273struct UnrecognizedExt {
274 affects_validation: bool,
277 ext_type: ExtType,
279 body: Vec<u8>,
281}
282
283impl CertExt {
284 fn ext_id(&self) -> ExtType {
286 match self {
287 CertExt::SignedWithEd25519(_) => ExtType::SIGNED_WITH_ED25519_KEY,
288 CertExt::Unrecognized(u) => u.ext_type,
289 }
290 }
291}
292
293#[derive(Debug, Clone)]
295struct SignedWithEd25519Ext {
296 pk: ed25519::Ed25519Identity,
298}
299
300impl Readable for CertExt {
301 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
302 let len = b.take_u16()?;
303 let ext_type: ExtType = b.take_u8()?.into();
304 let flags = b.take_u8()?;
305 let body = b.take(len as usize)?;
306
307 Ok(match ext_type {
308 ExtType::SIGNED_WITH_ED25519_KEY => CertExt::SignedWithEd25519(SignedWithEd25519Ext {
309 pk: ed25519::Ed25519Identity::from_bytes(body).ok_or_else(|| {
310 BytesError::InvalidMessage("wrong length on Ed25519 key".into())
311 })?,
312 }),
313 _ => {
314 if (flags & 1) != 0 {
315 return Err(BytesError::InvalidMessage(
316 "unrecognized certificate extension, with 'affects_validation' flag set."
317 .into(),
318 ));
319 }
320 CertExt::Unrecognized(UnrecognizedExt {
321 affects_validation: false,
322 ext_type,
323 body: body.into(),
324 })
325 }
326 })
327 }
328}
329
330impl Ed25519Cert {
331 pub fn decode(cert: &[u8]) -> BytesResult<KeyUnknownCert> {
340 let mut r = Reader::from_slice(cert);
341 let v = r.take_u8()?;
342 if v != 1 {
343 return Err(BytesError::InvalidMessage(
346 "Unrecognized certificate version".into(),
347 ));
348 }
349 let cert_type = r.take_u8()?.into();
350 let exp_hours = r.take_u32()?;
351 let mut cert_key_type = r.take_u8()?.into();
352
353 if cert_type == CertType::SIGNING_V_TLS_CERT && cert_key_type == KeyType::ED25519_KEY {
357 cert_key_type = KeyType::SHA256_OF_X509;
358 }
359
360 let cert_key = CertifiedKey::from_reader(cert_key_type, &mut r)?;
361 let n_exts = r.take_u8()?;
362 let mut extensions = Vec::new();
363 for _ in 0..n_exts {
364 let e: CertExt = r.extract()?;
365 extensions.push(e);
366 }
367
368 let sig_offset = r.consumed();
369 let signature: ed25519::Signature = r.extract()?;
370 r.should_be_exhausted()?;
371
372 let keyext = extensions
373 .iter()
374 .find(|e| e.ext_id() == ExtType::SIGNED_WITH_ED25519_KEY);
375
376 let included_pkey = match keyext {
377 Some(CertExt::SignedWithEd25519(s)) => Some(s.pk),
378 _ => None,
379 };
380
381 Ok(KeyUnknownCert {
382 cert: UncheckedCert {
383 cert: Ed25519Cert {
384 exp_hours,
385 cert_type,
386 cert_key,
387 extensions,
388
389 signed_with: included_pkey,
390 },
391 text: cert[0..sig_offset].into(),
392 signature,
393 },
394 })
395 }
396
397 pub fn expiry(&self) -> std::time::SystemTime {
399 let d = std::time::Duration::new(u64::from(self.exp_hours) * 3600, 0);
400 std::time::SystemTime::UNIX_EPOCH + d
401 }
402
403 pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
405 when >= self.expiry()
406 }
407
408 pub fn subject_key(&self) -> &CertifiedKey {
411 &self.cert_key
412 }
413
414 pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
416 self.signed_with.as_ref()
417 }
418
419 pub fn cert_type(&self) -> CertType {
421 self.cert_type
422 }
423}
424
425#[derive(Clone, Debug)]
434pub struct KeyUnknownCert {
435 cert: UncheckedCert,
437}
438
439impl KeyUnknownCert {
440 pub fn peek_cert_type(&self) -> CertType {
442 self.cert.cert.cert_type
443 }
444 pub fn peek_subject_key(&self) -> &CertifiedKey {
446 &self.cert.cert.cert_key
447 }
448
449 #[deprecated(
457 since = "0.7.1",
458 note = "Use should_have_signing_key or should_be_signed_with instead."
459 )]
460 pub fn check_key(self, pkey: Option<&ed25519::Ed25519Identity>) -> CertResult<UncheckedCert> {
461 match pkey {
462 Some(wanted) => self.should_be_signed_with(wanted),
463 None => self.should_have_signing_key(),
464 }
465 }
466
467 pub fn should_have_signing_key(self) -> CertResult<UncheckedCert> {
474 let real_key = match &self.cert.cert.signed_with {
475 Some(a) => *a,
476 None => return Err(CertError::MissingPubKey),
477 };
478
479 Ok(UncheckedCert {
480 cert: Ed25519Cert {
481 signed_with: Some(real_key),
482 ..self.cert.cert
483 },
484 ..self.cert
485 })
486 }
487
488 pub fn should_be_signed_with(
494 self,
495 pkey: &ed25519::Ed25519Identity,
496 ) -> CertResult<UncheckedCert> {
497 let real_key = match &self.cert.cert.signed_with {
498 Some(a) if a == pkey => *pkey,
499 None => *pkey,
500 Some(_) => return Err(CertError::KeyMismatch),
501 };
502
503 Ok(UncheckedCert {
504 cert: Ed25519Cert {
505 signed_with: Some(real_key),
506 ..self.cert.cert
507 },
508 ..self.cert
509 })
510 }
511}
512
513#[derive(Debug, Clone)]
516pub struct UncheckedCert {
517 cert: Ed25519Cert,
520
521 text: Vec<u8>,
526
527 signature: ed25519::Signature,
529}
530
531pub struct SigCheckedCert {
534 cert: Ed25519Cert,
536}
537
538impl UncheckedCert {
539 pub fn dangerously_split(
542 self,
543 ) -> CertResult<(SigCheckedCert, ed25519::ValidatableEd25519Signature)> {
544 use tor_checkable::SelfSigned;
545 let signing_key = self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
546 let signing_key = signing_key
547 .try_into()
548 .map_err(|_| CertError::BadSignature)?;
549 let signature =
550 ed25519::ValidatableEd25519Signature::new(signing_key, self.signature, &self.text[..]);
551 Ok((self.dangerously_assume_wellsigned(), signature))
552 }
553
554 pub fn peek_subject_key(&self) -> &CertifiedKey {
556 &self.cert.cert_key
557 }
558 pub fn peek_signing_key(&self) -> &ed25519::Ed25519Identity {
560 self.cert
561 .signed_with
562 .as_ref()
563 .expect("Made an UncheckedCert without a signing key")
564 }
565}
566
567impl tor_checkable::SelfSigned<SigCheckedCert> for UncheckedCert {
568 type Error = CertError;
569
570 fn is_well_signed(&self) -> CertResult<()> {
571 let pubkey = &self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
572 let pubkey: ed25519::PublicKey = pubkey.try_into().map_err(|_| CertError::BadSignature)?;
573
574 pubkey
575 .verify(&self.text[..], &self.signature)
576 .map_err(|_| CertError::BadSignature)?;
577
578 Ok(())
579 }
580
581 fn dangerously_assume_wellsigned(self) -> SigCheckedCert {
582 SigCheckedCert { cert: self.cert }
583 }
584}
585
586impl tor_checkable::Timebound<Ed25519Cert> for Ed25519Cert {
587 type Error = tor_checkable::TimeValidityError;
588
589 fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
590 if self.is_expired_at(*t) {
591 let expiry = self.expiry();
592 Err(Self::Error::Expired(
593 t.duration_since(expiry)
594 .expect("certificate expiry time inconsistent"),
595 ))
596 } else {
597 Ok(())
598 }
599 }
600
601 fn dangerously_assume_timely(self) -> Ed25519Cert {
602 self
603 }
604}
605
606impl tor_checkable::Timebound<Ed25519Cert> for SigCheckedCert {
607 type Error = tor_checkable::TimeValidityError;
608 fn is_valid_at(&self, t: &time::SystemTime) -> std::result::Result<(), Self::Error> {
609 self.cert.is_valid_at(t)
610 }
611
612 fn dangerously_assume_timely(self) -> Ed25519Cert {
613 self.cert.dangerously_assume_timely()
614 }
615}
616
617#[cfg(test)]
618mod test {
619 #![allow(clippy::bool_assert_comparison)]
621 #![allow(clippy::clone_on_copy)]
622 #![allow(clippy::dbg_macro)]
623 #![allow(clippy::mixed_attributes_style)]
624 #![allow(clippy::print_stderr)]
625 #![allow(clippy::print_stdout)]
626 #![allow(clippy::single_char_pattern)]
627 #![allow(clippy::unwrap_used)]
628 #![allow(clippy::unchecked_duration_subtraction)]
629 #![allow(clippy::useless_vec)]
630 #![allow(clippy::needless_pass_by_value)]
631 use super::*;
633 use hex_literal::hex;
634
635 #[test]
636 fn parse_unrecognized_ext() -> BytesResult<()> {
637 let b = hex!("0009 99 10 657874656e73696f6e");
639 let mut r = Reader::from_slice(&b);
640 let e: CertExt = r.extract()?;
641 r.should_be_exhausted()?;
642
643 assert_eq!(e.ext_id(), 0x99.into());
644
645 let b = hex!("0009 99 11 657874656e73696f6e");
648 let mut r = Reader::from_slice(&b);
649 let e: Result<CertExt, BytesError> = r.extract();
650 assert!(e.is_err());
651 assert_eq!(
652 e.err().unwrap(),
653 BytesError::InvalidMessage(
654 "unrecognized certificate extension, with 'affects_validation' flag set.".into()
655 )
656 );
657
658 Ok(())
659 }
660
661 #[test]
662 fn certified_key() -> BytesResult<()> {
663 let b =
664 hex!("4c27616d6f757220756e6974206365757820717527656e636861c3ae6e616974206c6520666572");
665 let mut r = Reader::from_slice(&b);
666
667 let ck = CertifiedKey::from_reader(KeyType::SHA256_OF_RSA, &mut r)?;
668 assert_eq!(ck.as_bytes(), &b[..32]);
669 assert_eq!(ck.key_type(), KeyType::SHA256_OF_RSA);
670 assert_eq!(r.remaining(), 7);
671
672 let mut r = Reader::from_slice(&b);
673 let ck = CertifiedKey::from_reader(42.into(), &mut r)?;
674 assert_eq!(ck.as_bytes(), &b[..32]);
675 assert_eq!(ck.key_type(), 42.into());
676 assert_eq!(r.remaining(), 7);
677
678 Ok(())
679 }
680}