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)] mod err;
48pub mod rsa;
49#[cfg(feature = "x509")]
50pub mod x509;
51
52use caret::caret_int;
53use tor_bytes::{Error as BytesError, Result as BytesResult};
54use tor_bytes::{Readable, Reader};
55use tor_llcrypto::pk::*;
56
57use std::time;
58
59pub use err::CertError;
60
61#[cfg(feature = "encode")]
62mod encode;
63#[cfg(feature = "encode")]
64pub use encode::EncodedEd25519Cert;
65#[cfg(feature = "encode")]
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)]
170#[cfg_attr(feature = "encode", derive(derive_builder::Builder))]
171#[cfg_attr(
172 feature = "encode",
173 builder(name = "Ed25519CertConstructor", build_fn(skip))
174)]
175pub struct Ed25519Cert {
176 #[cfg_attr(feature = "encode", builder(setter(custom)))]
178 exp_hours: u32,
179 cert_type: CertType,
181 cert_key: CertifiedKey,
183 #[allow(unused)]
185 #[cfg_attr(feature = "encode", builder(setter(custom)))]
186 extensions: Vec<CertExt>,
187 #[cfg_attr(feature = "encode", builder(setter(custom)))]
193 signed_with: Option<ed25519::Ed25519Identity>,
194}
195
196#[derive(Debug, Clone)]
198#[non_exhaustive]
199pub enum CertifiedKey {
200 Ed25519(ed25519::Ed25519Identity),
202 RsaSha256Digest([u8; 32]),
204 X509Sha256Digest([u8; 32]),
206 Unrecognized(UnrecognizedKey),
208}
209
210#[derive(Debug, Clone)]
212pub struct UnrecognizedKey {
213 key_type: KeyType,
215 key_digest: [u8; 32],
217}
218
219impl CertifiedKey {
220 pub fn key_type(&self) -> KeyType {
222 match self {
223 CertifiedKey::Ed25519(_) => KeyType::ED25519_KEY,
224 CertifiedKey::RsaSha256Digest(_) => KeyType::SHA256_OF_RSA,
225 CertifiedKey::X509Sha256Digest(_) => KeyType::SHA256_OF_X509,
226
227 CertifiedKey::Unrecognized(u) => u.key_type,
228 }
229 }
230 pub fn as_bytes(&self) -> &[u8] {
233 match self {
234 CertifiedKey::Ed25519(k) => k.as_bytes(),
235 CertifiedKey::RsaSha256Digest(k) => &k[..],
236 CertifiedKey::X509Sha256Digest(k) => &k[..],
237 CertifiedKey::Unrecognized(u) => &u.key_digest[..],
238 }
239 }
240 pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
243 match self {
244 CertifiedKey::Ed25519(k) => Some(k),
245 _ => None,
246 }
247 }
248 fn from_reader(key_type: KeyType, r: &mut Reader<'_>) -> BytesResult<Self> {
251 Ok(match key_type {
252 KeyType::ED25519_KEY => CertifiedKey::Ed25519(r.extract()?),
253 KeyType::SHA256_OF_RSA => CertifiedKey::RsaSha256Digest(r.extract()?),
254 KeyType::SHA256_OF_X509 => CertifiedKey::X509Sha256Digest(r.extract()?),
255 _ => CertifiedKey::Unrecognized(UnrecognizedKey {
256 key_type,
257 key_digest: r.extract()?,
258 }),
259 })
260 }
261}
262
263#[derive(Debug, Clone)]
265enum CertExt {
266 SignedWithEd25519(SignedWithEd25519Ext),
268 Unrecognized(UnrecognizedExt),
270}
271
272#[derive(Debug, Clone)]
274#[allow(unused)]
275struct UnrecognizedExt {
276 affects_validation: bool,
279 ext_type: ExtType,
281 body: Vec<u8>,
283}
284
285impl CertExt {
286 fn ext_id(&self) -> ExtType {
288 match self {
289 CertExt::SignedWithEd25519(_) => ExtType::SIGNED_WITH_ED25519_KEY,
290 CertExt::Unrecognized(u) => u.ext_type,
291 }
292 }
293}
294
295#[derive(Debug, Clone)]
297struct SignedWithEd25519Ext {
298 pk: ed25519::Ed25519Identity,
300}
301
302impl Readable for CertExt {
303 fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
304 let len = b.take_u16()?;
305 let ext_type: ExtType = b.take_u8()?.into();
306 let flags = b.take_u8()?;
307 let body = b.take(len as usize)?;
308
309 Ok(match ext_type {
310 ExtType::SIGNED_WITH_ED25519_KEY => CertExt::SignedWithEd25519(SignedWithEd25519Ext {
311 pk: ed25519::Ed25519Identity::from_bytes(body).ok_or_else(|| {
312 BytesError::InvalidMessage("wrong length on Ed25519 key".into())
313 })?,
314 }),
315 _ => {
316 if (flags & 1) != 0 {
317 return Err(BytesError::InvalidMessage(
318 "unrecognized certificate extension, with 'affects_validation' flag set."
319 .into(),
320 ));
321 }
322 CertExt::Unrecognized(UnrecognizedExt {
323 affects_validation: false,
324 ext_type,
325 body: body.into(),
326 })
327 }
328 })
329 }
330}
331
332impl Ed25519Cert {
333 pub fn decode(cert: &[u8]) -> BytesResult<KeyUnknownCert> {
342 let mut r = Reader::from_slice(cert);
343 let v = r.take_u8()?;
344 if v != 1 {
345 return Err(BytesError::InvalidMessage(
348 "Unrecognized certificate version".into(),
349 ));
350 }
351 let cert_type = r.take_u8()?.into();
352 let exp_hours = r.take_u32()?;
353 let mut cert_key_type = r.take_u8()?.into();
354
355 if cert_type == CertType::SIGNING_V_TLS_CERT && cert_key_type == KeyType::ED25519_KEY {
359 cert_key_type = KeyType::SHA256_OF_X509;
360 }
361
362 let cert_key = CertifiedKey::from_reader(cert_key_type, &mut r)?;
363 let n_exts = r.take_u8()?;
364 let mut extensions = Vec::new();
365 for _ in 0..n_exts {
366 let e: CertExt = r.extract()?;
367 extensions.push(e);
368 }
369
370 let sig_offset = r.consumed();
371 let signature: ed25519::Signature = r.extract()?;
372 r.should_be_exhausted()?;
373
374 let keyext = extensions
375 .iter()
376 .find(|e| e.ext_id() == ExtType::SIGNED_WITH_ED25519_KEY);
377
378 let included_pkey = match keyext {
379 Some(CertExt::SignedWithEd25519(s)) => Some(s.pk),
380 _ => None,
381 };
382
383 Ok(KeyUnknownCert {
384 cert: UncheckedCert {
385 cert: Ed25519Cert {
386 exp_hours,
387 cert_type,
388 cert_key,
389 extensions,
390
391 signed_with: included_pkey,
392 },
393 text: cert[0..sig_offset].into(),
394 signature,
395 },
396 })
397 }
398
399 pub fn expiry(&self) -> std::time::SystemTime {
401 let d = std::time::Duration::new(u64::from(self.exp_hours) * 3600, 0);
402 std::time::SystemTime::UNIX_EPOCH + d
403 }
404
405 pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
407 when >= self.expiry()
408 }
409
410 pub fn subject_key(&self) -> &CertifiedKey {
413 &self.cert_key
414 }
415
416 pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
418 self.signed_with.as_ref()
419 }
420
421 pub fn cert_type(&self) -> CertType {
423 self.cert_type
424 }
425}
426
427#[derive(Clone, Debug)]
436pub struct KeyUnknownCert {
437 cert: UncheckedCert,
439}
440
441impl KeyUnknownCert {
442 pub fn peek_cert_type(&self) -> CertType {
444 self.cert.cert.cert_type
445 }
446 pub fn peek_subject_key(&self) -> &CertifiedKey {
448 &self.cert.cert.cert_key
449 }
450
451 #[deprecated(
459 since = "0.7.1",
460 note = "Use should_have_signing_key or should_be_signed_with instead."
461 )]
462 pub fn check_key(self, pkey: Option<&ed25519::Ed25519Identity>) -> CertResult<UncheckedCert> {
463 match pkey {
464 Some(wanted) => self.should_be_signed_with(wanted),
465 None => self.should_have_signing_key(),
466 }
467 }
468
469 pub fn should_have_signing_key(self) -> CertResult<UncheckedCert> {
476 let real_key = match &self.cert.cert.signed_with {
477 Some(a) => *a,
478 None => return Err(CertError::MissingPubKey),
479 };
480
481 Ok(UncheckedCert {
482 cert: Ed25519Cert {
483 signed_with: Some(real_key),
484 ..self.cert.cert
485 },
486 ..self.cert
487 })
488 }
489
490 pub fn should_be_signed_with(
496 self,
497 pkey: &ed25519::Ed25519Identity,
498 ) -> CertResult<UncheckedCert> {
499 let real_key = match &self.cert.cert.signed_with {
500 Some(a) if a == pkey => *pkey,
501 None => *pkey,
502 Some(_) => return Err(CertError::KeyMismatch),
503 };
504
505 Ok(UncheckedCert {
506 cert: Ed25519Cert {
507 signed_with: Some(real_key),
508 ..self.cert.cert
509 },
510 ..self.cert
511 })
512 }
513}
514
515#[derive(Debug, Clone)]
518pub struct UncheckedCert {
519 cert: Ed25519Cert,
522
523 text: Vec<u8>,
528
529 signature: ed25519::Signature,
531}
532
533pub struct SigCheckedCert {
536 cert: Ed25519Cert,
538}
539
540impl UncheckedCert {
541 pub fn dangerously_split(
544 self,
545 ) -> CertResult<(SigCheckedCert, ed25519::ValidatableEd25519Signature)> {
546 use tor_checkable::SelfSigned;
547 let signing_key = self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
548 let signing_key = signing_key
549 .try_into()
550 .map_err(|_| CertError::BadSignature)?;
551 let signature =
552 ed25519::ValidatableEd25519Signature::new(signing_key, self.signature, &self.text[..]);
553 Ok((self.dangerously_assume_wellsigned(), signature))
554 }
555
556 pub fn peek_subject_key(&self) -> &CertifiedKey {
558 &self.cert.cert_key
559 }
560 pub fn peek_signing_key(&self) -> &ed25519::Ed25519Identity {
562 self.cert
563 .signed_with
564 .as_ref()
565 .expect("Made an UncheckedCert without a signing key")
566 }
567}
568
569impl tor_checkable::SelfSigned<SigCheckedCert> for UncheckedCert {
570 type Error = CertError;
571
572 fn is_well_signed(&self) -> CertResult<()> {
573 let pubkey = &self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
574 let pubkey: ed25519::PublicKey = pubkey.try_into().map_err(|_| CertError::BadSignature)?;
575
576 pubkey
577 .verify(&self.text[..], &self.signature)
578 .map_err(|_| CertError::BadSignature)?;
579
580 Ok(())
581 }
582
583 fn dangerously_assume_wellsigned(self) -> SigCheckedCert {
584 SigCheckedCert { cert: self.cert }
585 }
586}
587
588impl tor_checkable::Timebound<Ed25519Cert> for Ed25519Cert {
589 type Error = tor_checkable::TimeValidityError;
590
591 fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
592 if self.is_expired_at(*t) {
593 let expiry = self.expiry();
594 Err(Self::Error::Expired(
595 t.duration_since(expiry)
596 .expect("certificate expiry time inconsistent"),
597 ))
598 } else {
599 Ok(())
600 }
601 }
602
603 fn dangerously_assume_timely(self) -> Ed25519Cert {
604 self
605 }
606}
607
608impl tor_checkable::Timebound<Ed25519Cert> for SigCheckedCert {
609 type Error = tor_checkable::TimeValidityError;
610 fn is_valid_at(&self, t: &time::SystemTime) -> std::result::Result<(), Self::Error> {
611 self.cert.is_valid_at(t)
612 }
613
614 fn dangerously_assume_timely(self) -> Ed25519Cert {
615 self.cert.dangerously_assume_timely()
616 }
617}
618
619#[cfg(test)]
620mod test {
621 #![allow(clippy::bool_assert_comparison)]
623 #![allow(clippy::clone_on_copy)]
624 #![allow(clippy::dbg_macro)]
625 #![allow(clippy::mixed_attributes_style)]
626 #![allow(clippy::print_stderr)]
627 #![allow(clippy::print_stdout)]
628 #![allow(clippy::single_char_pattern)]
629 #![allow(clippy::unwrap_used)]
630 #![allow(clippy::unchecked_time_subtraction)]
631 #![allow(clippy::useless_vec)]
632 #![allow(clippy::needless_pass_by_value)]
633 use super::*;
635 use hex_literal::hex;
636
637 #[test]
638 fn parse_unrecognized_ext() -> BytesResult<()> {
639 let b = hex!("0009 99 10 657874656e73696f6e");
641 let mut r = Reader::from_slice(&b);
642 let e: CertExt = r.extract()?;
643 r.should_be_exhausted()?;
644
645 assert_eq!(e.ext_id(), 0x99.into());
646
647 let b = hex!("0009 99 11 657874656e73696f6e");
650 let mut r = Reader::from_slice(&b);
651 let e: Result<CertExt, BytesError> = r.extract();
652 assert!(e.is_err());
653 assert_eq!(
654 e.err().unwrap(),
655 BytesError::InvalidMessage(
656 "unrecognized certificate extension, with 'affects_validation' flag set.".into()
657 )
658 );
659
660 Ok(())
661 }
662
663 #[test]
664 fn certified_key() -> BytesResult<()> {
665 let b =
666 hex!("4c27616d6f757220756e6974206365757820717527656e636861c3ae6e616974206c6520666572");
667 let mut r = Reader::from_slice(&b);
668
669 let ck = CertifiedKey::from_reader(KeyType::SHA256_OF_RSA, &mut r)?;
670 assert_eq!(ck.as_bytes(), &b[..32]);
671 assert_eq!(ck.key_type(), KeyType::SHA256_OF_RSA);
672 assert_eq!(r.remaining(), 7);
673
674 let mut r = Reader::from_slice(&b);
675 let ck = CertifiedKey::from_reader(42.into(), &mut r)?;
676 assert_eq!(ck.as_bytes(), &b[..32]);
677 assert_eq!(ck.key_type(), 42.into());
678 assert_eq!(r.remaining(), 7);
679
680 Ok(())
681 }
682}