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//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46
47mod 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
68/// A Result defined to use CertError
69type CertResult<T> = std::result::Result<T, CertError>;
70
71caret_int! {
72    /// Recognized values for Tor's certificate type field.
73    ///
74    /// In the names used here, "X_V_Y" means "key X verifying key Y",
75    /// whereas "X_CC_Y" means "key X cross-certifying key Y".  In both
76    /// cases, X is the key that is doing the signing, and Y is the key
77    /// or object that is getting signed.
78    ///
79    /// Not every one of these types is valid for an Ed25519
80    /// certificate.  Some are for X.509 certs in a CERTS cell; some
81    /// are for RSA->Ed crosscerts in a CERTS cell.
82    pub struct CertType(u8) {
83        /// TLS link key, signed with RSA identity. X.509 format. (Obsolete)
84        TLS_LINK_X509 = 0x01,
85        /// Self-signed RSA identity certificate. X.509 format. (Legacy)
86        RSA_ID_X509 = 0x02,
87        /// RSA lnk authentication key signed with RSA identity
88        /// key. X.509 format. (Obsolete)
89        LINK_AUTH_X509 = 0x03,
90
91        /// Identity verifying a signing key, directly.
92        IDENTITY_V_SIGNING = 0x04,
93
94        /// Signing key verifying a TLS certificate by digest.
95        SIGNING_V_TLS_CERT = 0x05,
96
97        /// Signing key verifying a link authentication key.
98        SIGNING_V_LINK_AUTH = 0x06,
99
100        /// RSA identity key certifying an Ed25519 identity key. RSA
101        /// crosscert format. (Legacy)
102        RSA_ID_V_IDENTITY = 0x07,
103
104        /// For onion services: short-term descriptor signing key
105        /// (`KP_hs_desc_sign`), signed with blinded onion service identity
106        /// (`KP_hs_blind_id`).
107        HS_BLINDED_ID_V_SIGNING = 0x08,
108
109        /// For onion services: Introduction point authentication key
110        /// (`KP_hs_ipt_sid`), signed with short term descriptor signing key
111        /// (`KP_hs_desc_sign`).
112        ///
113        /// This one is, sadly, a bit complicated. In the original specification
114        /// it was meant to be a cross-certificate, where the signature would be
115        /// _on_ the descriptor signing key, _signed with_ the intro TID key.
116        /// But we got it backwards in the C Tor implementation, and now, for
117        /// compatibility, we are stuck doing it backwards in the future.
118        ///
119        /// If we find in the future that it is actually important to
120        /// cross-certify these keys (as originally intended), then we should
121        /// add a new certificate type, and put the new certificate in the onion
122        /// service descriptor.
123        HS_IP_V_SIGNING = 0x09,
124
125        /// An ntor key converted to a ed25519 key, cross-certifying an
126        /// identity key.
127        NTOR_CC_IDENTITY = 0x0A,
128
129        /// For onion services: Ntor encryption key (`KP_hss_ntor`),
130        /// converted to ed25519, signed with the descriptor signing key
131        /// (`KP_hs_desc_sign`).
132        ///
133        /// As with [`HS_IP_V_SIGNING`](CertType::HS_IP_V_SIGNING), this
134        /// certificate type is backwards.  In the original specification it was
135        /// meant to be a cross certificate, with the signing and signed keys
136        /// reversed.
137        HS_IP_CC_SIGNING = 0x0B,
138
139        /// For relays: family key certifying membership of a relay
140        /// by signing its identity.
141        FAMILY_V_IDENTITY = 0x0C,
142    }
143}
144
145caret_int! {
146    /// Extension identifiers for extensions in certificates.
147    pub struct ExtType(u8) {
148        /// Extension indicating an Ed25519 key that signed this certificate.
149        ///
150        /// Certificates do not always contain the key that signed them.
151        SIGNED_WITH_ED25519_KEY = 0x04,
152    }
153}
154
155caret_int! {
156    /// Identifiers for the type of key or object getting signed.
157    pub struct KeyType(u8) {
158        /// Identifier for an Ed25519 key.
159        ED25519_KEY = 0x01,
160        /// Identifier for the SHA256 of an DER-encoded RSA key.
161        SHA256_OF_RSA = 0x02,
162        /// Identifies the SHA256 of an X.509 certificate.
163        SHA256_OF_X509 = 0x03,
164    }
165}
166
167/// Structure for an Ed25519-signed certificate as described in Tor's
168/// cert-spec.txt.
169#[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    /// How many _hours_ after the epoch will this certificate expire?
177    #[cfg_attr(feature = "encode", builder(setter(custom)))]
178    exp_hours: u32,
179    /// Type of the certificate; recognized values are in certtype::*
180    cert_type: CertType,
181    /// The key or object being certified.
182    cert_key: CertifiedKey,
183    /// A list of extensions.
184    #[allow(unused)]
185    #[cfg_attr(feature = "encode", builder(setter(custom)))]
186    extensions: Vec<CertExt>,
187    /// The key that signed this cert.
188    ///
189    /// Once the cert has been unwrapped from an KeyUnknownCert, this field will
190    /// be set.  If there is a `SignedWithEd25519` extension in
191    /// `self.extensions`, this will match it.
192    #[cfg_attr(feature = "encode", builder(setter(custom)))]
193    signed_with: Option<ed25519::Ed25519Identity>,
194}
195
196/// One of the data types that can be certified by an Ed25519Cert.
197#[derive(Debug, Clone)]
198#[non_exhaustive]
199pub enum CertifiedKey {
200    /// An Ed25519 public key, signed directly.
201    Ed25519(ed25519::Ed25519Identity),
202    /// The SHA256 digest of a DER-encoded RsaPublicKey
203    RsaSha256Digest([u8; 32]),
204    /// The SHA256 digest of an X.509 certificate.
205    X509Sha256Digest([u8; 32]),
206    /// Some unrecognized key type.
207    Unrecognized(UnrecognizedKey),
208}
209
210/// A key whose type we didn't recognize.
211#[derive(Debug, Clone)]
212pub struct UnrecognizedKey {
213    /// Actual type of the key.
214    key_type: KeyType,
215    /// digest of the key, or the key itself.
216    key_digest: [u8; 32],
217}
218
219impl CertifiedKey {
220    /// Return the byte that identifies the type of this key.
221    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    /// Return the bytes that are used for the body of this certified
231    /// key or object.
232    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    /// If this is an Ed25519 public key, return Some(key).
241    /// Otherwise, return None.
242    pub fn as_ed25519(&self) -> Option<&ed25519::Ed25519Identity> {
243        match self {
244            CertifiedKey::Ed25519(k) => Some(k),
245            _ => None,
246        }
247    }
248    /// Try to extract a CertifiedKey from a Reader, given that we have
249    /// already read its type as `key_type`.
250    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/// An extension in a Tor certificate.
264#[derive(Debug, Clone)]
265enum CertExt {
266    /// Indicates which Ed25519 public key signed this cert.
267    SignedWithEd25519(SignedWithEd25519Ext),
268    /// An extension whose identity we don't recognize.
269    Unrecognized(UnrecognizedExt),
270}
271
272/// Any unrecognized extension on a Tor certificate.
273#[derive(Debug, Clone)]
274#[allow(unused)]
275struct UnrecognizedExt {
276    /// True iff this extension must be understand in order to validate the
277    /// certificate.
278    affects_validation: bool,
279    /// The type of the extension
280    ext_type: ExtType,
281    /// The body of the extension.
282    body: Vec<u8>,
283}
284
285impl CertExt {
286    /// Return the identifier code for this Extension.
287    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/// Extension indicating that a key that signed a given certificate.
296#[derive(Debug, Clone)]
297struct SignedWithEd25519Ext {
298    /// The key that signed the certificate including this extension.
299    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    /// Try to decode a certificate from a byte slice.
334    ///
335    /// This function returns an error if the byte slice is not
336    /// completely exhausted.
337    ///
338    /// Note that the resulting KeyUnknownCertificate is not checked
339    /// for validity at all: you will need to provide it with an expected
340    /// signing key, then check it for timeliness and well-signedness.
341    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            // This would be something other than a "v1" certificate. We don't
346            // understand those.
347            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        // This is a workaround for a tor bug: the key type is
356        // wrong. It was fixed in tor#40124, which got merged into Tor
357        // 0.4.5.x and later.
358        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    /// Return the time at which this certificate becomes expired
400    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    /// Return true iff this certificate will be expired at the time `when`.
406    pub fn is_expired_at(&self, when: std::time::SystemTime) -> bool {
407        when >= self.expiry()
408    }
409
410    /// Return the signed key or object that is authenticated by this
411    /// certificate.
412    pub fn subject_key(&self) -> &CertifiedKey {
413        &self.cert_key
414    }
415
416    /// Return the ed25519 key that signed this certificate.
417    pub fn signing_key(&self) -> Option<&ed25519::Ed25519Identity> {
418        self.signed_with.as_ref()
419    }
420
421    /// Return the type of this certificate.
422    pub fn cert_type(&self) -> CertType {
423        self.cert_type
424    }
425}
426
427/// A parsed Ed25519 certificate. Maybe it includes its signing key;
428/// maybe it doesn't.
429///
430/// To validate this cert, either it must contain its signing key,
431/// or the caller must know the signing key.  In the first case, call
432/// [`should_have_signing_key`](KeyUnknownCert::should_have_signing_key);
433/// in the latter, call
434/// [`should_be_signed_with`](KeyUnknownCert::should_be_signed_with).
435#[derive(Clone, Debug)]
436pub struct KeyUnknownCert {
437    /// The certificate whose signing key might not be known.
438    cert: UncheckedCert,
439}
440
441impl KeyUnknownCert {
442    /// Return the certificate type of the underling cert.
443    pub fn peek_cert_type(&self) -> CertType {
444        self.cert.cert.cert_type
445    }
446    /// Return subject key of the underlying cert.
447    pub fn peek_subject_key(&self) -> &CertifiedKey {
448        &self.cert.cert.cert_key
449    }
450
451    /// Check whether a given pkey is (or might be) a key that has correctly
452    /// signed this certificate.
453    ///
454    /// If pkey is None, this certificate must contain its signing key.
455    ///
456    /// On success, we can check whether the certificate is well-signed;
457    /// otherwise, we can't check the certificate.
458    #[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    /// Declare that this should be a self-contained certificate that contains its own
470    /// signing key.
471    ///
472    /// On success, this certificate did indeed turn out to be self-contained, and so
473    /// we can validate it.
474    /// On failure, this certificate was not self-contained.
475    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    /// Declare that this should be a certificate signed with a given key.
491    ///
492    /// On success, this certificate either listed the provided key, or did not
493    /// list any key: in either case, we can validate it.
494    /// On failure, this certificate claims to be signed with a different key.
495    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/// A certificate that has been parsed, but whose signature and
516/// timeliness have not been checked.
517#[derive(Debug, Clone)]
518pub struct UncheckedCert {
519    /// The parsed certificate, possibly modified by inserting an externally
520    /// supplied key as its signing key.
521    cert: Ed25519Cert,
522
523    /// The signed text of the certificate. (Checking ed25519 signatures
524    /// forces us to store this.
525    // TODO(nickm)  It would be better to store a hash here, but we
526    // don't have the right Ed25519 API.
527    text: Vec<u8>,
528
529    /// The alleged signature
530    signature: ed25519::Signature,
531}
532
533/// A certificate that has been parsed and signature-checked, but whose
534/// timeliness has not been checked.
535pub struct SigCheckedCert {
536    /// The certificate that might or might not be timely
537    cert: Ed25519Cert,
538}
539
540impl UncheckedCert {
541    /// Split this unchecked cert into a component that assumes it has
542    /// been checked, and a signature to validate.
543    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    /// Return subject key of the underlying cert.
557    pub fn peek_subject_key(&self) -> &CertifiedKey {
558        &self.cert.cert_key
559    }
560    /// Return signing key of the underlying cert.
561    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    // @@ begin test lint list maintained by maint/add_warning @@
622    #![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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
634    use super::*;
635    use hex_literal::hex;
636
637    #[test]
638    fn parse_unrecognized_ext() -> BytesResult<()> {
639        // case one: a flag is set but we don't know it
640        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        // case two: we've been told to ignore the cert if we can't
648        // handle the extension.
649        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}