Skip to main content

tor_cert/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![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)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45#![allow(clippy::collapsible_if)] // See arti#2342
46#![deny(clippy::unused_async)]
47#![deny(clippy::string_slice)] // See arti#2571
48//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49
50mod 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
70/// A Result defined to use CertError
71type CertResult<T> = std::result::Result<T, CertError>;
72
73caret_int! {
74    /// Recognized values for Tor's certificate type field.
75    ///
76    /// In the names used here, "X_V_Y" means "key X verifying key Y",
77    /// whereas "X_CC_Y" means "key X cross-certifying key Y".  In both
78    /// cases, X is the key that is doing the signing, and Y is the key
79    /// or object that is getting signed.
80    ///
81    /// Not every one of these types is valid for an Ed25519
82    /// certificate.  Some are for X.509 certs in a CERTS cell; some
83    /// are for RSA->Ed crosscerts in a CERTS cell.
84    pub struct CertType(u8) {
85        /// TLS link key, signed with RSA identity. X.509 format. (Obsolete)
86        TLS_LINK_X509 = 0x01,
87        /// Self-signed RSA identity certificate. X.509 format. (Legacy)
88        RSA_ID_X509 = 0x02,
89        /// RSA lnk authentication key signed with RSA identity
90        /// key. X.509 format. (Obsolete)
91        LINK_AUTH_X509 = 0x03,
92
93        /// Identity verifying a signing key, directly.
94        IDENTITY_V_SIGNING = 0x04,
95
96        /// Signing key verifying a TLS certificate by digest.
97        SIGNING_V_TLS_CERT = 0x05,
98
99        /// Signing key verifying a link authentication key.
100        SIGNING_V_LINK_AUTH = 0x06,
101
102        /// RSA identity key certifying an Ed25519 identity key. RSA
103        /// crosscert format. (Legacy)
104        RSA_ID_V_IDENTITY = 0x07,
105
106        /// For onion services: short-term descriptor signing key
107        /// (`KP_hs_desc_sign`), signed with blinded onion service identity
108        /// (`KP_hs_blind_id`).
109        HS_BLINDED_ID_V_SIGNING = 0x08,
110
111        /// For onion services: Introduction point authentication key
112        /// (`KP_hs_ipt_sid`), signed with short term descriptor signing key
113        /// (`KP_hs_desc_sign`).
114        ///
115        /// This one is, sadly, a bit complicated. In the original specification
116        /// it was meant to be a cross-certificate, where the signature would be
117        /// _on_ the descriptor signing key, _signed with_ the intro TID key.
118        /// But we got it backwards in the C Tor implementation, and now, for
119        /// compatibility, we are stuck doing it backwards in the future.
120        ///
121        /// If we find in the future that it is actually important to
122        /// cross-certify these keys (as originally intended), then we should
123        /// add a new certificate type, and put the new certificate in the onion
124        /// service descriptor.
125        HS_IP_V_SIGNING = 0x09,
126
127        /// An ntor key converted to a ed25519 key, cross-certifying an
128        /// identity key.
129        NTOR_CC_IDENTITY = 0x0A,
130
131        /// For onion services: Ntor encryption key (`KP_hss_ntor`),
132        /// converted to ed25519, signed with the descriptor signing key
133        /// (`KP_hs_desc_sign`).
134        ///
135        /// As with [`HS_IP_V_SIGNING`](CertType::HS_IP_V_SIGNING), this
136        /// certificate type is backwards.  In the original specification it was
137        /// meant to be a cross certificate, with the signing and signed keys
138        /// reversed.
139        HS_IP_CC_SIGNING = 0x0B,
140
141        /// For relays: family key certifying membership of a relay
142        /// by signing its identity.
143        FAMILY_V_IDENTITY = 0x0C,
144    }
145}
146
147caret_int! {
148    /// Extension identifiers for extensions in certificates.
149    pub struct ExtType(u8) {
150        /// Extension indicating an Ed25519 key that signed this certificate.
151        ///
152        /// Certificates do not always contain the key that signed them.
153        SIGNED_WITH_ED25519_KEY = 0x04,
154    }
155}
156
157caret_int! {
158    /// Identifiers for the type of key or object getting signed.
159    pub struct KeyType(u8) {
160        /// Identifier for an Ed25519 key.
161        ED25519_KEY = 0x01,
162        /// Identifier for the SHA256 of an DER-encoded RSA key.
163        SHA256_OF_RSA = 0x02,
164        /// Identifies the SHA256 of an X.509 certificate.
165        SHA256_OF_X509 = 0x03,
166    }
167}
168
169/// Structure for an Ed25519-signed certificate as described in Tor's
170/// cert-spec.txt.
171#[derive(Debug, Clone, PartialEq, Eq, derive_builder::Builder)]
172#[builder(build_fn(skip))]
173pub struct Ed25519Cert {
174    /// How many _hours_ after the epoch will this certificate expire?
175    #[builder(setter(custom))]
176    exp_hours: ExpiryHours,
177    /// Type of the certificate; recognized values are in certtype::*
178    cert_type: CertType,
179    /// The key or object being certified.
180    cert_key: CertifiedKey,
181    /// A list of extensions.
182    #[allow(unused)] // TODO review CertExt and make it pub, and add a getter
183    #[builder(setter(custom))]
184    extensions: Vec<CertExt>,
185    /// The key that signed this cert.
186    ///
187    /// Once the cert has been unwrapped from an KeyUnknownCert, this field will
188    /// be set.  If there is a `SignedWithEd25519` extension in
189    /// `self.extensions`, this will match it.
190    #[builder(setter(custom))]
191    signed_with: Option<ed25519::Ed25519Identity>,
192}
193
194/// One of the data types that can be certified by an Ed25519Cert.
195#[derive(Debug, Clone, PartialEq, Eq, derive_more::From)]
196#[non_exhaustive]
197pub enum CertifiedKey {
198    /// An Ed25519 public key, signed directly.
199    Ed25519(ed25519::Ed25519Identity),
200    /// The SHA256 digest of a DER-encoded RsaPublicKey
201    #[from(skip)]
202    RsaSha256Digest([u8; 32]),
203    /// The SHA256 digest of an X.509 certificate.
204    #[from(skip)]
205    X509Sha256Digest([u8; 32]),
206    /// Some unrecognized key type.
207    #[from(skip)]
208    Unrecognized(UnrecognizedKey),
209}
210
211/// A key whose type we didn't recognize.
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct UnrecognizedKey {
214    /// Actual type of the key.
215    key_type: KeyType,
216    /// digest of the key, or the key itself.
217    key_digest: [u8; 32],
218}
219
220impl CertifiedKey {
221    /// Return the byte that identifies the type of this key.
222    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    /// Return the bytes that are used for the body of this certified
232    /// key or object.
233    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    /// If this is an Ed25519 public key, return Some(key).
242    /// Otherwise, return None.
243    pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
244        match self {
245            CertifiedKey::Ed25519(k) => Some(k),
246            _ => None,
247        }
248    }
249    /// Try to extract a CertifiedKey from a Reader, given that we have
250    /// already read its type as `key_type`.
251    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/// An extension in a Tor certificate.
265#[derive(Debug, Clone, PartialEq, Eq)]
266enum CertExt {
267    /// Indicates which Ed25519 public key signed this cert.
268    SignedWithEd25519(SignedWithEd25519Ext),
269    /// An extension whose identity we don't recognize.
270    Unrecognized(UnrecognizedExt),
271}
272
273/// Any unrecognized extension on a Tor certificate.
274#[derive(Debug, Clone, PartialEq, Eq)]
275#[allow(unused)]
276struct UnrecognizedExt {
277    /// True iff this extension must be understand in order to validate the
278    /// certificate.
279    affects_validation: bool,
280    /// The type of the extension
281    ext_type: ExtType,
282    /// The body of the extension.
283    body: Vec<u8>,
284}
285
286impl CertExt {
287    /// Return the identifier code for this Extension.
288    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/// Extension indicating that a key that signed a given certificate.
297#[derive(Debug, Clone, PartialEq, Eq)]
298struct SignedWithEd25519Ext {
299    /// The key that signed the certificate including this extension.
300    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    /// Try to decode a certificate from a byte slice.
348    ///
349    /// This function returns an error if the byte slice is not
350    /// completely exhausted.
351    ///
352    /// Note that the resulting KeyUnknownCertificate is not checked
353    /// for validity at all: you will need to provide it with an expected
354    /// signing key, then check it for timeliness and well-signedness.
355    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            // This would be something other than a "v1" certificate. We don't
360            // understand those.
361            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        // This is a workaround for a tor bug: the key type is
370        // wrong. It was fixed in tor#40124, which got merged into Tor
371        // 0.4.5.x and later.
372        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        // See comment in `impl Writeable for UncheckedCert`.
388
389        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    /// Return the time at which this certificate becomes expired
415    pub fn expiry(&self) -> std::time::SystemTime {
416        self.exp_hours.into()
417    }
418
419    /// Return true iff this certificate will be expired at the time `when`.
420    ///
421    /// This is inclusive, meaning that `when == self.expiry()` is still valid.
422    pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
423        when > self.expiry()
424    }
425
426    /// Return the signed key or object that is authenticated by this
427    /// certificate.
428    pub fn subject_key(&self) -> &CertifiedKey {
429        &self.cert_key
430    }
431
432    /// Return the ed25519 key that signed this certificate.
433    pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
434        self.signed_with.as_ref()
435    }
436
437    /// Return the type of this certificate.
438    pub fn cert_type(&self) -> CertType {
439        self.cert_type
440    }
441}
442
443/// A parsed Ed25519 certificate. Maybe it includes its signing key;
444/// maybe it doesn't.
445///
446/// To validate this cert, either it must contain its signing key,
447/// or the caller must know the signing key.  In the first case, call
448/// [`should_have_signing_key`](KeyUnknownCert::should_have_signing_key);
449/// in the latter, call
450/// [`should_be_signed_with`](KeyUnknownCert::should_be_signed_with).
451#[derive(Clone, Debug, PartialEq, Eq)]
452pub struct KeyUnknownCert {
453    /// The certificate whose signing key might not be known.
454    cert: UncheckedCert,
455}
456
457impl KeyUnknownCert {
458    /// Return the certificate type of the underling cert.
459    pub fn peek_cert_type(&self) -> CertType {
460        self.cert.cert.cert_type
461    }
462    /// Return subject key of the underlying cert.
463    pub fn peek_subject_key(&self) -> &CertifiedKey {
464        &self.cert.cert.cert_key
465    }
466
467    /// Check whether a given pkey is (or might be) a key that has correctly
468    /// signed this certificate.
469    ///
470    /// If pkey is None, this certificate must contain its signing key.
471    ///
472    /// On success, we can check whether the certificate is well-signed;
473    /// otherwise, we can't check the certificate.
474    #[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    /// Declare that this should be a self-contained certificate that contains its own
486    /// signing key.
487    ///
488    /// On success, this certificate did indeed turn out to be self-contained, and so
489    /// we can validate it.
490    /// On failure, this certificate was not self-contained.
491    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    /// Declare that this should be a certificate signed with a given key.
507    ///
508    /// On success, this certificate either listed the provided key, or did not
509    /// list any key: in either case, we can validate it.
510    /// On failure, this certificate claims to be signed with a different key.
511    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/// A certificate that has been parsed, but whose signature and
532/// timeliness have not been checked.
533#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct UncheckedCert {
535    /// The parsed certificate, possibly modified by inserting an externally
536    /// supplied key as its signing key.
537    cert: Ed25519Cert,
538
539    /// The signed text of the certificate. (Checking ed25519 signatures
540    /// forces us to store this.
541    // TODO(nickm)  It would be better to store a hash here, but we
542    // don't have the right Ed25519 API.
543    text: Vec<u8>,
544
545    /// The alleged signature
546    signature: ed25519::Signature,
547}
548
549/// A certificate that has been parsed and signature-checked, but whose
550/// timeliness has not been checked.
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub struct SigCheckedCert {
553    /// The certificate that might or might not be timely
554    cert: Ed25519Cert,
555}
556
557impl UncheckedCert {
558    /// Split this unchecked cert into a component that assumes it has
559    /// been checked, and a signature to validate.
560    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    /// Return subject key of the underlying cert.
574    pub fn peek_subject_key(&self) -> &CertifiedKey {
575        &self.cert.cert_key
576    }
577    /// Return signing key of the underlying cert.
578    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    // TODO in some sense this duplicates things in encode.rs.
588    // However, encode.rs is not useable in type-driven (derive-based) situations,
589    // because it uses entirely different types for encoding to those for decoding.
590    //
591    // Therefore, here we implement tor_bytes's encoding trait for the type which can
592    // also be decoded.  Perhaps the encode module could be abolished.
593    fn write_onto<B: Writer + ?Sized>(&self, b: &mut B) -> Result<(), tor_bytes::EncodeError> {
594        // Ed25519Cert::decode does a lot of work, which finds a lot of fields,
595        // but also `sig_offset`.  It then splits the incoming byte buffer at `sig_offset`
596        // into `text` and `signature`, insisting that there is nothing else.
597        //
598        // Therefore this is guaranteed to write precisely the input to `decode`.
599        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/// A certificate expiration time, represented in _hours_ since the unix epoch.
654#[derive(Debug, Clone, Copy, PartialEq, Eq)]
655struct ExpiryHours(u32);
656
657/// The number of seconds in an hour.
658const SEC_PER_HOUR: u64 = 3600;
659
660impl From<ExpiryHours> for time::SystemTime {
661    fn from(value: ExpiryHours) -> Self {
662        // TODO MSRV 1.91; use from_hours.
663        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    /// Return the earliest possible `ExpiryHours` that is no earlier than `expiry`.
670    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    /// Return the latest possible ExpiryHours
683    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    // @@ begin test lint list maintained by maint/add_warning @@
704    #![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)] // See arti#2571
716    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
717    use super::*;
718    use hex_literal::hex;
719    use web_time_compat::SystemTimeExt;
720
721    #[test]
722    fn parse_unrecognized_ext() -> BytesResult<()> {
723        // case one: a flag is set but we don't know it
724        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        // case two: we've been told to ignore the cert if we can't
732        // handle the extension.
733        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}