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#![allow(clippy::cognitive_complexity)] #![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_checkable::{TimeRange, TimeRangeBound};
60use tor_llcrypto::pk::*;
61
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 for Ed25519Cert {
625 type Inner = Ed25519Cert;
626
627 fn bounds(&self) -> TimeRange {
628 TimeRangeBound::new((), ..=self.expiry())
629 }
630
631 fn dangerously_assume_timely(self) -> Ed25519Cert {
632 self
633 }
634}
635
636impl tor_checkable::TimeBound for SigCheckedCert {
637 type Inner = Ed25519Cert;
638
639 fn bounds(&self) -> TimeRange {
640 self.cert.bounds()
641 }
642
643 fn dangerously_assume_timely(self) -> Ed25519Cert {
644 self.cert.dangerously_assume_timely()
645 }
646}
647
648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
650struct ExpiryHours(u32);
651
652const SEC_PER_HOUR: u64 = 3600;
654
655impl From<ExpiryHours> for time::SystemTime {
656 fn from(value: ExpiryHours) -> Self {
657 let d = std::time::Duration::from_secs(u64::from(value.0) * SEC_PER_HOUR);
659 std::time::SystemTime::UNIX_EPOCH + d
660 }
661}
662
663impl ExpiryHours {
664 fn try_from_systemtime_ceil(expiry: time::SystemTime) -> Result<Self, CertEncodeError> {
666 let d = expiry
667 .duration_since(time::SystemTime::UNIX_EPOCH)
668 .map_err(|_| CertEncodeError::InvalidExpiration)?;
669 let sec_ceil = d.as_secs() + if d.subsec_nanos() > 0 { 1 } else { 0 };
670 let hours = sec_ceil
671 .div_ceil(SEC_PER_HOUR)
672 .try_into()
673 .map_err(|_| CertEncodeError::InvalidExpiration)?;
674 Ok(ExpiryHours(hours))
675 }
676
677 const fn max() -> Self {
679 ExpiryHours(u32::MAX)
680 }
681}
682
683impl Readable for ExpiryHours {
684 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
685 Ok(ExpiryHours(b.take_u32()?))
686 }
687}
688
689impl tor_bytes::Writeable for ExpiryHours {
690 fn write_onto<B: tor_bytes::Writer + ?Sized>(&self, b: &mut B) -> tor_bytes::EncodeResult<()> {
691 b.write_u32(self.0);
692 Ok(())
693 }
694}
695
696#[cfg(test)]
697mod test {
698 #![allow(clippy::bool_assert_comparison)]
700 #![allow(clippy::clone_on_copy)]
701 #![allow(clippy::dbg_macro)]
702 #![allow(clippy::mixed_attributes_style)]
703 #![allow(clippy::print_stderr)]
704 #![allow(clippy::print_stdout)]
705 #![allow(clippy::single_char_pattern)]
706 #![allow(clippy::unwrap_used)]
707 #![allow(clippy::unchecked_time_subtraction)]
708 #![allow(clippy::useless_vec)]
709 #![allow(clippy::needless_pass_by_value)]
710 #![allow(clippy::string_slice)] use super::*;
713 use hex_literal::hex;
714 use web_time_compat::SystemTimeExt;
715
716 #[test]
717 fn parse_unrecognized_ext() -> BytesResult<()> {
718 let b = hex!("0009 99 10 657874656e73696f6e");
720 let mut r = Reader::from_slice(&b);
721 let e: CertExt = r.extract()?;
722 r.should_be_exhausted()?;
723
724 assert_eq!(e.ext_id(), 0x99.into());
725
726 let b = hex!("0009 99 11 657874656e73696f6e");
729 let mut r = Reader::from_slice(&b);
730 let e: Result<CertExt, BytesError> = r.extract();
731 assert!(e.is_err());
732 assert_eq!(
733 e.err().unwrap(),
734 BytesError::InvalidMessage(
735 "unrecognized certificate extension, with 'affects_validation' flag set.".into()
736 )
737 );
738
739 Ok(())
740 }
741
742 #[test]
743 fn certified_key() -> BytesResult<()> {
744 let b =
745 hex!("4c27616d6f757220756e6974206365757820717527656e636861c3ae6e616974206c6520666572");
746 let mut r = Reader::from_slice(&b);
747
748 let ck = CertifiedKey::from_reader(KeyType::SHA256_OF_RSA, &mut r)?;
749 assert_eq!(ck.as_bytes(), &b[..32]);
750 assert_eq!(ck.key_type(), KeyType::SHA256_OF_RSA);
751 assert_eq!(r.remaining(), 7);
752
753 let mut r = Reader::from_slice(&b);
754 let ck = CertifiedKey::from_reader(42.into(), &mut r)?;
755 assert_eq!(ck.as_bytes(), &b[..32]);
756 assert_eq!(ck.key_type(), 42.into());
757 assert_eq!(r.remaining(), 7);
758
759 Ok(())
760 }
761
762 #[test]
763 fn expiry_hours_ceil() {
764 use std::time::{Duration, SystemTime};
765
766 let now = SystemTime::get();
767 let mut exp = now + Duration::from_secs(24 * 60 * 60);
768 for _ in 0..=3600 {
769 let eh = ExpiryHours::try_from_systemtime_ceil(exp).unwrap();
770 assert!(SystemTime::from(eh) >= exp);
771 assert!(SystemTime::from(eh) < exp + Duration::from_secs(SEC_PER_HOUR));
772
773 exp += Duration::from_secs(1);
774 }
775 }
776}