tor_cert/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg, 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_duration_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//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46
47mod err;
48pub mod rsa;
49
50use caret::caret_int;
51use tor_bytes::{Error as BytesError, Result as BytesResult};
52use tor_bytes::{Readable, Reader};
53use tor_llcrypto::pk::*;
54
55use std::time;
56
57pub use err::CertError;
58
59#[cfg(feature = "encode")]
60mod encode;
61#[cfg(feature = "encode")]
62pub use encode::EncodedEd25519Cert;
63#[cfg(feature = "encode")]
64pub use err::CertEncodeError;
65
66/// A Result defined to use CertError
67type CertResult<T> = std::result::Result<T, CertError>;
68
69caret_int! {
70    /// Recognized values for Tor's certificate type field.
71    ///
72    /// In the names used here, "X_V_Y" means "key X verifying key Y",
73    /// whereas "X_CC_Y" means "key X cross-certifying key Y".  In both
74    /// cases, X is the key that is doing the signing, and Y is the key
75    /// or object that is getting signed.
76    ///
77    /// Not every one of these types is valid for an Ed25519
78    /// certificate.  Some are for X.509 certs in a CERTS cell; some
79    /// are for RSA->Ed crosscerts in a CERTS cell.
80    pub struct CertType(u8) {
81        /// TLS link key, signed with RSA identity. X.509 format. (Obsolete)
82        TLS_LINK_X509 = 0x01,
83        /// Self-signed RSA identity certificate. X.509 format. (Legacy)
84        RSA_ID_X509 = 0x02,
85        /// RSA lnk authentication key signed with RSA identity
86        /// key. X.509 format. (Obsolete)
87        LINK_AUTH_X509 = 0x03,
88
89        /// Identity verifying a signing key, directly.
90        IDENTITY_V_SIGNING = 0x04,
91
92        /// Signing key verifying a TLS certificate by digest.
93        SIGNING_V_TLS_CERT = 0x05,
94
95        /// Signing key verifying a link authentication key.
96        SIGNING_V_LINK_AUTH = 0x06,
97
98        /// RSA identity key certifying an Ed25519 identity key. RSA
99        /// crosscert format. (Legacy)
100        RSA_ID_V_IDENTITY = 0x07,
101
102        /// For onion services: short-term descriptor signing key
103        /// (`KP_hs_desc_sign`), signed with blinded onion service identity
104        /// (`KP_hs_blind_id`).
105        HS_BLINDED_ID_V_SIGNING = 0x08,
106
107        /// For onion services: Introduction point authentication key
108        /// (`KP_hs_ipt_sid`), signed with short term descriptor signing key
109        /// (`KP_hs_desc_sign`).
110        ///
111        /// This one is, sadly, a bit complicated. In the original specification
112        /// it was meant to be a cross-certificate, where the signature would be
113        /// _on_ the descriptor signing key, _signed with_ the intro TID key.
114        /// But we got it backwards in the C Tor implementation, and now, for
115        /// compatibility, we are stuck doing it backwards in the future.
116        ///
117        /// If we find in the future that it is actually important to
118        /// cross-certify these keys (as originally intended), then we should
119        /// add a new certificate type, and put the new certificate in the onion
120        /// service descriptor.
121        HS_IP_V_SIGNING = 0x09,
122
123        /// An ntor key converted to a ed25519 key, cross-certifying an
124        /// identity key.
125        NTOR_CC_IDENTITY = 0x0A,
126
127        /// For onion services: Ntor encryption key (`KP_hss_ntor`),
128        /// converted to ed25519, signed with the descriptor signing key
129        /// (`KP_hs_desc_sign`).
130        ///
131        /// As with [`HS_IP_V_SIGNING`](CertType::HS_IP_V_SIGNING), this
132        /// certificate type is backwards.  In the original specification it was
133        /// meant to be a cross certificate, with the signing and signed keys
134        /// reversed.
135        HS_IP_CC_SIGNING = 0x0B,
136
137        /// For relays: family key certifying membership of a relay
138        /// by signing its identity.
139        FAMILY_V_IDENTITY = 0x0C,
140    }
141}
142
143caret_int! {
144    /// Extension identifiers for extensions in certificates.
145    pub struct ExtType(u8) {
146        /// Extension indicating an Ed25519 key that signed this certificate.
147        ///
148        /// Certificates do not always contain the key that signed them.
149        SIGNED_WITH_ED25519_KEY = 0x04,
150    }
151}
152
153caret_int! {
154    /// Identifiers for the type of key or object getting signed.
155    pub struct KeyType(u8) {
156        /// Identifier for an Ed25519 key.
157        ED25519_KEY = 0x01,
158        /// Identifier for the SHA256 of an DER-encoded RSA key.
159        SHA256_OF_RSA = 0x02,
160        /// Identifies the SHA256 of an X.509 certificate.
161        SHA256_OF_X509 = 0x03,
162    }
163}
164
165/// Structure for an Ed25519-signed certificate as described in Tor's
166/// cert-spec.txt.
167#[derive(Debug, Clone)]
168#[cfg_attr(feature = "encode", derive(derive_builder::Builder))]
169#[cfg_attr(
170    feature = "encode",
171    builder(name = "Ed25519CertConstructor", build_fn(skip))
172)]
173pub struct Ed25519Cert {
174    /// How many _hours_ after the epoch will this certificate expire?
175    #[cfg_attr(feature = "encode", builder(setter(custom)))]
176    exp_hours: u32,
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)]
183    #[cfg_attr(feature = "encode", 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    #[cfg_attr(feature = "encode", 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)]
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    RsaSha256Digest([u8; 32]),
202    /// The SHA256 digest of an X.509 certificate.
203    X509Sha256Digest([u8; 32]),
204    /// Some unrecognized key type.
205    Unrecognized(UnrecognizedKey),
206}
207
208/// A key whose type we didn't recognize.
209#[derive(Debug, Clone)]
210pub struct UnrecognizedKey {
211    /// Actual type of the key.
212    key_type: KeyType,
213    /// digest of the key, or the key itself.
214    key_digest: [u8; 32],
215}
216
217impl CertifiedKey {
218    /// Return the byte that identifies the type of this key.
219    pub fn key_type(&self) -> KeyType {
220        match self {
221            CertifiedKey::Ed25519(_) => KeyType::ED25519_KEY,
222            CertifiedKey::RsaSha256Digest(_) => KeyType::SHA256_OF_RSA,
223            CertifiedKey::X509Sha256Digest(_) => KeyType::SHA256_OF_X509,
224
225            CertifiedKey::Unrecognized(u) => u.key_type,
226        }
227    }
228    /// Return the bytes that are used for the body of this certified
229    /// key or object.
230    pub fn as_bytes(&self) -> &[u8] {
231        match self {
232            CertifiedKey::Ed25519(k) => k.as_bytes(),
233            CertifiedKey::RsaSha256Digest(k) => &k[..],
234            CertifiedKey::X509Sha256Digest(k) => &k[..],
235            CertifiedKey::Unrecognized(u) => &u.key_digest[..],
236        }
237    }
238    /// If this is an Ed25519 public key, return Some(key).
239    /// Otherwise, return None.
240    pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
241        match self {
242            CertifiedKey::Ed25519(k) => Some(k),
243            _ => None,
244        }
245    }
246    /// Try to extract a CertifiedKey from a Reader, given that we have
247    /// already read its type as `key_type`.
248    fn from_reader(key_type: KeyType, r: &mut Reader<'_>) -> BytesResult<Self> {
249        Ok(match key_type {
250            KeyType::ED25519_KEY => CertifiedKey::Ed25519(r.extract()?),
251            KeyType::SHA256_OF_RSA => CertifiedKey::RsaSha256Digest(r.extract()?),
252            KeyType::SHA256_OF_X509 => CertifiedKey::X509Sha256Digest(r.extract()?),
253            _ => CertifiedKey::Unrecognized(UnrecognizedKey {
254                key_type,
255                key_digest: r.extract()?,
256            }),
257        })
258    }
259}
260
261/// An extension in a Tor certificate.
262#[derive(Debug, Clone)]
263enum CertExt {
264    /// Indicates which Ed25519 public key signed this cert.
265    SignedWithEd25519(SignedWithEd25519Ext),
266    /// An extension whose identity we don't recognize.
267    Unrecognized(UnrecognizedExt),
268}
269
270/// Any unrecognized extension on a Tor certificate.
271#[derive(Debug, Clone)]
272#[allow(unused)]
273struct UnrecognizedExt {
274    /// True iff this extension must be understand in order to validate the
275    /// certificate.
276    affects_validation: bool,
277    /// The type of the extension
278    ext_type: ExtType,
279    /// The body of the extension.
280    body: Vec<u8>,
281}
282
283impl CertExt {
284    /// Return the identifier code for this Extension.
285    fn ext_id(&self) -> ExtType {
286        match self {
287            CertExt::SignedWithEd25519(_) => ExtType::SIGNED_WITH_ED25519_KEY,
288            CertExt::Unrecognized(u) => u.ext_type,
289        }
290    }
291}
292
293/// Extension indicating that a key that signed a given certificate.
294#[derive(Debug, Clone)]
295struct SignedWithEd25519Ext {
296    /// The key that signed the certificate including this extension.
297    pk: ed25519::Ed25519Identity,
298}
299
300impl Readable for CertExt {
301    fn take_from(b: &mut Reader<'_>) -> BytesResult<Self> {
302        let len = b.take_u16()?;
303        let ext_type: ExtType = b.take_u8()?.into();
304        let flags = b.take_u8()?;
305        let body = b.take(len as usize)?;
306
307        Ok(match ext_type {
308            ExtType::SIGNED_WITH_ED25519_KEY => CertExt::SignedWithEd25519(SignedWithEd25519Ext {
309                pk: ed25519::Ed25519Identity::from_bytes(body).ok_or_else(|| {
310                    BytesError::InvalidMessage("wrong length on Ed25519 key".into())
311                })?,
312            }),
313            _ => {
314                if (flags & 1) != 0 {
315                    return Err(BytesError::InvalidMessage(
316                        "unrecognized certificate extension, with 'affects_validation' flag set."
317                            .into(),
318                    ));
319                }
320                CertExt::Unrecognized(UnrecognizedExt {
321                    affects_validation: false,
322                    ext_type,
323                    body: body.into(),
324                })
325            }
326        })
327    }
328}
329
330impl Ed25519Cert {
331    /// Try to decode a certificate from a byte slice.
332    ///
333    /// This function returns an error if the byte slice is not
334    /// completely exhausted.
335    ///
336    /// Note that the resulting KeyUnknownCertificate is not checked
337    /// for validity at all: you will need to provide it with an expected
338    /// signing key, then check it for timeliness and well-signedness.
339    pub fn decode(cert: &[u8]) -> BytesResult<KeyUnknownCert> {
340        let mut r = Reader::from_slice(cert);
341        let v = r.take_u8()?;
342        if v != 1 {
343            // This would be something other than a "v1" certificate. We don't
344            // understand those.
345            return Err(BytesError::InvalidMessage(
346                "Unrecognized certificate version".into(),
347            ));
348        }
349        let cert_type = r.take_u8()?.into();
350        let exp_hours = r.take_u32()?;
351        let mut cert_key_type = r.take_u8()?.into();
352
353        // This is a workaround for a tor bug: the key type is
354        // wrong. It was fixed in tor#40124, which got merged into Tor
355        // 0.4.5.x and later.
356        if cert_type == CertType::SIGNING_V_TLS_CERT && cert_key_type == KeyType::ED25519_KEY {
357            cert_key_type = KeyType::SHA256_OF_X509;
358        }
359
360        let cert_key = CertifiedKey::from_reader(cert_key_type, &mut r)?;
361        let n_exts = r.take_u8()?;
362        let mut extensions = Vec::new();
363        for _ in 0..n_exts {
364            let e: CertExt = r.extract()?;
365            extensions.push(e);
366        }
367
368        let sig_offset = r.consumed();
369        let signature: ed25519::Signature = r.extract()?;
370        r.should_be_exhausted()?;
371
372        let keyext = extensions
373            .iter()
374            .find(|e| e.ext_id() == ExtType::SIGNED_WITH_ED25519_KEY);
375
376        let included_pkey = match keyext {
377            Some(CertExt::SignedWithEd25519(s)) => Some(s.pk),
378            _ => None,
379        };
380
381        Ok(KeyUnknownCert {
382            cert: UncheckedCert {
383                cert: Ed25519Cert {
384                    exp_hours,
385                    cert_type,
386                    cert_key,
387                    extensions,
388
389                    signed_with: included_pkey,
390                },
391                text: cert[0..sig_offset].into(),
392                signature,
393            },
394        })
395    }
396
397    /// Return the time at which this certificate becomes expired
398    pub fn expiry(&self) -> std::time::SystemTime {
399        let d = std::time::Duration::new(u64::from(self.exp_hours) * 3600, 0);
400        std::time::SystemTime::UNIX_EPOCH + d
401    }
402
403    /// Return true iff this certificate will be expired at the time `when`.
404    pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
405        when >= self.expiry()
406    }
407
408    /// Return the signed key or object that is authenticated by this
409    /// certificate.
410    pub fn subject_key(&self) -> &CertifiedKey {
411        &self.cert_key
412    }
413
414    /// Return the ed25519 key that signed this certificate.
415    pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
416        self.signed_with.as_ref()
417    }
418
419    /// Return the type of this certificate.
420    pub fn cert_type(&self) -> CertType {
421        self.cert_type
422    }
423}
424
425/// A parsed Ed25519 certificate. Maybe it includes its signing key;
426/// maybe it doesn't.
427///
428/// To validate this cert, either it must contain its signing key,
429/// or the caller must know the signing key.  In the first case, call
430/// [`should_have_signing_key`](KeyUnknownCert::should_have_signing_key);
431/// in the latter, call
432/// [`should_be_signed_with`](KeyUnknownCert::should_be_signed_with).
433#[derive(Clone, Debug)]
434pub struct KeyUnknownCert {
435    /// The certificate whose signing key might not be known.
436    cert: UncheckedCert,
437}
438
439impl KeyUnknownCert {
440    /// Return the certificate type of the underling cert.
441    pub fn peek_cert_type(&self) -> CertType {
442        self.cert.cert.cert_type
443    }
444    /// Return subject key of the underlying cert.
445    pub fn peek_subject_key(&self) -> &CertifiedKey {
446        &self.cert.cert.cert_key
447    }
448
449    /// Check whether a given pkey is (or might be) a key that has correctly
450    /// signed this certificate.
451    ///
452    /// If pkey is None, this certificate must contain its signing key.
453    ///
454    /// On success, we can check whether the certificate is well-signed;
455    /// otherwise, we can't check the certificate.
456    #[deprecated(
457        since = "0.7.1",
458        note = "Use should_have_signing_key or should_be_signed_with instead."
459    )]
460    pub fn check_key(self, pkey: Option<&ed25519::Ed25519Identity>) -> CertResult<UncheckedCert> {
461        match pkey {
462            Some(wanted) => self.should_be_signed_with(wanted),
463            None => self.should_have_signing_key(),
464        }
465    }
466
467    /// Declare that this should be a self-contained certificate that contains its own
468    /// signing key.
469    ///
470    /// On success, this certificate did indeed turn out to be self-contained, and so
471    /// we can validate it.
472    /// On failure, this certificate was not self-contained.
473    pub fn should_have_signing_key(self) -> CertResult<UncheckedCert> {
474        let real_key = match &self.cert.cert.signed_with {
475            Some(a) => *a,
476            None => return Err(CertError::MissingPubKey),
477        };
478
479        Ok(UncheckedCert {
480            cert: Ed25519Cert {
481                signed_with: Some(real_key),
482                ..self.cert.cert
483            },
484            ..self.cert
485        })
486    }
487
488    /// Declare that this should be a certificate signed with a given key.
489    ///
490    /// On success, this certificate either listed the provided key, or did not
491    /// list any key: in either case, we can validate it.
492    /// On failure, this certificate claims to be signed with a different key.
493    pub fn should_be_signed_with(
494        self,
495        pkey: &ed25519::Ed25519Identity,
496    ) -> CertResult<UncheckedCert> {
497        let real_key = match &self.cert.cert.signed_with {
498            Some(a) if a == pkey => *pkey,
499            None => *pkey,
500            Some(_) => return Err(CertError::KeyMismatch),
501        };
502
503        Ok(UncheckedCert {
504            cert: Ed25519Cert {
505                signed_with: Some(real_key),
506                ..self.cert.cert
507            },
508            ..self.cert
509        })
510    }
511}
512
513/// A certificate that has been parsed, but whose signature and
514/// timeliness have not been checked.
515#[derive(Debug, Clone)]
516pub struct UncheckedCert {
517    /// The parsed certificate, possibly modified by inserting an externally
518    /// supplied key as its signing key.
519    cert: Ed25519Cert,
520
521    /// The signed text of the certificate. (Checking ed25519 signatures
522    /// forces us to store this.
523    // TODO(nickm)  It would be better to store a hash here, but we
524    // don't have the right Ed25519 API.
525    text: Vec<u8>,
526
527    /// The alleged signature
528    signature: ed25519::Signature,
529}
530
531/// A certificate that has been parsed and signature-checked, but whose
532/// timeliness has not been checked.
533pub struct SigCheckedCert {
534    /// The certificate that might or might not be timely
535    cert: Ed25519Cert,
536}
537
538impl UncheckedCert {
539    /// Split this unchecked cert into a component that assumes it has
540    /// been checked, and a signature to validate.
541    pub fn dangerously_split(
542        self,
543    ) -> CertResult<(SigCheckedCert, ed25519::ValidatableEd25519Signature)> {
544        use tor_checkable::SelfSigned;
545        let signing_key = self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
546        let signing_key = signing_key
547            .try_into()
548            .map_err(|_| CertError::BadSignature)?;
549        let signature =
550            ed25519::ValidatableEd25519Signature::new(signing_key, self.signature, &self.text[..]);
551        Ok((self.dangerously_assume_wellsigned(), signature))
552    }
553
554    /// Return subject key of the underlying cert.
555    pub fn peek_subject_key(&self) -> &CertifiedKey {
556        &self.cert.cert_key
557    }
558    /// Return signing key of the underlying cert.
559    pub fn peek_signing_key(&self) -> &ed25519::Ed25519Identity {
560        self.cert
561            .signed_with
562            .as_ref()
563            .expect("Made an UncheckedCert without a signing key")
564    }
565}
566
567impl tor_checkable::SelfSigned<SigCheckedCert> for UncheckedCert {
568    type Error = CertError;
569
570    fn is_well_signed(&self) -> CertResult<()> {
571        let pubkey = &self.cert.signed_with.ok_or(CertError::MissingPubKey)?;
572        let pubkey: ed25519::PublicKey = pubkey.try_into().map_err(|_| CertError::BadSignature)?;
573
574        pubkey
575            .verify(&self.text[..], &self.signature)
576            .map_err(|_| CertError::BadSignature)?;
577
578        Ok(())
579    }
580
581    fn dangerously_assume_wellsigned(self) -> SigCheckedCert {
582        SigCheckedCert { cert: self.cert }
583    }
584}
585
586impl tor_checkable::Timebound<Ed25519Cert> for Ed25519Cert {
587    type Error = tor_checkable::TimeValidityError;
588
589    fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
590        if self.is_expired_at(*t) {
591            let expiry = self.expiry();
592            Err(Self::Error::Expired(
593                t.duration_since(expiry)
594                    .expect("certificate expiry time inconsistent"),
595            ))
596        } else {
597            Ok(())
598        }
599    }
600
601    fn dangerously_assume_timely(self) -> Ed25519Cert {
602        self
603    }
604}
605
606impl tor_checkable::Timebound<Ed25519Cert> for SigCheckedCert {
607    type Error = tor_checkable::TimeValidityError;
608    fn is_valid_at(&self, t: &time::SystemTime) -> std::result::Result<(), Self::Error> {
609        self.cert.is_valid_at(t)
610    }
611
612    fn dangerously_assume_timely(self) -> Ed25519Cert {
613        self.cert.dangerously_assume_timely()
614    }
615}
616
617#[cfg(test)]
618mod test {
619    // @@ begin test lint list maintained by maint/add_warning @@
620    #![allow(clippy::bool_assert_comparison)]
621    #![allow(clippy::clone_on_copy)]
622    #![allow(clippy::dbg_macro)]
623    #![allow(clippy::mixed_attributes_style)]
624    #![allow(clippy::print_stderr)]
625    #![allow(clippy::print_stdout)]
626    #![allow(clippy::single_char_pattern)]
627    #![allow(clippy::unwrap_used)]
628    #![allow(clippy::unchecked_duration_subtraction)]
629    #![allow(clippy::useless_vec)]
630    #![allow(clippy::needless_pass_by_value)]
631    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
632    use super::*;
633    use hex_literal::hex;
634
635    #[test]
636    fn parse_unrecognized_ext() -> BytesResult<()> {
637        // case one: a flag is set but we don't know it
638        let b = hex!("0009 99 10 657874656e73696f6e");
639        let mut r = Reader::from_slice(&b);
640        let e: CertExt = r.extract()?;
641        r.should_be_exhausted()?;
642
643        assert_eq!(e.ext_id(), 0x99.into());
644
645        // case two: we've been told to ignore the cert if we can't
646        // handle the extension.
647        let b = hex!("0009 99 11 657874656e73696f6e");
648        let mut r = Reader::from_slice(&b);
649        let e: Result<CertExt, BytesError> = r.extract();
650        assert!(e.is_err());
651        assert_eq!(
652            e.err().unwrap(),
653            BytesError::InvalidMessage(
654                "unrecognized certificate extension, with 'affects_validation' flag set.".into()
655            )
656        );
657
658        Ok(())
659    }
660
661    #[test]
662    fn certified_key() -> BytesResult<()> {
663        let b =
664            hex!("4c27616d6f757220756e6974206365757820717527656e636861c3ae6e616974206c6520666572");
665        let mut r = Reader::from_slice(&b);
666
667        let ck = CertifiedKey::from_reader(KeyType::SHA256_OF_RSA, &mut r)?;
668        assert_eq!(ck.as_bytes(), &b[..32]);
669        assert_eq!(ck.key_type(), KeyType::SHA256_OF_RSA);
670        assert_eq!(r.remaining(), 7);
671
672        let mut r = Reader::from_slice(&b);
673        let ck = CertifiedKey::from_reader(42.into(), &mut r)?;
674        assert_eq!(ck.as_bytes(), &b[..32]);
675        assert_eq!(ck.key_type(), 42.into());
676        assert_eq!(r.remaining(), 7);
677
678        Ok(())
679    }
680}