1#![cfg_attr(docsrs, feature(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_time_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)] #![allow(clippy::collapsible_if)] #![deny(clippy::unused_async)]
47#![deny(clippy::string_slice)] mod err;
51pub mod rsa;
52
53#[cfg(feature = "x509")]
54pub use tor_cert_x509 as x509;
55
56use caret::caret_int;
57use tor_bytes::{Error as BytesError, Result as BytesResult};
58use tor_bytes::{Readable, Reader, Writeable, Writer};
59use tor_llcrypto::pk::*;
60
61use saturating_time::SaturatingTime;
62use web_time_compat as time;
63
64pub use err::CertError;
65
66mod encode;
67pub use encode::{EncodedCert, EncodedEd25519Cert};
68pub use err::CertEncodeError;
69
70type CertResult<T> = std::result::Result<T, CertError>;
72
73caret_int! {
74 pub struct CertType(u8) {
85 TLS_LINK_X509 = 0x01,
87 RSA_ID_X509 = 0x02,
89 LINK_AUTH_X509 = 0x03,
92
93 IDENTITY_V_SIGNING = 0x04,
95
96 SIGNING_V_TLS_CERT = 0x05,
98
99 SIGNING_V_LINK_AUTH = 0x06,
101
102 RSA_ID_V_IDENTITY = 0x07,
105
106 HS_BLINDED_ID_V_SIGNING = 0x08,
110
111 HS_IP_V_SIGNING = 0x09,
126
127 NTOR_CC_IDENTITY = 0x0A,
130
131 HS_IP_CC_SIGNING = 0x0B,
140
141 FAMILY_V_IDENTITY = 0x0C,
144 }
145}
146
147caret_int! {
148 pub struct ExtType(u8) {
150 SIGNED_WITH_ED25519_KEY = 0x04,
154 }
155}
156
157caret_int! {
158 pub struct KeyType(u8) {
160 ED25519_KEY = 0x01,
162 SHA256_OF_RSA = 0x02,
164 SHA256_OF_X509 = 0x03,
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, derive_builder::Builder)]
172#[builder(build_fn(skip))]
173pub struct Ed25519Cert {
174 #[builder(setter(custom))]
176 exp_hours: ExpiryHours,
177 cert_type: CertType,
179 cert_key: CertifiedKey,
181 #[allow(unused)] #[builder(setter(custom))]
184 extensions: Vec<CertExt>,
185 #[builder(setter(custom))]
191 signed_with: Option<ed25519::Ed25519Identity>,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, derive_more::From)]
196#[non_exhaustive]
197pub enum CertifiedKey {
198 Ed25519(ed25519::Ed25519Identity),
200 #[from(skip)]
202 RsaSha256Digest([u8; 32]),
203 #[from(skip)]
205 X509Sha256Digest([u8; 32]),
206 #[from(skip)]
208 Unrecognized(UnrecognizedKey),
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct UnrecognizedKey {
214 key_type: KeyType,
216 key_digest: [u8; 32],
218}
219
220impl CertifiedKey {
221 pub fn key_type(&self) -> KeyType {
223 match self {
224 CertifiedKey::Ed25519(_) => KeyType::ED25519_KEY,
225 CertifiedKey::RsaSha256Digest(_) => KeyType::SHA256_OF_RSA,
226 CertifiedKey::X509Sha256Digest(_) => KeyType::SHA256_OF_X509,
227
228 CertifiedKey::Unrecognized(u) => u.key_type,
229 }
230 }
231 pub fn as_bytes(&self) -> &[u8] {
234 match self {
235 CertifiedKey::Ed25519(k) => k.as_bytes(),
236 CertifiedKey::RsaSha256Digest(k) => &k[..],
237 CertifiedKey::X509Sha256Digest(k) => &k[..],
238 CertifiedKey::Unrecognized(u) => &u.key_digest[..],
239 }
240 }
241 pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
244 match self {
245 CertifiedKey::Ed25519(k) => Some(k),
246 _ => None,
247 }
248 }
249 fn from_reader(key_type: KeyType, r: &mut Reader<'_>) -> BytesResult<Self> {
252 Ok(match key_type {
253 KeyType::ED25519_KEY => CertifiedKey::Ed25519(r.extract()?),
254 KeyType::SHA256_OF_RSA => CertifiedKey::RsaSha256Digest(r.extract()?),
255 KeyType::SHA256_OF_X509 => CertifiedKey::X509Sha256Digest(r.extract()?),
256 _ => CertifiedKey::Unrecognized(UnrecognizedKey {
257 key_type,
258 key_digest: r.extract()?,
259 }),
260 })
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Eq)]
266enum CertExt {
267 SignedWithEd25519(SignedWithEd25519Ext),
269 Unrecognized(UnrecognizedExt),
271}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
275#[allow(unused)]
276struct UnrecognizedExt {
277 affects_validation: bool,
280 ext_type: ExtType,
282 body: Vec<u8>,
284}
285
286impl CertExt {
287 fn ext_id(&self) -> ExtType {
289 match self {
290 CertExt::SignedWithEd25519(_) => ExtType::SIGNED_WITH_ED25519_KEY,
291 CertExt::Unrecognized(u) => u.ext_type,
292 }
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
298struct SignedWithEd25519Ext {
299 pk: ed25519::Ed25519Identity,
301}
302
303impl Readable for CertExt {
304 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
305 let len = b.take_u16()?;
306 let ext_type: ExtType = b.take_u8()?.into();
307 let flags = b.take_u8()?;
308 let body = b.take(len as usize)?;
309
310 Ok(match ext_type {
311 ExtType::SIGNED_WITH_ED25519_KEY => CertExt::SignedWithEd25519(SignedWithEd25519Ext {
312 pk: ed25519::Ed25519Identity::from_bytes(body).ok_or_else(|| {
313 BytesError::InvalidMessage("wrong length on Ed25519 key".into())
314 })?,
315 }),
316 _ => {
317 if (flags & 1) != 0 {
318 return Err(BytesError::InvalidMessage(
319 "unrecognized certificate extension, with 'affects_validation' flag set."
320 .into(),
321 ));
322 }
323 CertExt::Unrecognized(UnrecognizedExt {
324 affects_validation: false,
325 ext_type,
326 body: body.into(),
327 })
328 }
329 })
330 }
331}
332
333impl Writeable for KeyUnknownCert {
334 fn write_onto<B: Writer + ?Sized>(&self, b: &mut B) -> Result<(), tor_bytes::EncodeError> {
335 self.cert.write_onto(b)
336 }
337}
338
339impl Readable for KeyUnknownCert {
340 fn take_from(r: &mut Reader<'_>) -> BytesResult<KeyUnknownCert> {
341 let b = r.take_rest();
342 Ed25519Cert::decode(b)
343 }
344}
345
346impl Ed25519Cert {
347 pub fn decode(cert: &[u8]) -> BytesResult<KeyUnknownCert> {
356 let mut r = Reader::from_slice(cert);
357 let v = r.take_u8()?;
358 if v != 1 {
359 return Err(BytesError::InvalidMessage(
362 "Unrecognized certificate version".into(),
363 ));
364 }
365 let cert_type = r.take_u8()?.into();
366 let exp_hours = r.extract()?;
367 let mut cert_key_type = r.take_u8()?.into();
368
369 if cert_type == CertType::SIGNING_V_TLS_CERT && cert_key_type == KeyType::ED25519_KEY {
373 cert_key_type = KeyType::SHA256_OF_X509;
374 }
375
376 let cert_key = CertifiedKey::from_reader(cert_key_type, &mut r)?;
377 let n_exts = r.take_u8()?;
378 let mut extensions = Vec::new();
379 for _ in 0..n_exts {
380 let e: CertExt = r.extract()?;
381 extensions.push(e);
382 }
383
384 let sig_offset = r.consumed();
385 let signature: ed25519::Signature = r.extract()?;
386 r.should_be_exhausted()?;
387 let keyext = extensions
390 .iter()
391 .find(|e| e.ext_id() == ExtType::SIGNED_WITH_ED25519_KEY);
392
393 let included_pkey = match keyext {
394 Some(CertExt::SignedWithEd25519(s)) => Some(s.pk),
395 _ => None,
396 };
397
398 Ok(KeyUnknownCert {
399 cert: UncheckedCert {
400 cert: Ed25519Cert {
401 exp_hours,
402 cert_type,
403 cert_key,
404 extensions,
405
406 signed_with: included_pkey,
407 },
408 text: cert[0..sig_offset].into(),
409 signature,
410 },
411 })
412 }
413
414 pub fn expiry(&self) -> std::time::SystemTime {
416 self.exp_hours.into()
417 }
418
419 pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
423 when > self.expiry()
424 }
425
426 pub fn subject_key(&self) -> &CertifiedKey {
429 &self.cert_key
430 }
431
432 pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
434 self.signed_with.as_ref()
435 }
436
437 pub fn cert_type(&self) -> CertType {
439 self.cert_type
440 }
441}
442
443#[derive(Clone, Debug, PartialEq, Eq)]
452pub struct KeyUnknownCert {
453 cert: UncheckedCert,
455}
456
457impl KeyUnknownCert {
458 pub fn peek_cert_type(&self) -> CertType {
460 self.cert.cert.cert_type
461 }
462 pub fn peek_subject_key(&self) -> &CertifiedKey {
464 &self.cert.cert.cert_key
465 }
466
467 #[deprecated(
475 since = "0.7.1",
476 note = "Use should_have_signing_key or should_be_signed_with instead."
477 )]
478 pub fn check_key(self, pkey: Option<&ed25519::Ed25519Identity>) -> CertResult<UncheckedCert> {
479 match pkey {
480 Some(wanted) => self.should_be_signed_with(wanted),
481 None => self.should_have_signing_key(),
482 }
483 }
484
485 pub fn should_have_signing_key(self) -> CertResult<UncheckedCert> {
492 let real_key = match &self.cert.cert.signed_with {
493 Some(a) => *a,
494 None => return Err(CertError::MissingPubKey),
495 };
496
497 Ok(UncheckedCert {
498 cert: Ed25519Cert {
499 signed_with: Some(real_key),
500 ..self.cert.cert
501 },
502 ..self.cert
503 })
504 }
505
506 pub fn should_be_signed_with(
512 self,
513 pkey: &ed25519::Ed25519Identity,
514 ) -> CertResult<UncheckedCert> {
515 let real_key = match &self.cert.cert.signed_with {
516 Some(a) if a == pkey => *pkey,
517 None => *pkey,
518 Some(_) => return Err(CertError::KeyMismatch),
519 };
520
521 Ok(UncheckedCert {
522 cert: Ed25519Cert {
523 signed_with: Some(real_key),
524 ..self.cert.cert
525 },
526 ..self.cert
527 })
528 }
529}
530
531#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct UncheckedCert {
535 cert: Ed25519Cert,
538
539 text: Vec<u8>,
544
545 signature: ed25519::Signature,
547}
548
549#[derive(Debug, Clone, PartialEq, Eq)]
552pub struct SigCheckedCert {
553 cert: Ed25519Cert,
555}
556
557impl UncheckedCert {
558 pub fn dangerously_split(
561 self,
562 ) -> CertResult<(SigCheckedCert, ed25519::ValidatableEd25519Signature)> {
563 use tor_checkable::SelfSigned;
564 let signing_key = self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
565 let signing_key = signing_key
566 .try_into()
567 .map_err(|_| CertError::BadSignature)?;
568 let signature =
569 ed25519::ValidatableEd25519Signature::new(signing_key, self.signature, &self.text[..]);
570 Ok((self.dangerously_assume_wellsigned(), signature))
571 }
572
573 pub fn peek_subject_key(&self) -> &CertifiedKey {
575 &self.cert.cert_key
576 }
577 pub fn peek_signing_key(&self) -> &ed25519::Ed25519Identity {
579 self.cert
580 .signed_with
581 .as_ref()
582 .expect("Made an UncheckedCert without a signing key")
583 }
584}
585
586impl Writeable for UncheckedCert {
587 fn write_onto<B: Writer + ?Sized>(&self, b: &mut B) -> Result<(), tor_bytes::EncodeError> {
594 self.text.write_onto(b)?;
600 self.signature.write_onto(b)?;
601 Ok(())
602 }
603}
604
605impl tor_checkable::SelfSigned<SigCheckedCert> for UncheckedCert {
606 type Error = CertError;
607
608 fn is_well_signed(&self) -> CertResult<()> {
609 let pubkey = &self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
610 let pubkey: ed25519::PublicKey = pubkey.try_into().map_err(|_| CertError::BadSignature)?;
611
612 pubkey
613 .verify(&self.text[..], &self.signature)
614 .map_err(|_| CertError::BadSignature)?;
615
616 Ok(())
617 }
618
619 fn dangerously_assume_wellsigned(self) -> SigCheckedCert {
620 SigCheckedCert { cert: self.cert }
621 }
622}
623
624impl tor_checkable::Timebound<Ed25519Cert> for Ed25519Cert {
625 type Error = tor_checkable::TimeValidityError;
626
627 #[allow(unstable_name_collisions)]
628 fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
629 if self.is_expired_at(*t) {
630 let expiry = self.expiry();
631 Err(Self::Error::Expired(t.saturating_duration_since(expiry)))
632 } else {
633 Ok(())
634 }
635 }
636
637 fn dangerously_assume_timely(self) -> Ed25519Cert {
638 self
639 }
640}
641
642impl tor_checkable::Timebound<Ed25519Cert> for SigCheckedCert {
643 type Error = tor_checkable::TimeValidityError;
644 fn is_valid_at(&self, t: &time::SystemTime) -> std::result::Result<(), Self::Error> {
645 self.cert.is_valid_at(t)
646 }
647
648 fn dangerously_assume_timely(self) -> Ed25519Cert {
649 self.cert.dangerously_assume_timely()
650 }
651}
652
653#[derive(Debug, Clone, Copy, PartialEq, Eq)]
655struct ExpiryHours(u32);
656
657const SEC_PER_HOUR: u64 = 3600;
659
660impl From<ExpiryHours> for time::SystemTime {
661 fn from(value: ExpiryHours) -> Self {
662 let d = std::time::Duration::from_secs(u64::from(value.0) * SEC_PER_HOUR);
664 std::time::SystemTime::UNIX_EPOCH + d
665 }
666}
667
668impl ExpiryHours {
669 fn try_from_systemtime_ceil(expiry: time::SystemTime) -> Result<Self, CertEncodeError> {
671 let d = expiry
672 .duration_since(time::SystemTime::UNIX_EPOCH)
673 .map_err(|_| CertEncodeError::InvalidExpiration)?;
674 let sec_ceil = d.as_secs() + if d.subsec_nanos() > 0 { 1 } else { 0 };
675 let hours = sec_ceil
676 .div_ceil(SEC_PER_HOUR)
677 .try_into()
678 .map_err(|_| CertEncodeError::InvalidExpiration)?;
679 Ok(ExpiryHours(hours))
680 }
681
682 const fn max() -> Self {
684 ExpiryHours(u32::MAX)
685 }
686}
687
688impl Readable for ExpiryHours {
689 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
690 Ok(ExpiryHours(b.take_u32()?))
691 }
692}
693
694impl tor_bytes::Writeable for ExpiryHours {
695 fn write_onto<B: tor_bytes::Writer + ?Sized>(&self, b: &mut B) -> tor_bytes::EncodeResult<()> {
696 b.write_u32(self.0);
697 Ok(())
698 }
699}
700
701#[cfg(test)]
702mod test {
703 #![allow(clippy::bool_assert_comparison)]
705 #![allow(clippy::clone_on_copy)]
706 #![allow(clippy::dbg_macro)]
707 #![allow(clippy::mixed_attributes_style)]
708 #![allow(clippy::print_stderr)]
709 #![allow(clippy::print_stdout)]
710 #![allow(clippy::single_char_pattern)]
711 #![allow(clippy::unwrap_used)]
712 #![allow(clippy::unchecked_time_subtraction)]
713 #![allow(clippy::useless_vec)]
714 #![allow(clippy::needless_pass_by_value)]
715 #![allow(clippy::string_slice)] use super::*;
718 use hex_literal::hex;
719 use web_time_compat::SystemTimeExt;
720
721 #[test]
722 fn parse_unrecognized_ext() -> BytesResult<()> {
723 let b = hex!("0009 99 10 657874656e73696f6e");
725 let mut r = Reader::from_slice(&b);
726 let e: CertExt = r.extract()?;
727 r.should_be_exhausted()?;
728
729 assert_eq!(e.ext_id(), 0x99.into());
730
731 let b = hex!("0009 99 11 657874656e73696f6e");
734 let mut r = Reader::from_slice(&b);
735 let e: Result<CertExt, BytesError> = r.extract();
736 assert!(e.is_err());
737 assert_eq!(
738 e.err().unwrap(),
739 BytesError::InvalidMessage(
740 "unrecognized certificate extension, with 'affects_validation' flag set.".into()
741 )
742 );
743
744 Ok(())
745 }
746
747 #[test]
748 fn certified_key() -> BytesResult<()> {
749 let b =
750 hex!("4c27616d6f757220756e6974206365757820717527656e636861c3ae6e616974206c6520666572");
751 let mut r = Reader::from_slice(&b);
752
753 let ck = CertifiedKey::from_reader(KeyType::SHA256_OF_RSA, &mut r)?;
754 assert_eq!(ck.as_bytes(), &b[..32]);
755 assert_eq!(ck.key_type(), KeyType::SHA256_OF_RSA);
756 assert_eq!(r.remaining(), 7);
757
758 let mut r = Reader::from_slice(&b);
759 let ck = CertifiedKey::from_reader(42.into(), &mut r)?;
760 assert_eq!(ck.as_bytes(), &b[..32]);
761 assert_eq!(ck.key_type(), 42.into());
762 assert_eq!(r.remaining(), 7);
763
764 Ok(())
765 }
766
767 #[test]
768 fn expiry_hours_ceil() {
769 use std::time::{Duration, SystemTime};
770
771 let now = SystemTime::get();
772 let mut exp = now + Duration::from_secs(24 * 60 * 60);
773 for _ in 0..=3600 {
774 let eh = ExpiryHours::try_from_systemtime_ceil(exp).unwrap();
775 assert!(SystemTime::from(eh) >= exp);
776 assert!(SystemTime::from(eh) < exp + Duration::from_secs(SEC_PER_HOUR));
777
778 exp += Duration::from_secs(1);
779 }
780 }
781}