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};
58use tor_llcrypto::pk::*;
59
60use web_time_compat as time;
61
62pub use err::CertError;
63
64#[cfg(feature = "encode")]
65mod encode;
66#[cfg(feature = "encode")]
67pub use encode::{EncodedCert, EncodedEd25519Cert};
68#[cfg(feature = "encode")]
69pub use err::CertEncodeError;
70
71type CertResult<T> = std::result::Result<T, CertError>;
73
74caret_int! {
75 pub struct CertType(u8) {
86 TLS_LINK_X509 = 0x01,
88 RSA_ID_X509 = 0x02,
90 LINK_AUTH_X509 = 0x03,
93
94 IDENTITY_V_SIGNING = 0x04,
96
97 SIGNING_V_TLS_CERT = 0x05,
99
100 SIGNING_V_LINK_AUTH = 0x06,
102
103 RSA_ID_V_IDENTITY = 0x07,
106
107 HS_BLINDED_ID_V_SIGNING = 0x08,
111
112 HS_IP_V_SIGNING = 0x09,
127
128 NTOR_CC_IDENTITY = 0x0A,
131
132 HS_IP_CC_SIGNING = 0x0B,
141
142 FAMILY_V_IDENTITY = 0x0C,
145 }
146}
147
148caret_int! {
149 pub struct ExtType(u8) {
151 SIGNED_WITH_ED25519_KEY = 0x04,
155 }
156}
157
158caret_int! {
159 pub struct KeyType(u8) {
161 ED25519_KEY = 0x01,
163 SHA256_OF_RSA = 0x02,
165 SHA256_OF_X509 = 0x03,
167 }
168}
169
170#[derive(Debug, Clone)]
173#[cfg_attr(feature = "encode", derive(derive_builder::Builder))]
174#[cfg_attr(
175 feature = "encode",
176 builder(name = "Ed25519CertConstructor", build_fn(skip))
177)]
178pub struct Ed25519Cert {
179 #[cfg_attr(feature = "encode", builder(setter(custom)))]
181 exp_hours: ExpiryHours,
182 cert_type: CertType,
184 cert_key: CertifiedKey,
186 #[allow(unused)]
188 #[cfg_attr(feature = "encode", builder(setter(custom)))]
189 extensions: Vec<CertExt>,
190 #[cfg_attr(feature = "encode", builder(setter(custom)))]
196 signed_with: Option<ed25519::Ed25519Identity>,
197}
198
199#[derive(Debug, Clone)]
201#[non_exhaustive]
202pub enum CertifiedKey {
203 Ed25519(ed25519::Ed25519Identity),
205 RsaSha256Digest([u8; 32]),
207 X509Sha256Digest([u8; 32]),
209 Unrecognized(UnrecognizedKey),
211}
212
213#[derive(Debug, Clone)]
215pub struct UnrecognizedKey {
216 key_type: KeyType,
218 key_digest: [u8; 32],
220}
221
222impl CertifiedKey {
223 pub fn key_type(&self) -> KeyType {
225 match self {
226 CertifiedKey::Ed25519(_) => KeyType::ED25519_KEY,
227 CertifiedKey::RsaSha256Digest(_) => KeyType::SHA256_OF_RSA,
228 CertifiedKey::X509Sha256Digest(_) => KeyType::SHA256_OF_X509,
229
230 CertifiedKey::Unrecognized(u) => u.key_type,
231 }
232 }
233 pub fn as_bytes(&self) -> &[u8] {
236 match self {
237 CertifiedKey::Ed25519(k) => k.as_bytes(),
238 CertifiedKey::RsaSha256Digest(k) => &k[..],
239 CertifiedKey::X509Sha256Digest(k) => &k[..],
240 CertifiedKey::Unrecognized(u) => &u.key_digest[..],
241 }
242 }
243 pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
246 match self {
247 CertifiedKey::Ed25519(k) => Some(k),
248 _ => None,
249 }
250 }
251 fn from_reader(key_type: KeyType, r: &mut Reader<'_>) -> BytesResult<Self> {
254 Ok(match key_type {
255 KeyType::ED25519_KEY => CertifiedKey::Ed25519(r.extract()?),
256 KeyType::SHA256_OF_RSA => CertifiedKey::RsaSha256Digest(r.extract()?),
257 KeyType::SHA256_OF_X509 => CertifiedKey::X509Sha256Digest(r.extract()?),
258 _ => CertifiedKey::Unrecognized(UnrecognizedKey {
259 key_type,
260 key_digest: r.extract()?,
261 }),
262 })
263 }
264}
265
266#[derive(Debug, Clone)]
268enum CertExt {
269 SignedWithEd25519(SignedWithEd25519Ext),
271 Unrecognized(UnrecognizedExt),
273}
274
275#[derive(Debug, Clone)]
277#[allow(unused)]
278struct UnrecognizedExt {
279 affects_validation: bool,
282 ext_type: ExtType,
284 body: Vec<u8>,
286}
287
288impl CertExt {
289 fn ext_id(&self) -> ExtType {
291 match self {
292 CertExt::SignedWithEd25519(_) => ExtType::SIGNED_WITH_ED25519_KEY,
293 CertExt::Unrecognized(u) => u.ext_type,
294 }
295 }
296}
297
298#[derive(Debug, Clone)]
300struct SignedWithEd25519Ext {
301 pk: ed25519::Ed25519Identity,
303}
304
305impl Readable for CertExt {
306 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
307 let len = b.take_u16()?;
308 let ext_type: ExtType = b.take_u8()?.into();
309 let flags = b.take_u8()?;
310 let body = b.take(len as usize)?;
311
312 Ok(match ext_type {
313 ExtType::SIGNED_WITH_ED25519_KEY => CertExt::SignedWithEd25519(SignedWithEd25519Ext {
314 pk: ed25519::Ed25519Identity::from_bytes(body).ok_or_else(|| {
315 BytesError::InvalidMessage("wrong length on Ed25519 key".into())
316 })?,
317 }),
318 _ => {
319 if (flags & 1) != 0 {
320 return Err(BytesError::InvalidMessage(
321 "unrecognized certificate extension, with 'affects_validation' flag set."
322 .into(),
323 ));
324 }
325 CertExt::Unrecognized(UnrecognizedExt {
326 affects_validation: false,
327 ext_type,
328 body: body.into(),
329 })
330 }
331 })
332 }
333}
334
335impl Ed25519Cert {
336 pub fn decode(cert: &[u8]) -> BytesResult<KeyUnknownCert> {
345 let mut r = Reader::from_slice(cert);
346 let v = r.take_u8()?;
347 if v != 1 {
348 return Err(BytesError::InvalidMessage(
351 "Unrecognized certificate version".into(),
352 ));
353 }
354 let cert_type = r.take_u8()?.into();
355 let exp_hours = r.extract()?;
356 let mut cert_key_type = r.take_u8()?.into();
357
358 if cert_type == CertType::SIGNING_V_TLS_CERT && cert_key_type == KeyType::ED25519_KEY {
362 cert_key_type = KeyType::SHA256_OF_X509;
363 }
364
365 let cert_key = CertifiedKey::from_reader(cert_key_type, &mut r)?;
366 let n_exts = r.take_u8()?;
367 let mut extensions = Vec::new();
368 for _ in 0..n_exts {
369 let e: CertExt = r.extract()?;
370 extensions.push(e);
371 }
372
373 let sig_offset = r.consumed();
374 let signature: ed25519::Signature = r.extract()?;
375 r.should_be_exhausted()?;
376
377 let keyext = extensions
378 .iter()
379 .find(|e| e.ext_id() == ExtType::SIGNED_WITH_ED25519_KEY);
380
381 let included_pkey = match keyext {
382 Some(CertExt::SignedWithEd25519(s)) => Some(s.pk),
383 _ => None,
384 };
385
386 Ok(KeyUnknownCert {
387 cert: UncheckedCert {
388 cert: Ed25519Cert {
389 exp_hours,
390 cert_type,
391 cert_key,
392 extensions,
393
394 signed_with: included_pkey,
395 },
396 text: cert[0..sig_offset].into(),
397 signature,
398 },
399 })
400 }
401
402 pub fn expiry(&self) -> std::time::SystemTime {
404 self.exp_hours.into()
405 }
406
407 pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
409 when >= self.expiry()
410 }
411
412 pub fn subject_key(&self) -> &CertifiedKey {
415 &self.cert_key
416 }
417
418 pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
420 self.signed_with.as_ref()
421 }
422
423 pub fn cert_type(&self) -> CertType {
425 self.cert_type
426 }
427}
428
429#[derive(Clone, Debug)]
438pub struct KeyUnknownCert {
439 cert: UncheckedCert,
441}
442
443impl KeyUnknownCert {
444 pub fn peek_cert_type(&self) -> CertType {
446 self.cert.cert.cert_type
447 }
448 pub fn peek_subject_key(&self) -> &CertifiedKey {
450 &self.cert.cert.cert_key
451 }
452
453 #[deprecated(
461 since = "0.7.1",
462 note = "Use should_have_signing_key or should_be_signed_with instead."
463 )]
464 pub fn check_key(self, pkey: Option<&ed25519::Ed25519Identity>) -> CertResult<UncheckedCert> {
465 match pkey {
466 Some(wanted) => self.should_be_signed_with(wanted),
467 None => self.should_have_signing_key(),
468 }
469 }
470
471 pub fn should_have_signing_key(self) -> CertResult<UncheckedCert> {
478 let real_key = match &self.cert.cert.signed_with {
479 Some(a) => *a,
480 None => return Err(CertError::MissingPubKey),
481 };
482
483 Ok(UncheckedCert {
484 cert: Ed25519Cert {
485 signed_with: Some(real_key),
486 ..self.cert.cert
487 },
488 ..self.cert
489 })
490 }
491
492 pub fn should_be_signed_with(
498 self,
499 pkey: &ed25519::Ed25519Identity,
500 ) -> CertResult<UncheckedCert> {
501 let real_key = match &self.cert.cert.signed_with {
502 Some(a) if a == pkey => *pkey,
503 None => *pkey,
504 Some(_) => return Err(CertError::KeyMismatch),
505 };
506
507 Ok(UncheckedCert {
508 cert: Ed25519Cert {
509 signed_with: Some(real_key),
510 ..self.cert.cert
511 },
512 ..self.cert
513 })
514 }
515}
516
517#[derive(Debug, Clone)]
520pub struct UncheckedCert {
521 cert: Ed25519Cert,
524
525 text: Vec<u8>,
530
531 signature: ed25519::Signature,
533}
534
535pub struct SigCheckedCert {
538 cert: Ed25519Cert,
540}
541
542impl UncheckedCert {
543 pub fn dangerously_split(
546 self,
547 ) -> CertResult<(SigCheckedCert, ed25519::ValidatableEd25519Signature)> {
548 use tor_checkable::SelfSigned;
549 let signing_key = self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
550 let signing_key = signing_key
551 .try_into()
552 .map_err(|_| CertError::BadSignature)?;
553 let signature =
554 ed25519::ValidatableEd25519Signature::new(signing_key, self.signature, &self.text[..]);
555 Ok((self.dangerously_assume_wellsigned(), signature))
556 }
557
558 pub fn peek_subject_key(&self) -> &CertifiedKey {
560 &self.cert.cert_key
561 }
562 pub fn peek_signing_key(&self) -> &ed25519::Ed25519Identity {
564 self.cert
565 .signed_with
566 .as_ref()
567 .expect("Made an UncheckedCert without a signing key")
568 }
569}
570
571impl tor_checkable::SelfSigned<SigCheckedCert> for UncheckedCert {
572 type Error = CertError;
573
574 fn is_well_signed(&self) -> CertResult<()> {
575 let pubkey = &self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
576 let pubkey: ed25519::PublicKey = pubkey.try_into().map_err(|_| CertError::BadSignature)?;
577
578 pubkey
579 .verify(&self.text[..], &self.signature)
580 .map_err(|_| CertError::BadSignature)?;
581
582 Ok(())
583 }
584
585 fn dangerously_assume_wellsigned(self) -> SigCheckedCert {
586 SigCheckedCert { cert: self.cert }
587 }
588}
589
590impl tor_checkable::Timebound<Ed25519Cert> for Ed25519Cert {
591 type Error = tor_checkable::TimeValidityError;
592
593 fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
594 if self.is_expired_at(*t) {
595 let expiry = self.expiry();
596 Err(Self::Error::Expired(
597 t.duration_since(expiry)
598 .expect("certificate expiry time inconsistent"),
599 ))
600 } else {
601 Ok(())
602 }
603 }
604
605 fn dangerously_assume_timely(self) -> Ed25519Cert {
606 self
607 }
608}
609
610impl tor_checkable::Timebound<Ed25519Cert> for SigCheckedCert {
611 type Error = tor_checkable::TimeValidityError;
612 fn is_valid_at(&self, t: &time::SystemTime) -> std::result::Result<(), Self::Error> {
613 self.cert.is_valid_at(t)
614 }
615
616 fn dangerously_assume_timely(self) -> Ed25519Cert {
617 self.cert.dangerously_assume_timely()
618 }
619}
620
621#[derive(Debug, Clone, Copy)]
623struct ExpiryHours(u32);
624
625const SEC_PER_HOUR: u64 = 3600;
627
628impl From<ExpiryHours> for time::SystemTime {
629 fn from(value: ExpiryHours) -> Self {
630 let d = std::time::Duration::from_secs(u64::from(value.0) * SEC_PER_HOUR);
632 std::time::SystemTime::UNIX_EPOCH + d
633 }
634}
635
636#[cfg(feature = "encode")]
637impl ExpiryHours {
638 fn try_from_systemtime_ceil(expiry: time::SystemTime) -> Result<Self, CertEncodeError> {
640 let d = expiry
641 .duration_since(time::SystemTime::UNIX_EPOCH)
642 .map_err(|_| CertEncodeError::InvalidExpiration)?;
643 let sec_ceil = d.as_secs() + if d.subsec_nanos() > 0 { 1 } else { 0 };
644 let hours = sec_ceil
645 .div_ceil(SEC_PER_HOUR)
646 .try_into()
647 .map_err(|_| CertEncodeError::InvalidExpiration)?;
648 Ok(ExpiryHours(hours))
649 }
650
651 const fn max() -> Self {
653 ExpiryHours(u32::MAX)
654 }
655}
656
657impl Readable for ExpiryHours {
658 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
659 Ok(ExpiryHours(b.take_u32()?))
660 }
661}
662
663impl tor_bytes::Writeable for ExpiryHours {
664 fn write_onto<B: tor_bytes::Writer + ?Sized>(&self, b: &mut B) -> tor_bytes::EncodeResult<()> {
665 b.write_u32(self.0);
666 Ok(())
667 }
668}
669
670#[cfg(test)]
671mod test {
672 #![allow(clippy::bool_assert_comparison)]
674 #![allow(clippy::clone_on_copy)]
675 #![allow(clippy::dbg_macro)]
676 #![allow(clippy::mixed_attributes_style)]
677 #![allow(clippy::print_stderr)]
678 #![allow(clippy::print_stdout)]
679 #![allow(clippy::single_char_pattern)]
680 #![allow(clippy::unwrap_used)]
681 #![allow(clippy::unchecked_time_subtraction)]
682 #![allow(clippy::useless_vec)]
683 #![allow(clippy::needless_pass_by_value)]
684 use super::*;
686 use hex_literal::hex;
687 use web_time_compat::SystemTimeExt;
688
689 #[test]
690 fn parse_unrecognized_ext() -> BytesResult<()> {
691 let b = hex!("0009 99 10 657874656e73696f6e");
693 let mut r = Reader::from_slice(&b);
694 let e: CertExt = r.extract()?;
695 r.should_be_exhausted()?;
696
697 assert_eq!(e.ext_id(), 0x99.into());
698
699 let b = hex!("0009 99 11 657874656e73696f6e");
702 let mut r = Reader::from_slice(&b);
703 let e: Result<CertExt, BytesError> = r.extract();
704 assert!(e.is_err());
705 assert_eq!(
706 e.err().unwrap(),
707 BytesError::InvalidMessage(
708 "unrecognized certificate extension, with 'affects_validation' flag set.".into()
709 )
710 );
711
712 Ok(())
713 }
714
715 #[test]
716 fn certified_key() -> BytesResult<()> {
717 let b =
718 hex!("4c27616d6f757220756e6974206365757820717527656e636861c3ae6e616974206c6520666572");
719 let mut r = Reader::from_slice(&b);
720
721 let ck = CertifiedKey::from_reader(KeyType::SHA256_OF_RSA, &mut r)?;
722 assert_eq!(ck.as_bytes(), &b[..32]);
723 assert_eq!(ck.key_type(), KeyType::SHA256_OF_RSA);
724 assert_eq!(r.remaining(), 7);
725
726 let mut r = Reader::from_slice(&b);
727 let ck = CertifiedKey::from_reader(42.into(), &mut r)?;
728 assert_eq!(ck.as_bytes(), &b[..32]);
729 assert_eq!(ck.key_type(), 42.into());
730 assert_eq!(r.remaining(), 7);
731
732 Ok(())
733 }
734
735 #[cfg(feature = "encode")]
736 #[test]
737 fn expiry_hours_ceil() {
738 use std::time::{Duration, SystemTime};
739
740 let now = SystemTime::get();
741 let mut exp = now + Duration::from_secs(24 * 60 * 60);
742 for _ in 0..=3600 {
743 let eh = ExpiryHours::try_from_systemtime_ceil(exp).unwrap();
744 assert!(SystemTime::from(eh) >= exp);
745 assert!(SystemTime::from(eh) < exp + Duration::from_secs(SEC_PER_HOUR));
746
747 exp += Duration::from_secs(1);
748 }
749 }
750}