Skip to main content

tls2/
certificates.rs

1use crypto::encoding as crypto_encoding;
2
3use crate::{CryptoProvider, Error, MAX_CERTS, SignatureScheme};
4
5#[cfg(not(target_os = "macos"))]
6const DEFAULT_ROOT_DIRS: &[&str] = &[
7    "/etc/ssl/cert",
8    "/etc/ssl/certs",
9    "/etc/pki/tls/certs",
10    "/usr/local/share/certs",
11    "/usr/share/ca-certificates/mozilla",
12];
13
14#[cfg(target_os = "macos")]
15const DEFAULT_ROOT_DIRS: &[&str] = &["/etc/ssl", "/usr/local/etc/openssl/certs"];
16
17#[async_trait::async_trait]
18pub trait CertificateVerifier {
19    async fn verify_certificate(&self, cert: &ReceivedCertificate, server_name: Option<&str>) -> Result<(), Error>;
20}
21
22/// A certificate received from the peer during the TLS handshake.
23pub enum ReceivedCertificate<'a> {
24    /// X.509 certificate chain, end-entity first.
25    X509 {
26        chain: heapless::Vec<ParsedCertificate<'a>, MAX_CERTS>,
27    },
28    /// Raw public key (RFC 7250).
29    RawPublicKey {
30        public_key: &'a [u8],
31        scheme: SignatureScheme,
32    },
33}
34
35/// A pre-parsed X.509 certificate with all commonly accessed fields
36/// extracted in a single DER walk.
37pub struct ParsedCertificate<'a> {
38    /// Full DER encoding.
39    pub der: &'a [u8],
40    /// SubjectPublicKeyInfo DER.
41    pub spki: &'a [u8],
42    /// Raw public key bytes (BIT STRING content, minus unused-bits byte).
43    pub public_key: &'a [u8],
44    /// Issuer Distinguished Name (value of the Name SEQUENCE).
45    pub issuer_dn: &'a [u8],
46    /// Subject Distinguished Name (value of the Name SEQUENCE).
47    pub subject_dn: &'a [u8],
48    /// TBSCertificate raw bytes (tag + length + value) — the signed portion.
49    pub tbs: &'a [u8],
50    /// Signature value bytes (BIT STRING content).
51    pub signature_value: &'a [u8],
52    /// Signature algorithm OID.
53    pub sig_alg_oid: &'a [u8],
54    /// SPKI algorithm OID.
55    pub spki_alg_oid: &'a [u8],
56    /// Whether Basic Constraints cA is TRUE (`None` = extension absent).
57    pub is_ca: Option<bool>,
58    /// Whether EKU includes serverAuth (`None` = extension absent).
59    pub has_server_auth_eku: Option<bool>,
60    /// notBefore as Unix timestamp (seconds since epoch).
61    pub not_before: u64,
62    /// notAfter as Unix timestamp (seconds since epoch).
63    pub not_after: u64,
64}
65
66impl<'a> ParsedCertificate<'a> {
67    /// Parse a DER-encoded X.509 certificate, extracting all fields at once.
68    pub fn from_der(der: &'a [u8]) -> Result<Self, Error> {
69        let spki = x509::extract_spki_from_cert(der).map_err(|_| Error::CertificateParseFailed)?;
70        let public_key = x509::extract_key_from_spki(spki).map_err(|_| Error::CertificateParseFailed)?;
71        let issuer_dn = x509::extract_issuer_dn(der).map_err(|_| Error::CertificateParseFailed)?;
72        let subject_dn = x509::extract_subject_dn(der).map_err(|_| Error::CertificateParseFailed)?;
73        let tbs = x509::extract_tbs_cert(der).map_err(|_| Error::CertificateParseFailed)?;
74        let signature_value = x509::extract_signature_value(der).map_err(|_| Error::CertificateParseFailed)?;
75        let sig_alg_oid = x509::extract_signature_algorithm_oid(der).map_err(|_| Error::CertificateParseFailed)?;
76        let spki_alg_oid = x509::extract_spki_algorithm_oid(spki).map_err(|_| Error::CertificateParseFailed)?;
77        let is_ca = x509::is_ca(der);
78        let has_server_auth_eku = x509::has_eku_server_auth(der);
79        let (nb, na) = x509::parse_validity(der).map_err(|_| Error::CertificateParseFailed)?;
80
81        Ok(Self {
82            der,
83            spki,
84            public_key,
85            issuer_dn,
86            subject_dn,
87            tbs,
88            signature_value,
89            sig_alg_oid,
90            spki_alg_oid,
91            is_ca,
92            has_server_auth_eku,
93            not_before: nb.to_unix_seconds(),
94            not_after: na.to_unix_seconds(),
95        })
96    }
97}
98
99/// A trusted root certificate authority with pre-parsed fields.
100///
101/// All fields use fixed-capacity [`heapless::Vec`] for zero-allocation
102/// storage. Use [`RootCa::from_der`] to parse a DER-encoded certificate.
103#[derive(Clone)]
104pub struct RootCa {
105    pub subject_dn: heapless::Vec<u8, 256>,
106    pub spki: heapless::Vec<u8, 512>,
107    pub spki_alg_oid: heapless::Vec<u8, 16>,
108    pub not_before: u64,
109    pub not_after: u64,
110}
111
112impl RootCa {
113    /// Parse a DER-encoded X.509 certificate into a root trust anchor.
114    pub fn from_der(der: &[u8]) -> Result<Self, Error> {
115        let parsed = ParsedCertificate::from_der(der)?;
116        let mut subject_dn = heapless::Vec::new();
117        subject_dn
118            .extend_from_slice(parsed.subject_dn)
119            .map_err(|_| Error::CertificateParseFailed)?;
120        let mut spki = heapless::Vec::new();
121        spki.extend_from_slice(parsed.spki)
122            .map_err(|_| Error::CertificateParseFailed)?;
123        let mut spki_alg_oid = heapless::Vec::new();
124        spki_alg_oid
125            .extend_from_slice(parsed.spki_alg_oid)
126            .map_err(|_| Error::CertificateParseFailed)?;
127        Ok(Self {
128            subject_dn,
129            spki,
130            spki_alg_oid,
131            not_before: parsed.not_before,
132            not_after: parsed.not_after,
133        })
134    }
135}
136
137/// A raw public key pinned as a trust anchor (RFC 7250).
138///
139/// Used with [`DefaultCertificateVerifier::with_raw_keys`] to validate
140/// raw-public-key connections. Each entry holds the raw key bytes and
141/// the [`SignatureScheme`] the key belongs to.
142#[derive(Clone)]
143pub struct RawPublicKey {
144    pub public_key: alloc::borrow::Cow<'static, [u8]>,
145    pub scheme: SignatureScheme,
146}
147
148impl RawPublicKey {
149    pub fn new(scheme: SignatureScheme, public_key: impl Into<alloc::borrow::Cow<'static, [u8]>>) -> Self {
150        Self {
151            public_key: public_key.into(),
152            scheme,
153        }
154    }
155}
156
157// ── Clock trait ──
158
159/// A wall clock used to check certificate validity periods.
160///
161/// Returns the current Unix timestamp (seconds since epoch). This trait
162/// exists so that no_std environments can inject their own time source
163/// (hardware RTC, NTP, etc.) instead of relying on `std::time::SystemTime`.
164pub trait Clock: Send + Sync {
165    fn now(&self) -> u64;
166}
167
168/// A [`Clock`] backed by `std::time::SystemTime`.
169///
170/// Available only when the `std` feature is enabled.
171#[cfg(feature = "std")]
172pub struct SystemClock;
173
174#[cfg(feature = "std")]
175impl Clock for SystemClock {
176    fn now(&self) -> u64 {
177        std::time::SystemTime::now()
178            .duration_since(std::time::UNIX_EPOCH)
179            .unwrap_or_default()
180            .as_secs()
181    }
182}
183
184// ── Default certificate verifier ──
185
186/// Default certificate validator that can handle both X509 chains and raw public keys.
187///
188/// Validates X.509 chains and raw public keys against configured trust
189/// anchors.  Use [`with_roots`][Self::with_roots] to seed X.509 root CAs
190/// and [`with_raw_keys`][Self::with_raw_keys] to pin raw public keys
191/// (RFC 7250).
192///
193/// Call [`danger_with_no_verification`][Self::danger_with_no_verification] to skip
194/// all validation. Intended for testing only.
195///
196/// This is the recommended `CertificateVerifier` unless you are working with very tiny embedded
197/// systems that only need one or more pinned keys.
198///
199/// # Examples
200///
201/// ```ignore
202/// use tls2::{CertificateVerifier, ClientConfig, DefaultCertificateVerifier, RawPublicKey, SignatureScheme};
203///
204/// // Full X.509 validation with system roots (requires `std` feature)
205/// let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).with_system_roots();
206///
207/// // Pin raw public keys only, reject everything else
208/// let pinned = RawPublicKey::new(SignatureScheme::Ed25519, b"...key bytes...");
209/// let verifier = DefaultCertificateVerifier::new().with_raw_keys([pinned]);
210///
211/// // Testing mode — accept anything
212/// let verifier = DefaultCertificateVerifier::new().danger_with_no_verification();
213/// ```
214#[cfg(feature = "default-certificate-verifier")]
215#[derive(Clone)]
216pub struct DefaultCertificateVerifier<C: CryptoProvider> {
217    crypto: C,
218    roots: Option<alloc::vec::Vec<RootCa>>,
219    raw_keys: Option<alloc::vec::Vec<RawPublicKey>>,
220    accept_any: bool,
221    clock: Option<alloc::sync::Arc<dyn Clock>>,
222}
223
224#[cfg(feature = "default-certificate-verifier")]
225impl<C: CryptoProvider> DefaultCertificateVerifier<C> {
226    /// Create a new verifier with no trust anchors configured.
227    ///
228    /// By default:
229    /// - No X.509 roots are configured — X.509 chains are rejected.
230    /// - No raw public keys are pinned — raw public keys are rejected.
231    ///
232    /// Use the builder methods ([`with_roots`][Self::with_roots],
233    /// [`with_system_roots`][Self::with_system_roots],
234    /// [`with_raw_keys`][Self::with_raw_keys],
235    /// [`danger_with_no_verification`][Self::danger_with_no_verification]) to configure
236    /// the verifier.
237    pub fn new(crypto: C) -> Self {
238        Self {
239            crypto,
240            roots: None,
241            raw_keys: None,
242            accept_any: false,
243            clock: None,
244        }
245    }
246
247    /// Load root CAs from the operating system's trust store.
248
249    pub fn with_system_roots(mut self) -> Self {
250        self.roots = Some(load_roots(DEFAULT_ROOT_DIRS));
251        self
252    }
253
254    /// Add custom root trust anchors.
255    pub fn with_roots(mut self, custom_roots: impl IntoIterator<Item = RootCa>) -> Self {
256        let iter = custom_roots.into_iter();
257        let (iter_size_hint, _) = iter.size_hint();
258        let mut roots = self
259            .roots
260            .take()
261            .unwrap_or(alloc::vec::Vec::with_capacity(iter_size_hint));
262
263        roots.reserve(iter_size_hint); // basically a no-op if the Vec is slaready correctly sized
264        roots.extend(iter);
265        self.roots = Some(roots);
266        self
267    }
268
269    /// Pin raw public keys as trust anchors (RFC 7250).
270    ///
271    /// When raw keys are configured, only received raw public keys
272    /// that match one of the pinned entries are accepted.  X.509
273    /// chains are not affected by this list.
274    pub fn with_raw_keys(mut self, keys: impl IntoIterator<Item = RawPublicKey>) -> Self {
275        let iter = keys.into_iter();
276        let (iter_size_hint, _) = iter.size_hint();
277        let mut raw_keys = self
278            .raw_keys
279            .take()
280            .unwrap_or(alloc::vec::Vec::with_capacity(iter_size_hint));
281
282        raw_keys.reserve(iter_size_hint); // basically a no-op if the Vec is slaready correctly sized
283        raw_keys.extend(iter);
284        self.raw_keys = Some(raw_keys);
285        self
286    }
287
288    /// Skip all certificate validation.
289    ///
290    /// Every certificate — X.509 chain or raw public key — is
291    /// accepted without any checks.  **Insecure; intended for
292    /// testing only.**
293    pub fn danger_with_no_verification(mut self) -> Self {
294        self.accept_any = true;
295        self
296    }
297
298    /// Use a custom clock for certificate validity checks.
299    ///
300    /// When no clock is set, [`DefaultCertificateVerifier`] falls back to
301    /// `std::time::SystemTime` if the `std` feature is enabled.  Without
302    /// `std` and without a custom clock, validation returns
303    /// [`Error::CertificateClockMissing`].
304    pub fn with_clock(mut self, clock: alloc::sync::Arc<dyn Clock>) -> Self {
305        self.clock = Some(clock);
306        self
307    }
308}
309
310#[cfg(feature = "default-certificate-verifier")]
311#[async_trait::async_trait]
312impl<C: CryptoProvider> CertificateVerifier for DefaultCertificateVerifier<C> {
313    async fn verify_certificate(&self, cert: &ReceivedCertificate, server_name: Option<&str>) -> Result<(), Error> {
314        if self.accept_any {
315            return Ok(());
316        }
317
318        match cert {
319            ReceivedCertificate::RawPublicKey {
320                public_key,
321                scheme,
322            } => {
323                let keys = self.raw_keys.as_ref().ok_or(Error::CertificateNoTrustedRootFound {
324                    searched_roots: 0,
325                })?;
326                for key in keys {
327                    if key.scheme == *scheme && key.public_key.as_ref() == *public_key {
328                        return Ok(());
329                    }
330                }
331                Err(Error::CertificateNoTrustedRootFound {
332                    searched_roots: keys.len(),
333                })
334            }
335            ReceivedCertificate::X509 {
336                chain,
337            } => {
338                if self.roots.is_none() || self.roots.as_ref().unwrap().is_empty() {
339                    return Err(Error::CertificateNoTrustedRootFound {
340                        searched_roots: 0,
341                    });
342                }
343                self.validate_chain(chain, server_name)
344            }
345        }
346    }
347}
348
349// ── Chain validation (private) ──
350
351#[cfg(feature = "default-certificate-verifier")]
352impl<C: CryptoProvider> DefaultCertificateVerifier<C> {
353    fn validate_chain(&self, chain: &[ParsedCertificate], server_name: Option<&str>) -> Result<(), Error> {
354        if chain.is_empty() {
355            return Err(Error::CertificateEmptyChain);
356        }
357
358        let server_name = server_name.ok_or(Error::CertificateServerNameRequired)?;
359
360        self.validate_ee_extensions(&chain[0], server_name)?;
361
362        let now = match &self.clock {
363            Some(clock) => clock.now(),
364            None => {
365                #[cfg(feature = "std")]
366                {
367                    std::time::SystemTime::now()
368                        .duration_since(std::time::UNIX_EPOCH)
369                        .unwrap_or_default()
370                        .as_secs()
371                }
372
373                #[cfg(not(feature = "std"))]
374                return Err(Error::CertificateClockMissing);
375            }
376        };
377
378        for i in 0..chain.len() {
379            let cert = &chain[i];
380            let is_ee = i == 0;
381            let is_last = i == chain.len() - 1;
382
383            let (issuer_spki, issuer_subject_dn, issuer_spki_alg_oid): (&[u8], &[u8], &[u8]) = {
384                if i + 1 < chain.len() {
385                    let issuer = &chain[i + 1];
386                    (issuer.spki, issuer.subject_dn, issuer.spki_alg_oid)
387                } else {
388                    match self.find_root(cert.issuer_dn, now) {
389                        Ok(root) => (&root.spki[..], &root.subject_dn[..], &root.spki_alg_oid[..]),
390                        Err(_) => {
391                            let root = self.find_root_by_spki(cert.spki, now)?;
392                            (&root.spki[..], &root.subject_dn[..], &root.spki_alg_oid[..])
393                        }
394                    }
395                }
396            };
397
398            if !x509::dn_equal(cert.issuer_dn, issuer_subject_dn) {
399                let is_self_key = !is_ee && self.is_own_root_key(cert.spki, now);
400                if !is_self_key {
401                    return Err(Error::CertificateIssuerSubjectDnMismatch);
402                }
403            }
404
405            let is_cross_signed = !is_ee
406                && is_last
407                && self.is_own_root_key(cert.spki, now)
408                && !x509::dn_equal(cert.issuer_dn, issuer_subject_dn);
409            if !is_cross_signed {
410                self.verify_cert_signature(cert, issuer_spki, issuer_spki_alg_oid)
411                    .map_err(|_| Error::CertificateSignatureVerificationFailed)?;
412            }
413
414            if now < cert.not_before {
415                return Err(Error::CertificateNotYetValid);
416            }
417            if now > cert.not_after {
418                return Err(Error::CertificateExpired);
419            }
420
421            if !is_ee && cert.is_ca != Some(true) {
422                return Err(Error::CertificateIntermediateNotCa);
423            }
424        }
425
426        Ok(())
427    }
428
429    fn validate_ee_extensions(&self, ee: &ParsedCertificate, server_name: &str) -> Result<(), Error> {
430        let matched = x509::check_san_dns_name(ee.der, server_name).map_err(|_| Error::CertificateParseFailed)?;
431        if !matched {
432            return Err(Error::CertificateSubjectNameMismatch);
433        }
434
435        if ee.is_ca == Some(true) {
436            return Err(Error::CertificateEndEntityMustNotBeCa);
437        }
438
439        if let Some(false) = ee.has_server_auth_eku {
440            return Err(Error::CertificateEkuDoesNotIncludeServerAuth);
441        }
442
443        Ok(())
444    }
445
446    fn verify_cert_signature(
447        &self,
448        cert: &ParsedCertificate,
449        issuer_spki: &[u8],
450        issuer_spki_alg_oid: &[u8],
451    ) -> Result<(), Error> {
452        let scheme = determine_signature_scheme(cert.sig_alg_oid, issuer_spki_alg_oid)?;
453
454        let public_key = x509::extract_key_from_spki(issuer_spki).map_err(|_| Error::CertificateParseFailed)?;
455
456        self.crypto.verify(scheme, public_key, cert.tbs, cert.signature_value)
457    }
458
459    // now is an Unix timestamp in second
460    fn find_root(&self, issuer_dn: &[u8], now: u64) -> Result<&RootCa, Error> {
461        if self.roots.is_none() {
462            return Err(Error::CertificateNoTrustedRootFound {
463                searched_roots: 0,
464            });
465        }
466
467        let roots = self.roots.as_ref().unwrap();
468
469        for root in roots {
470            if now < root.not_before || now > root.not_after {
471                continue;
472            }
473            if x509::dn_equal(&root.subject_dn[..], issuer_dn) {
474                return Ok(root);
475            }
476        }
477        Err(Error::CertificateNoTrustedRootFound {
478            searched_roots: roots.len(),
479        })
480    }
481
482    // now is an Unix timestamp in second
483    fn find_root_by_spki(&self, spki: &[u8], now: u64) -> Result<&RootCa, Error> {
484        if self.roots.is_none() {
485            return Err(Error::CertificateNoRootFoundBySpkiMatching);
486        }
487
488        let roots = self.roots.as_ref().unwrap();
489        for root in roots {
490            if now < root.not_before || now > root.not_after {
491                continue;
492            }
493
494            if &root.spki[..] == spki {
495                return Ok(root);
496            }
497        }
498        Err(Error::CertificateNoRootFoundBySpkiMatching)
499    }
500
501    fn is_own_root_key(&self, spki: &[u8], now: u64) -> bool {
502        self.find_root_by_spki(spki, now).is_ok()
503    }
504}
505
506fn determine_signature_scheme(sig_alg_oid: &[u8], spki_alg_oid: &[u8]) -> Result<SignatureScheme, Error> {
507    if sig_alg_oid == x509::OID_ED25519 {
508        return Ok(SignatureScheme::Ed25519);
509    }
510    if sig_alg_oid == x509::OID_ECDSA_SHA256 && spki_alg_oid == x509::OID_EC_PUBLIC_KEY_ALG {
511        return Ok(SignatureScheme::EcdsaP256Sha256);
512    }
513    if sig_alg_oid == x509::OID_ECDSA_SHA384 && spki_alg_oid == x509::OID_EC_PUBLIC_KEY_ALG {
514        return Ok(SignatureScheme::EcdsaP384Sha384);
515    }
516    if sig_alg_oid == x509::OID_RSA_SHA256 {
517        return Ok(SignatureScheme::RsaPkcs1Sha256);
518    }
519    if sig_alg_oid == x509::OID_RSA_SHA384 {
520        return Ok(SignatureScheme::RsaPkcs1Sha384);
521    }
522    if sig_alg_oid == x509::OID_RSA_SHA512 {
523        return Ok(SignatureScheme::RsaPkcs1Sha512);
524    }
525    if sig_alg_oid == x509::OID_RSA_PSS {
526        return Ok(SignatureScheme::RsaPssRsaSha256);
527    }
528    Err(Error::CertificateUnsupportedSignatureAlgorithm)
529}
530
531// ── System root loading ──
532
533#[cfg(feature = "std")]
534pub fn load_roots(paths: &[&str]) -> alloc::vec::Vec<RootCa> {
535    let mut roots = alloc::vec::Vec::with_capacity(120);
536    for dir in paths {
537        load_certs_from_dir(&mut roots, dir);
538    }
539    roots
540}
541
542#[cfg(feature = "std")]
543fn load_certs_from_dir(roots: &mut alloc::vec::Vec<RootCa>, dir: &str) {
544    let Ok(entries) = std::fs::read_dir(dir) else {
545        return;
546    };
547    for entry in entries.flatten() {
548        let path = entry.path();
549        let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
550        if ext != "crt" && ext != "pem" && ext != "cer" && (!ext.is_empty() || !path.is_file()) {
551            continue;
552        }
553        load_certs_from_file(roots, &path);
554    }
555}
556
557/// Try to load one or more certificates per file.
558/// If the file contains PEM-encoded certificates, it loads them all.
559/// Otherwise, it tries to parse the file as a binary DER-encoded certificate.
560fn load_certs_from_file(roots: &mut alloc::vec::Vec<RootCa>, path: &std::path::Path) {
561    let raw = match std::fs::read(path) {
562        Ok(d) => d,
563        Err(_) => return,
564    };
565
566    if raw.trim_ascii().starts_with(b"-----") {
567        for block in crypto_encoding::pem::decode(&raw) {
568            let Ok(block) = block else { continue };
569            let root = match RootCa::from_der(&block.contents) {
570                Ok(r) => r,
571                Err(_) => continue,
572            };
573            if roots
574                .iter()
575                .any(|r| x509::dn_equal(&r.subject_dn[..], &root.subject_dn[..]))
576            {
577                continue;
578            }
579            let _ = roots.push(root);
580        }
581    } else {
582        if let Ok(root) = RootCa::from_der(&raw) {
583            if !roots
584                .iter()
585                .any(|r| x509::dn_equal(&r.subject_dn[..], &root.subject_dn[..]))
586            {
587                roots.push(root);
588            }
589        }
590    }
591}
592
593// ── Tests ──
594
595#[cfg(all(test, feature = "crypto-default-provider"))]
596mod tests {
597    use super::*;
598    use crate::{SignatureScheme, crypto_default_provider::DefaultCryptoProvider};
599
600    fn tokio_runtime() -> tokio::runtime::Runtime {
601        tokio::runtime::Runtime::new().unwrap()
602    }
603
604    fn rpk(scheme: SignatureScheme, key: &[u8]) -> ReceivedCertificate<'_> {
605        ReceivedCertificate::RawPublicKey {
606            public_key: key,
607            scheme,
608        }
609    }
610
611    #[test]
612    fn raw_key_match_accepts() {
613        let pinned = RawPublicKey::new(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04");
614        let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).with_raw_keys([pinned]);
615
616        let rt = tokio_runtime();
617        let result =
618            rt.block_on(verifier.verify_certificate(&rpk(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04"), None));
619        assert!(result.is_ok(), "matching raw key should be accepted");
620    }
621
622    #[test]
623    fn raw_key_mismatch_key_rejects() {
624        let pinned = RawPublicKey::new(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04");
625        let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).with_raw_keys([pinned]);
626
627        let rt = tokio_runtime();
628        let result =
629            rt.block_on(verifier.verify_certificate(&rpk(SignatureScheme::Ed25519, b"\xff\xff\xff\xff\xff"), None));
630        assert!(result.is_err(), "mismatched key bytes should be rejected");
631    }
632
633    #[test]
634    fn raw_key_mismatch_scheme_rejects() {
635        let pinned = RawPublicKey::new(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04");
636        let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).with_raw_keys([pinned]);
637
638        let rt = tokio_runtime();
639        let result = rt.block_on(
640            verifier.verify_certificate(&rpk(SignatureScheme::EcdsaP256Sha256, b"\x00\x01\x02\x03\x04"), None),
641        );
642        assert!(result.is_err(), "mismatched scheme should be rejected");
643    }
644
645    #[test]
646    fn raw_key_no_keys_rejects() {
647        let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider);
648
649        let rt = tokio_runtime();
650        let result =
651            rt.block_on(verifier.verify_certificate(&rpk(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04"), None));
652        assert!(result.is_err(), "no pinned keys should reject raw public key");
653    }
654
655    #[test]
656    fn no_verification_accepts_raw_key() {
657        let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).danger_with_no_verification();
658
659        let rt = tokio_runtime();
660        let result =
661            rt.block_on(verifier.verify_certificate(&rpk(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04"), None));
662        assert!(result.is_ok(), "with_no_verification should accept raw public key");
663    }
664}