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)]
47mod err;
50pub mod rsa;
51
52#[cfg(feature = "x509")]
53pub use tor_cert_x509 as x509;
54
55use caret::caret_int;
56use tor_bytes::{Error as BytesError, Result as BytesResult};
57use tor_bytes::{Readable, Reader, Writeable, Writer};
58use tor_llcrypto::pk::*;
59
60use web_time_compat as time;
61
62pub use err::CertError;
63
64mod encode;
65pub use encode::{EncodedCert, EncodedEd25519Cert};
66pub use err::CertEncodeError;
67
68type CertResult<T> = std::result::Result<T, CertError>;
70
71caret_int! {
72 pub struct CertType(u8) {
83 TLS_LINK_X509 = 0x01,
85 RSA_ID_X509 = 0x02,
87 LINK_AUTH_X509 = 0x03,
90
91 IDENTITY_V_SIGNING = 0x04,
93
94 SIGNING_V_TLS_CERT = 0x05,
96
97 SIGNING_V_LINK_AUTH = 0x06,
99
100 RSA_ID_V_IDENTITY = 0x07,
103
104 HS_BLINDED_ID_V_SIGNING = 0x08,
108
109 HS_IP_V_SIGNING = 0x09,
124
125 NTOR_CC_IDENTITY = 0x0A,
128
129 HS_IP_CC_SIGNING = 0x0B,
138
139 FAMILY_V_IDENTITY = 0x0C,
142 }
143}
144
145caret_int! {
146 pub struct ExtType(u8) {
148 SIGNED_WITH_ED25519_KEY = 0x04,
152 }
153}
154
155caret_int! {
156 pub struct KeyType(u8) {
158 ED25519_KEY = 0x01,
160 SHA256_OF_RSA = 0x02,
162 SHA256_OF_X509 = 0x03,
164 }
165}
166
167#[derive(Debug, Clone, derive_builder::Builder)]
170#[builder(build_fn(skip))]
171pub struct Ed25519Cert {
172 #[builder(setter(custom))]
174 exp_hours: ExpiryHours,
175 cert_type: CertType,
177 cert_key: CertifiedKey,
179 #[allow(unused)] #[builder(setter(custom))]
182 extensions: Vec<CertExt>,
183 #[builder(setter(custom))]
189 signed_with: Option<ed25519::Ed25519Identity>,
190}
191
192#[derive(Debug, Clone, derive_more::From)]
194#[non_exhaustive]
195pub enum CertifiedKey {
196 Ed25519(ed25519::Ed25519Identity),
198 #[from(skip)]
200 RsaSha256Digest([u8; 32]),
201 #[from(skip)]
203 X509Sha256Digest([u8; 32]),
204 #[from(skip)]
206 Unrecognized(UnrecognizedKey),
207}
208
209#[derive(Debug, Clone)]
211pub struct UnrecognizedKey {
212 key_type: KeyType,
214 key_digest: [u8; 32],
216}
217
218impl CertifiedKey {
219 pub fn key_type(&self) -> KeyType {
221 match self {
222 CertifiedKey::Ed25519(_) => KeyType::ED25519_KEY,
223 CertifiedKey::RsaSha256Digest(_) => KeyType::SHA256_OF_RSA,
224 CertifiedKey::X509Sha256Digest(_) => KeyType::SHA256_OF_X509,
225
226 CertifiedKey::Unrecognized(u) => u.key_type,
227 }
228 }
229 pub fn as_bytes(&self) -> &[u8] {
232 match self {
233 CertifiedKey::Ed25519(k) => k.as_bytes(),
234 CertifiedKey::RsaSha256Digest(k) => &k[..],
235 CertifiedKey::X509Sha256Digest(k) => &k[..],
236 CertifiedKey::Unrecognized(u) => &u.key_digest[..],
237 }
238 }
239 pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
242 match self {
243 CertifiedKey::Ed25519(k) => Some(k),
244 _ => None,
245 }
246 }
247 fn from_reader(key_type: KeyType, r: &mut Reader<'_>) -> BytesResult<Self> {
250 Ok(match key_type {
251 KeyType::ED25519_KEY => CertifiedKey::Ed25519(r.extract()?),
252 KeyType::SHA256_OF_RSA => CertifiedKey::RsaSha256Digest(r.extract()?),
253 KeyType::SHA256_OF_X509 => CertifiedKey::X509Sha256Digest(r.extract()?),
254 _ => CertifiedKey::Unrecognized(UnrecognizedKey {
255 key_type,
256 key_digest: r.extract()?,
257 }),
258 })
259 }
260}
261
262#[derive(Debug, Clone)]
264enum CertExt {
265 SignedWithEd25519(SignedWithEd25519Ext),
267 Unrecognized(UnrecognizedExt),
269}
270
271#[derive(Debug, Clone)]
273#[allow(unused)]
274struct UnrecognizedExt {
275 affects_validation: bool,
278 ext_type: ExtType,
280 body: Vec<u8>,
282}
283
284impl CertExt {
285 fn ext_id(&self) -> ExtType {
287 match self {
288 CertExt::SignedWithEd25519(_) => ExtType::SIGNED_WITH_ED25519_KEY,
289 CertExt::Unrecognized(u) => u.ext_type,
290 }
291 }
292}
293
294#[derive(Debug, Clone)]
296struct SignedWithEd25519Ext {
297 pk: ed25519::Ed25519Identity,
299}
300
301impl Readable for CertExt {
302 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
303 let len = b.take_u16()?;
304 let ext_type: ExtType = b.take_u8()?.into();
305 let flags = b.take_u8()?;
306 let body = b.take(len as usize)?;
307
308 Ok(match ext_type {
309 ExtType::SIGNED_WITH_ED25519_KEY => CertExt::SignedWithEd25519(SignedWithEd25519Ext {
310 pk: ed25519::Ed25519Identity::from_bytes(body).ok_or_else(|| {
311 BytesError::InvalidMessage("wrong length on Ed25519 key".into())
312 })?,
313 }),
314 _ => {
315 if (flags & 1) != 0 {
316 return Err(BytesError::InvalidMessage(
317 "unrecognized certificate extension, with 'affects_validation' flag set."
318 .into(),
319 ));
320 }
321 CertExt::Unrecognized(UnrecognizedExt {
322 affects_validation: false,
323 ext_type,
324 body: body.into(),
325 })
326 }
327 })
328 }
329}
330
331impl Writeable for KeyUnknownCert {
332 fn write_onto<B: Writer + ?Sized>(&self, b: &mut B) -> Result<(), tor_bytes::EncodeError> {
333 self.cert.write_onto(b)
334 }
335}
336
337impl Readable for KeyUnknownCert {
338 fn take_from(r: &mut Reader<'_>) -> BytesResult<KeyUnknownCert> {
339 let b = r.take_rest();
340 Ed25519Cert::decode(b)
341 }
342}
343
344impl Ed25519Cert {
345 pub fn decode(cert: &[u8]) -> BytesResult<KeyUnknownCert> {
354 let mut r = Reader::from_slice(cert);
355 let v = r.take_u8()?;
356 if v != 1 {
357 return Err(BytesError::InvalidMessage(
360 "Unrecognized certificate version".into(),
361 ));
362 }
363 let cert_type = r.take_u8()?.into();
364 let exp_hours = r.extract()?;
365 let mut cert_key_type = r.take_u8()?.into();
366
367 if cert_type == CertType::SIGNING_V_TLS_CERT && cert_key_type == KeyType::ED25519_KEY {
371 cert_key_type = KeyType::SHA256_OF_X509;
372 }
373
374 let cert_key = CertifiedKey::from_reader(cert_key_type, &mut r)?;
375 let n_exts = r.take_u8()?;
376 let mut extensions = Vec::new();
377 for _ in 0..n_exts {
378 let e: CertExt = r.extract()?;
379 extensions.push(e);
380 }
381
382 let sig_offset = r.consumed();
383 let signature: ed25519::Signature = r.extract()?;
384 r.should_be_exhausted()?;
385 let keyext = extensions
388 .iter()
389 .find(|e| e.ext_id() == ExtType::SIGNED_WITH_ED25519_KEY);
390
391 let included_pkey = match keyext {
392 Some(CertExt::SignedWithEd25519(s)) => Some(s.pk),
393 _ => None,
394 };
395
396 Ok(KeyUnknownCert {
397 cert: UncheckedCert {
398 cert: Ed25519Cert {
399 exp_hours,
400 cert_type,
401 cert_key,
402 extensions,
403
404 signed_with: included_pkey,
405 },
406 text: cert[0..sig_offset].into(),
407 signature,
408 },
409 })
410 }
411
412 pub fn expiry(&self) -> std::time::SystemTime {
414 self.exp_hours.into()
415 }
416
417 pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
419 when >= self.expiry()
420 }
421
422 pub fn subject_key(&self) -> &CertifiedKey {
425 &self.cert_key
426 }
427
428 pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
430 self.signed_with.as_ref()
431 }
432
433 pub fn cert_type(&self) -> CertType {
435 self.cert_type
436 }
437}
438
439#[derive(Clone, Debug)]
448pub struct KeyUnknownCert {
449 cert: UncheckedCert,
451}
452
453impl KeyUnknownCert {
454 pub fn peek_cert_type(&self) -> CertType {
456 self.cert.cert.cert_type
457 }
458 pub fn peek_subject_key(&self) -> &CertifiedKey {
460 &self.cert.cert.cert_key
461 }
462
463 #[deprecated(
471 since = "0.7.1",
472 note = "Use should_have_signing_key or should_be_signed_with instead."
473 )]
474 pub fn check_key(self, pkey: Option<&ed25519::Ed25519Identity>) -> CertResult<UncheckedCert> {
475 match pkey {
476 Some(wanted) => self.should_be_signed_with(wanted),
477 None => self.should_have_signing_key(),
478 }
479 }
480
481 pub fn should_have_signing_key(self) -> CertResult<UncheckedCert> {
488 let real_key = match &self.cert.cert.signed_with {
489 Some(a) => *a,
490 None => return Err(CertError::MissingPubKey),
491 };
492
493 Ok(UncheckedCert {
494 cert: Ed25519Cert {
495 signed_with: Some(real_key),
496 ..self.cert.cert
497 },
498 ..self.cert
499 })
500 }
501
502 pub fn should_be_signed_with(
508 self,
509 pkey: &ed25519::Ed25519Identity,
510 ) -> CertResult<UncheckedCert> {
511 let real_key = match &self.cert.cert.signed_with {
512 Some(a) if a == pkey => *pkey,
513 None => *pkey,
514 Some(_) => return Err(CertError::KeyMismatch),
515 };
516
517 Ok(UncheckedCert {
518 cert: Ed25519Cert {
519 signed_with: Some(real_key),
520 ..self.cert.cert
521 },
522 ..self.cert
523 })
524 }
525}
526
527#[derive(Debug, Clone)]
530pub struct UncheckedCert {
531 cert: Ed25519Cert,
534
535 text: Vec<u8>,
540
541 signature: ed25519::Signature,
543}
544
545#[derive(Debug, Clone)]
548pub struct SigCheckedCert {
549 cert: Ed25519Cert,
551}
552
553impl UncheckedCert {
554 pub fn dangerously_split(
557 self,
558 ) -> CertResult<(SigCheckedCert, ed25519::ValidatableEd25519Signature)> {
559 use tor_checkable::SelfSigned;
560 let signing_key = self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
561 let signing_key = signing_key
562 .try_into()
563 .map_err(|_| CertError::BadSignature)?;
564 let signature =
565 ed25519::ValidatableEd25519Signature::new(signing_key, self.signature, &self.text[..]);
566 Ok((self.dangerously_assume_wellsigned(), signature))
567 }
568
569 pub fn peek_subject_key(&self) -> &CertifiedKey {
571 &self.cert.cert_key
572 }
573 pub fn peek_signing_key(&self) -> &ed25519::Ed25519Identity {
575 self.cert
576 .signed_with
577 .as_ref()
578 .expect("Made an UncheckedCert without a signing key")
579 }
580}
581
582impl Writeable for UncheckedCert {
583 fn write_onto<B: Writer + ?Sized>(&self, b: &mut B) -> Result<(), tor_bytes::EncodeError> {
590 self.text.write_onto(b)?;
596 self.signature.write_onto(b)?;
597 Ok(())
598 }
599}
600
601impl tor_checkable::SelfSigned<SigCheckedCert> for UncheckedCert {
602 type Error = CertError;
603
604 fn is_well_signed(&self) -> CertResult<()> {
605 let pubkey = &self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
606 let pubkey: ed25519::PublicKey = pubkey.try_into().map_err(|_| CertError::BadSignature)?;
607
608 pubkey
609 .verify(&self.text[..], &self.signature)
610 .map_err(|_| CertError::BadSignature)?;
611
612 Ok(())
613 }
614
615 fn dangerously_assume_wellsigned(self) -> SigCheckedCert {
616 SigCheckedCert { cert: self.cert }
617 }
618}
619
620impl tor_checkable::Timebound<Ed25519Cert> for Ed25519Cert {
621 type Error = tor_checkable::TimeValidityError;
622
623 fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
624 if self.is_expired_at(*t) {
625 let expiry = self.expiry();
626 Err(Self::Error::Expired(
627 t.duration_since(expiry)
628 .expect("certificate expiry time inconsistent"),
629 ))
630 } else {
631 Ok(())
632 }
633 }
634
635 fn dangerously_assume_timely(self) -> Ed25519Cert {
636 self
637 }
638}
639
640impl tor_checkable::Timebound<Ed25519Cert> for SigCheckedCert {
641 type Error = tor_checkable::TimeValidityError;
642 fn is_valid_at(&self, t: &time::SystemTime) -> std::result::Result<(), Self::Error> {
643 self.cert.is_valid_at(t)
644 }
645
646 fn dangerously_assume_timely(self) -> Ed25519Cert {
647 self.cert.dangerously_assume_timely()
648 }
649}
650
651#[derive(Debug, Clone, Copy)]
653struct ExpiryHours(u32);
654
655const SEC_PER_HOUR: u64 = 3600;
657
658impl From<ExpiryHours> for time::SystemTime {
659 fn from(value: ExpiryHours) -> Self {
660 let d = std::time::Duration::from_secs(u64::from(value.0) * SEC_PER_HOUR);
662 std::time::SystemTime::UNIX_EPOCH + d
663 }
664}
665
666impl ExpiryHours {
667 fn try_from_systemtime_ceil(expiry: time::SystemTime) -> Result<Self, CertEncodeError> {
669 let d = expiry
670 .duration_since(time::SystemTime::UNIX_EPOCH)
671 .map_err(|_| CertEncodeError::InvalidExpiration)?;
672 let sec_ceil = d.as_secs() + if d.subsec_nanos() > 0 { 1 } else { 0 };
673 let hours = sec_ceil
674 .div_ceil(SEC_PER_HOUR)
675 .try_into()
676 .map_err(|_| CertEncodeError::InvalidExpiration)?;
677 Ok(ExpiryHours(hours))
678 }
679
680 const fn max() -> Self {
682 ExpiryHours(u32::MAX)
683 }
684}
685
686impl Readable for ExpiryHours {
687 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
688 Ok(ExpiryHours(b.take_u32()?))
689 }
690}
691
692impl tor_bytes::Writeable for ExpiryHours {
693 fn write_onto<B: tor_bytes::Writer + ?Sized>(&self, b: &mut B) -> tor_bytes::EncodeResult<()> {
694 b.write_u32(self.0);
695 Ok(())
696 }
697}
698
699#[cfg(test)]
700mod test {
701 #![allow(clippy::bool_assert_comparison)]
703 #![allow(clippy::clone_on_copy)]
704 #![allow(clippy::dbg_macro)]
705 #![allow(clippy::mixed_attributes_style)]
706 #![allow(clippy::print_stderr)]
707 #![allow(clippy::print_stdout)]
708 #![allow(clippy::single_char_pattern)]
709 #![allow(clippy::unwrap_used)]
710 #![allow(clippy::unchecked_time_subtraction)]
711 #![allow(clippy::useless_vec)]
712 #![allow(clippy::needless_pass_by_value)]
713 use super::*;
715 use hex_literal::hex;
716 use web_time_compat::SystemTimeExt;
717
718 #[test]
719 fn parse_unrecognized_ext() -> BytesResult<()> {
720 let b = hex!("0009 99 10 657874656e73696f6e");
722 let mut r = Reader::from_slice(&b);
723 let e: CertExt = r.extract()?;
724 r.should_be_exhausted()?;
725
726 assert_eq!(e.ext_id(), 0x99.into());
727
728 let b = hex!("0009 99 11 657874656e73696f6e");
731 let mut r = Reader::from_slice(&b);
732 let e: Result<CertExt, BytesError> = r.extract();
733 assert!(e.is_err());
734 assert_eq!(
735 e.err().unwrap(),
736 BytesError::InvalidMessage(
737 "unrecognized certificate extension, with 'affects_validation' flag set.".into()
738 )
739 );
740
741 Ok(())
742 }
743
744 #[test]
745 fn certified_key() -> BytesResult<()> {
746 let b =
747 hex!("4c27616d6f757220756e6974206365757820717527656e636861c3ae6e616974206c6520666572");
748 let mut r = Reader::from_slice(&b);
749
750 let ck = CertifiedKey::from_reader(KeyType::SHA256_OF_RSA, &mut r)?;
751 assert_eq!(ck.as_bytes(), &b[..32]);
752 assert_eq!(ck.key_type(), KeyType::SHA256_OF_RSA);
753 assert_eq!(r.remaining(), 7);
754
755 let mut r = Reader::from_slice(&b);
756 let ck = CertifiedKey::from_reader(42.into(), &mut r)?;
757 assert_eq!(ck.as_bytes(), &b[..32]);
758 assert_eq!(ck.key_type(), 42.into());
759 assert_eq!(r.remaining(), 7);
760
761 Ok(())
762 }
763
764 #[test]
765 fn expiry_hours_ceil() {
766 use std::time::{Duration, SystemTime};
767
768 let now = SystemTime::get();
769 let mut exp = now + Duration::from_secs(24 * 60 * 60);
770 for _ in 0..=3600 {
771 let eh = ExpiryHours::try_from_systemtime_ceil(exp).unwrap();
772 assert!(SystemTime::from(eh) >= exp);
773 assert!(SystemTime::from(eh) < exp + Duration::from_secs(SEC_PER_HOUR));
774
775 exp += Duration::from_secs(1);
776 }
777 }
778}