Skip to main content

ntex_tls/x509/
identity.rs

1//! Server/client identity (certificate + private key) loading.
2//!
3//! PEM/DER loading accepts a leaf-first certificate list and an unencrypted PKCS#8 key.
4//! The key must match the leaf. Loading does not establish certificate trust.
5//!
6//! PKCS#12 uses native Windows import or, on Unix, the `openssl` feature, independently
7//! of the selected trust backend. Without that capability it returns [`Error::Unsupported`].
8//! System certificate stores are available only on Windows.
9//!
10//! [`IdentitySource`] is parsed from the existing string configuration values: a plain value
11//! is a PKCS#12 file path (empty password), a value starting with `cert:` selects a Windows
12//! system store, see [`IdentitySource::from_str`](std::str::FromStr). Store sources are
13//! rejected when parsed on non-Windows targets.
14//!
15//! Store selection semantics:
16//! - the existing store is opened read-only, a missing store is not created;
17//! - by thumbprint (40 hex chars, SHA-1 of DER): validity dates are ignored;
18//! - by subject: case-insensitive substring of the subject attribute values joined by `", "`
19//!   (attribute names such as `CN=` do not match), only currently valid certificates with a
20//!   private key are considered and the one that expires last wins;
21//! - no chain trust validation is performed, the private key must be accessible to the
22//!   service account.
23//!
24//! [`PrivateKey`] supports signing on every backend and fallible PKCS#8 export. On Windows,
25//! keys remain in CNG. [`Identity`] imports persist an isolated key for Schannel compatibility
26//! and delete it on final drop; abnormal exits may leave the temporary key behind.
27//! [`ClientIdentity`] imports use ephemeral keys instead.
28//!
29//! Key material is never logged, `Debug` redacts the key.
30//!
31//! # Client identity
32//!
33//! [`ClientIdentity`] is the identity presented by outbound TLS clients (rustls). Its chain is
34//! leaf first, ordered by issuer, without a self-signed root.
35//!
36//! Chain ordering checks issuer metadata and signatures, but does not check trust or dates.
37//! Rustls adapters are provided in `crate::rustls` when that feature is enabled.
38use std::{fmt, str::FromStr};
39
40use base64::Engine;
41
42use crate::x509::{BASE64, Certificate, Error, PrivateKey, crypto};
43
44#[cfg(windows)]
45pub use crate::x509::cng::CngKey;
46pub use crate::x509::key::{HashAlg, SignScheme};
47
48/// Location of a Windows system certificate store.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum StoreLocation {
51    /// `Cert:\CurrentUser`
52    CurrentUser,
53    /// `Cert:\LocalMachine`
54    LocalMachine,
55}
56
57/// Certificate selection within a Windows system certificate store.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum StoreSelector {
60    /// SHA-1 thumbprint of the DER certificate.
61    Thumbprint([u8; 20]),
62    /// Subject attribute value substring, see the module docs.
63    Subject(String),
64}
65
66/// Where an identity is loaded from.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum IdentitySource {
69    /// PKCS#12 file with an empty password.
70    Pkcs12File(String),
71    /// PEM certificate chain and unencrypted PKCS#8 private-key files.
72    PemFiles {
73        /// Leaf-first PEM certificate chain.
74        certificates: String,
75        /// PEM `PRIVATE KEY` file; loaded into a zero-on-drop buffer.
76        private_key: String,
77    },
78    /// Windows system certificate store, e.g. `cert:LocalMachine\MY\<thumbprint>`.
79    Store {
80        location: StoreLocation,
81        store: String,
82        select: StoreSelector,
83    },
84}
85
86impl FromStr for IdentitySource {
87    type Err = Error;
88
89    /// Parses a configuration value.
90    ///
91    /// - `cert:<CurrentUser|LocalMachine>\<store>\<40 hex thumbprint>`
92    /// - `cert:<CurrentUser|LocalMachine>\<store>\subject=<subject>`
93    /// - anything else is a PKCS#12 file path.
94    ///
95    /// `/` may be used instead of `\`; `cert:` and the location are case-insensitive.
96    ///
97    /// # Errors
98    /// [`Error::Unsupported`] for store sources on non-Windows targets,
99    /// [`Error::InvalidIdentity`] for malformed store sources.
100    fn from_str(s: &str) -> Result<Self, Error> {
101        let Some(rest) = s
102            .get(..5)
103            .filter(|p| p.eq_ignore_ascii_case("cert:"))
104            .map(|_| &s[5..])
105        else {
106            return Ok(IdentitySource::Pkcs12File(s.to_string()));
107        };
108        if cfg!(not(windows)) {
109            return Err(Error::Unsupported(format!(
110                "certificate store identity is Windows only: {s}"
111            )));
112        }
113        let invalid = || {
114            Error::InvalidIdentity(format!(
115                "expected cert:<Location>\\<Store>\\<thumbprint|subject=..>: {s}"
116            ))
117        };
118
119        let mut parts = rest.trim_start_matches(['\\', '/']).splitn(3, ['\\', '/']);
120        let (Some(location), Some(store), Some(select)) =
121            (parts.next(), parts.next(), parts.next())
122        else {
123            return Err(invalid());
124        };
125        let location = if location.eq_ignore_ascii_case("CurrentUser") {
126            StoreLocation::CurrentUser
127        } else if location.eq_ignore_ascii_case("LocalMachine") {
128            StoreLocation::LocalMachine
129        } else {
130            return Err(invalid());
131        };
132        if store.is_empty() {
133            return Err(invalid());
134        }
135        let select = if let Some(subject) = select.strip_prefix("subject=") {
136            if subject.is_empty() {
137                return Err(invalid());
138            }
139            StoreSelector::Subject(subject.to_string())
140        } else {
141            let mut tp = [0u8; 20];
142            hex::decode_to_slice(select, &mut tp).map_err(|_| invalid())?;
143            StoreSelector::Thumbprint(tp)
144        };
145        Ok(IdentitySource::Store {
146            location,
147            store: store.to_string(),
148            select,
149        })
150    }
151}
152
153impl IdentitySource {
154    /// Loads the identity.
155    ///
156    /// # Errors
157    /// [`Error::NotFound`] if the file cannot be read, otherwise see [`Identity::from_pkcs12`]
158    /// or [`Identity::from_pem`]. Store loading is Windows-only.
159    pub fn load(&self) -> Result<Identity, Error> {
160        match self {
161            IdentitySource::Pkcs12File(path) => {
162                let der =
163                    std::fs::read(path).map_err(|e| Error::NotFound(format!("{path}: {e}")))?;
164                Identity::from_pkcs12(&der, "")
165            }
166            IdentitySource::PemFiles {
167                certificates,
168                private_key,
169            } => {
170                let certs = std::fs::read(certificates)
171                    .map_err(|e| Error::NotFound(format!("{certificates}: {e}")))?;
172                let key = zeroize::Zeroizing::new(
173                    std::fs::read(private_key)
174                        .map_err(|e| Error::NotFound(format!("{private_key}: {e}")))?,
175                );
176                Identity::from_pem(&certs, &key)
177            }
178            IdentitySource::Store {
179                location,
180                store,
181                select,
182            } => Identity::from_store(*location, store, select),
183        }
184    }
185}
186
187/// Certificate with its private key, see the module docs.
188#[derive(Clone)]
189pub struct Identity {
190    cert: Certificate,
191    chain: Vec<Certificate>,
192    key: PrivateKey,
193    #[cfg(windows)]
194    schannel: crate::schannel::Certificate,
195}
196
197impl Identity {
198    /// Loads a leaf-first DER certificate list and an unencrypted PKCS#8 private key.
199    ///
200    /// # Errors
201    /// Rejects empty chains, invalid certificates/keys, mismatched keys and unsupported key
202    /// algorithms. On the rustls backend, install a provider or enable `rustls-ring`.
203    pub fn from_der(certificates: &[&[u8]], private_key: &[u8]) -> Result<Self, Error> {
204        let certs = certificates
205            .iter()
206            .map(|der| Certificate::from_der(ntex_bytes::Bytes::copy_from_slice(der)))
207            .collect::<Result<_, _>>()?;
208        Self::from_certificates(certs, private_key)
209    }
210
211    /// Loads PEM certificates and one unencrypted `PRIVATE KEY` (PKCS#8) block.
212    ///
213    /// The two slices may reference the same combined PEM document.
214    ///
215    /// # Errors
216    /// See [`Self::from_der`]; malformed PEM or multiple private keys are rejected.
217    pub fn from_pem(certificates: &[u8], private_key: &[u8]) -> Result<Self, Error> {
218        let key = pkcs8_pem(private_key)?;
219        Self::from_certificates(Certificate::from_pem_multi(certificates)?, &key)
220    }
221
222    fn from_certificates(mut certs: Vec<Certificate>, key: &[u8]) -> Result<Self, Error> {
223        if certs.is_empty() {
224            return Err(Error::InvalidIdentity("Certificate is missing".into()));
225        }
226        let private_key = PrivateKey::from_pkcs8(&certs[0], key)?;
227        #[cfg(not(windows))]
228        {
229            let cert = certs.remove(0);
230            Ok(Self {
231                cert,
232                chain: certs,
233                key: private_key,
234            })
235        }
236        #[cfg(windows)]
237        {
238            drop(private_key);
239            let schannel = crate::schannel::Certificate::from_pkcs8(&certs, key)
240                .map_err(|e| Error::InvalidIdentity(e.to_string()))?;
241            // The input remains the source of additional certificates, not the system stores.
242            let mut identity = Self::from_schannel(schannel)?;
243            certs.remove(0);
244            identity.chain = certs;
245            Ok(identity)
246        }
247    }
248
249    /// Loads an identity from a DER encoded PKCS#12 archive.
250    ///
251    /// # Errors
252    /// [`Error::InvalidDer`] if the input is not a PKCS#12 archive or a certificate is not valid
253    /// DER;
254    /// [`Error::InvalidIdentity`] if the archive cannot be decrypted with `password` or has no
255    /// certificate/private key (on Windows also for malformed archive contents);
256    /// [`Error::Unsupported`] outside Windows unless Unix and `openssl` are enabled.
257    pub fn from_pkcs12(der: &[u8], password: &str) -> Result<Self, Error> {
258        #[cfg(all(unix, feature = "openssl"))]
259        {
260            let invalid = |e: tls_openssl::error::ErrorStack| Error::InvalidIdentity(e.to_string());
261            let parsed = tls_openssl::pkcs12::Pkcs12::from_der(der)
262                .map_err(|e| Error::InvalidDer(e.to_string()))?
263                .parse2(password)
264                .map_err(invalid)?;
265            let cert = parsed
266                .cert
267                .ok_or_else(|| Error::InvalidIdentity("Certificate is missing".into()))?;
268            let key = parsed
269                .pkey
270                .ok_or_else(|| Error::InvalidIdentity("Private key is missing".into()))?;
271            let key = zeroize::Zeroizing::new(key.private_key_to_pkcs8().map_err(invalid)?);
272            let mut chain = vec![Certificate::from_der(cert.to_der().map_err(invalid)?)?];
273            chain.extend(
274                parsed
275                    .ca
276                    .into_iter()
277                    .flatten()
278                    .map(|c| Certificate::from_der(c.to_der().map_err(invalid)?))
279                    .collect::<Result<Vec<_>, _>>()?,
280            );
281            Self::from_certificates(chain, &key)
282        }
283        #[cfg(windows)]
284        {
285            crate::x509::cng::check_pfx(der)?;
286            let schannel = crate::schannel::Certificate::from_pkcs12(der, password)
287                .map_err(|e| Error::InvalidIdentity(e.to_string()))?;
288            Self::from_schannel(schannel)
289        }
290        #[cfg(not(any(windows, all(unix, feature = "openssl"))))]
291        {
292            let _ = (der, password);
293            Err(Error::Unsupported(
294                "PKCS#12 loading requires the openssl feature on Unix".into(),
295            ))
296        }
297    }
298
299    /// Loads an identity from a Windows system certificate store (Windows only).
300    ///
301    /// # Errors
302    /// [`Error::NotFound`] if the store or a matching certificate is not found,
303    /// [`Error::InvalidIdentity`] if it has no accessible private key,
304    /// [`Error::Unsupported`] on non-Windows platforms.
305    pub fn from_store(
306        location: StoreLocation,
307        store: &str,
308        select: &StoreSelector,
309    ) -> Result<Self, Error> {
310        #[cfg(not(windows))]
311        {
312            let _ = (location, store, select);
313            Err(Error::Unsupported(
314                "certificate stores require Windows".into(),
315            ))
316        }
317        #[cfg(windows)]
318        {
319            use crate::schannel::{CertStoreLocation, Certificate as SchannelCert};
320
321            let location = match location {
322                StoreLocation::CurrentUser => CertStoreLocation::CurrentUser,
323                StoreLocation::LocalMachine => CertStoreLocation::LocalMachine,
324            };
325            let result = match select {
326                StoreSelector::Thumbprint(tp) => SchannelCert::from_store(location, store, tp),
327                StoreSelector::Subject(subject) => {
328                    SchannelCert::from_store_by_subject(location, store, subject)
329                }
330            };
331            let schannel = result.map_err(|e| match e.kind() {
332                std::io::ErrorKind::NotFound => Error::NotFound(e.to_string()),
333                _ => Error::InvalidIdentity(e.to_string()),
334            })?;
335            Self::from_schannel(schannel)
336        }
337    }
338
339    #[cfg(windows)]
340    fn from_schannel(schannel: crate::schannel::Certificate) -> Result<Self, Error> {
341        let (mut chain, key) = crate::x509::cng::from_schannel(schannel.clone())?;
342        let cert = chain.remove(0);
343        let key = PrivateKey::from_native(cert.clone(), key);
344        Ok(Identity {
345            cert,
346            chain,
347            key,
348            schannel,
349        })
350    }
351
352    /// Leaf certificate.
353    #[must_use]
354    pub fn cert(&self) -> &Certificate {
355        &self.cert
356    }
357
358    /// Additional certificates, in input order or selected by the native chain engine.
359    ///
360    /// See [`Identity::ordered_chain`] for the chain to present.
361    #[must_use]
362    pub fn chain(&self) -> &[Certificate] {
363        &self.chain
364    }
365
366    /// Chain to present after the leaf: the archive certificates ordered from the leaf's
367    /// issuer upwards.
368    ///
369    /// The self-signed root is appended only if `include_root` is set. Issuers must match the
370    /// certificate's issuer metadata and verify its signature; unrelated certificates are
371    /// dropped with a warning and a self-signed leaf gets no chain.
372    ///
373    /// # Errors
374    /// Certificate decoding errors or [`Error::Backend`] when the selected crypto provider
375    /// is unavailable. Unsupported signatures cannot establish an issuer relationship.
376    pub fn ordered_chain(&self, include_root: bool) -> Result<Vec<Certificate>, Error> {
377        order_chain(&self.cert, self.chain.clone(), include_root)
378    }
379
380    /// Portable signing key; native keys need not be exportable.
381    #[must_use]
382    pub fn private_key(&self) -> &PrivateKey {
383        &self.key
384    }
385
386    /// PKCS#8 DER private key (non-Windows compatibility accessor). Never log it.
387    #[cfg(not(windows))]
388    #[must_use]
389    pub fn private_key_pkcs8_der(&self) -> &[u8] {
390        &self.key.der
391    }
392
393    /// Schannel identity for `crate::schannel::ServerConfig::new` (Windows only).
394    #[cfg(windows)]
395    #[must_use]
396    pub fn schannel(&self) -> &crate::schannel::Certificate {
397        &self.schannel
398    }
399}
400
401impl fmt::Debug for Identity {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        f.debug_struct("Identity")
404            .field("cert", &self.cert)
405            .field("chain", &self.chain())
406            .field("key", &"<redacted>")
407            .finish()
408    }
409}
410
411/// Outbound TLS client identity, see the module docs.
412#[derive(Clone)]
413pub struct ClientIdentity {
414    chain: Vec<Certificate>,
415    key: PrivateKey,
416}
417
418impl ClientIdentity {
419    /// Loads a leaf-first DER certificate list and an unencrypted PKCS#8 key.
420    ///
421    /// # Errors
422    /// See [`Identity::from_der`]. The resulting chain omits the self-signed root.
423    pub fn from_der(certificates: &[&[u8]], private_key: &[u8]) -> Result<Self, Error> {
424        let certs = certificates
425            .iter()
426            .map(|der| Certificate::from_der(ntex_bytes::Bytes::copy_from_slice(der)))
427            .collect::<Result<_, _>>()?;
428        Self::from_certificates(certs, private_key)
429    }
430
431    /// Loads PEM certificates and an unencrypted PKCS#8 key, without persisting native keys.
432    ///
433    /// # Errors
434    /// See [`Identity::from_pem`].
435    pub fn from_pem(certificates: &[u8], private_key: &[u8]) -> Result<Self, Error> {
436        let key = pkcs8_pem(private_key)?;
437        Self::from_certificates(Certificate::from_pem_multi(certificates)?, &key)
438    }
439
440    fn from_certificates(mut certs: Vec<Certificate>, private_key: &[u8]) -> Result<Self, Error> {
441        if certs.is_empty() {
442            return Err(Error::InvalidIdentity("Certificate is missing".into()));
443        }
444        let cert = certs.remove(0);
445        let key = PrivateKey::from_pkcs8(&cert, private_key)?;
446        let mut chain = vec![cert.clone()];
447        chain.extend(order_chain(&cert, certs, false)?);
448        Ok(Self { chain, key })
449    }
450
451    /// Loads the client identity.
452    ///
453    /// # Errors
454    /// See [`IdentitySource::load`]; on Windows also [`Error::Backend`] if the chain cannot be
455    /// built and [`Error::Unsupported`] for key algorithms other than RSA and EC P-256/384/521.
456    /// On Unix, [`Error::InvalidIdentity`] if an issuer certificate or public key cannot be decoded.
457    pub fn load(source: &IdentitySource) -> Result<Self, Error> {
458        #[cfg(not(windows))]
459        {
460            Self::from_identity(source.load()?)
461        }
462        #[cfg(windows)]
463        {
464            let (chain, key) = match source {
465                IdentitySource::Pkcs12File(path) => {
466                    let der =
467                        std::fs::read(path).map_err(|e| Error::NotFound(format!("{path}: {e}")))?;
468                    return Self::from_pkcs12(&der, "");
469                }
470                IdentitySource::PemFiles {
471                    certificates,
472                    private_key,
473                } => {
474                    let certs = std::fs::read(certificates)
475                        .map_err(|e| Error::NotFound(format!("{certificates}: {e}")))?;
476                    let key = zeroize::Zeroizing::new(
477                        std::fs::read(private_key)
478                            .map_err(|e| Error::NotFound(format!("{private_key}: {e}")))?,
479                    );
480                    return Self::from_pem(&certs, &key);
481                }
482                IdentitySource::Store {
483                    location,
484                    store,
485                    select,
486                } => crate::x509::cng::from_store(*location, store, select)?,
487            };
488            let key = PrivateKey::from_native(chain[0].clone(), key);
489            Ok(ClientIdentity { chain, key })
490        }
491    }
492
493    /// Parses a DER PKCS#12 archive; on Windows the key is imported into CNG without persisting.
494    ///
495    /// # Errors
496    /// [`Error::InvalidDer`] if the input is not a PKCS#12 archive,
497    /// [`Error::InvalidIdentity`] if the archive cannot be decrypted, has no key, or an issuer
498    /// certificate or public key cannot be decoded (Unix),
499    /// [`Error::Unsupported`] for unsupported key algorithms or unavailable PKCS#12 loading.
500    pub fn from_pkcs12(der: &[u8], password: &str) -> Result<Self, Error> {
501        #[cfg(not(windows))]
502        {
503            Identity::from_pkcs12(der, password).and_then(Self::from_identity)
504        }
505        #[cfg(windows)]
506        {
507            let (chain, key) = crate::x509::cng::from_pkcs12(der, password)?;
508            let key = PrivateKey::from_native(chain[0].clone(), key);
509            Ok(ClientIdentity { chain, key })
510        }
511    }
512
513    /// Converts an identity into a leaf-first client chain without a self-signed root.
514    ///
515    /// # Errors
516    /// See [`Identity::ordered_chain`].
517    pub fn from_identity(id: Identity) -> Result<Self, Error> {
518        let mut chain = vec![id.cert.clone()];
519        chain.extend(order_chain(&id.cert, id.chain, false)?);
520        Ok(ClientIdentity { chain, key: id.key })
521    }
522
523    /// Leaf certificate.
524    #[must_use]
525    pub fn cert(&self) -> &Certificate {
526        &self.chain[0]
527    }
528
529    /// Leaf first, then intermediates in issuer order; the self-signed root is not included.
530    #[must_use]
531    pub fn chain(&self) -> &[Certificate] {
532        &self.chain
533    }
534
535    /// Portable signing key; native keys need not be exportable.
536    #[must_use]
537    pub fn private_key(&self) -> &PrivateKey {
538        &self.key
539    }
540
541    /// PKCS#8 DER private key (non-Windows compatibility accessor). Never log it.
542    #[cfg(not(windows))]
543    #[must_use]
544    pub fn private_key_pkcs8_der(&self) -> &[u8] {
545        &self.key.der
546    }
547
548    /// CNG private key (Windows only).
549    #[cfg(windows)]
550    #[must_use]
551    pub fn key(&self) -> &CngKey {
552        &self.key.native
553    }
554}
555
556impl fmt::Debug for ClientIdentity {
557    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558        f.debug_struct("ClientIdentity")
559            .field("chain", &self.chain)
560            .field("key", &"<redacted>")
561            .finish()
562    }
563}
564
565/// Orders the archive certificates into the presented chain: intermediates from the leaf's
566/// issuer upwards, followed by the self-signed root if `include_root` is set.
567///
568/// Issuers must match the certificate's issuer metadata and verify its signature.
569/// Unrelated certificates are dropped with a warning; a self-signed leaf gets no chain.
570fn order_chain(
571    leaf: &Certificate,
572    extras: Vec<Certificate>,
573    include_root: bool,
574) -> Result<Vec<Certificate>, Error> {
575    let mut current = leaf.clone();
576    if crypto::is_self_signed(&current)? {
577        if !extras.is_empty() {
578            log::warn!("TLS certificate is self-signed, but certificate chain was provided");
579        }
580        return Ok(Vec::new());
581    }
582    let mut extras = extras;
583    let mut chain = Vec::new();
584    loop {
585        let mut issuer = None;
586        for (pos, candidate) in extras.iter().enumerate() {
587            if crypto::is_issuer(&current, candidate)? {
588                issuer = Some(pos);
589                break;
590            }
591        }
592        let Some(pos) = issuer else { break };
593        let cert = extras.swap_remove(pos);
594        if crypto::is_self_signed(&cert)? {
595            if include_root {
596                chain.push(cert);
597            }
598            break;
599        }
600        chain.push(cert.clone());
601        current = cert;
602    }
603    if !extras.is_empty() {
604        log::warn!("TLS certificate chain contains extra certificates");
605    }
606    Ok(chain)
607}
608
609/// Decodes exactly one unencrypted PKCS#8 PEM block without retaining plaintext copies.
610pub(crate) fn pkcs8_pem(pem: &[u8]) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
611    let text = std::str::from_utf8(pem).map_err(|e| Error::InvalidPem(e.to_string()))?;
612    for block in text.split("-----BEGIN ").skip(1) {
613        if let Some((label, _)) = block.split_once("-----")
614            && label.ends_with("PRIVATE KEY")
615            && label != "PRIVATE KEY"
616        {
617            return Err(Error::InvalidPem(
618                "only unencrypted PKCS#8 PRIVATE KEY blocks are supported".into(),
619            ));
620        }
621    }
622    let begin = "-----BEGIN PRIVATE KEY-----";
623    let end = "-----END PRIVATE KEY-----";
624    let mut starts = text.match_indices(begin);
625    let (start, _) = starts.next().ok_or_else(|| {
626        Error::InvalidPem("one unencrypted PKCS#8 PRIVATE KEY is required".into())
627    })?;
628    if starts.next().is_some() {
629        return Err(Error::InvalidPem("multiple PRIVATE KEY blocks".into()));
630    }
631    let body = &text[start + begin.len()..];
632    let end = body
633        .find(end)
634        .ok_or_else(|| Error::InvalidPem("missing PRIVATE KEY END".into()))?;
635    let encoded = zeroize::Zeroizing::new(
636        body[..end]
637            .bytes()
638            .filter(|b| !b.is_ascii_whitespace())
639            .collect::<Vec<_>>(),
640    );
641    BASE64
642        .decode(encoded.as_slice())
643        .map(zeroize::Zeroizing::new)
644        .map_err(|e| Error::InvalidPem(e.to_string()))
645}
646
647/// Converts a raw IEEE P1363 ECDSA signature (`r || s`, as returned by CNG) to the DER
648/// `ECDSA-Sig-Value` used by TLS.
649#[cfg(any(windows, all(test, unix, feature = "openssl")))]
650pub(crate) fn ecdsa_p1363_to_der(raw: &[u8]) -> Vec<u8> {
651    fn push_len(out: &mut Vec<u8>, len: usize) {
652        // P-521 signatures need the long form, lengths stay below 256
653        if len >= 0x80 {
654            out.push(0x81);
655        }
656        out.push(u8::try_from(len).unwrap_or(u8::MAX));
657    }
658
659    let (r, s) = raw.split_at(raw.len() / 2);
660    let mut body = Vec::with_capacity(raw.len() + 8);
661    for int in [r, s] {
662        let int = int
663            .iter()
664            .position(|b| *b != 0)
665            .map_or(&[0u8][..], |i| &int[i..]);
666        let pad = int[0] & 0x80 != 0;
667        body.push(0x02);
668        push_len(&mut body, int.len() + usize::from(pad));
669        if pad {
670            body.push(0);
671        }
672        body.extend_from_slice(int);
673    }
674    let mut out = Vec::with_capacity(body.len() + 3);
675    out.push(0x30);
676    push_len(&mut out, body.len());
677    out.extend_from_slice(&body);
678    out
679}
680
681#[cfg(test)]
682mod tests {
683    use super::*;
684    use crate::x509::testgen::{self, San};
685
686    /// STATEMENT: PEM and DER identities load matching keys and produce usable TLS adapters.
687    ///
688    /// HOW:
689    /// - Load the checked-in RSA identity through both constructors and the client facade.
690    /// - Sign a message, reject an EC scheme, and verify via the available TLS backend.
691    /// - Check exportable keys on non-Windows and non-exportable native keys on Windows.
692    #[test]
693    fn pem_der_identities() {
694        crate::x509::test_provider();
695        let pem = include_bytes!("../../examples/cert.pem");
696        let key_pem = include_bytes!("../../examples/key.pem");
697        let key_der = pkcs8_pem(key_pem).unwrap();
698        let identity = Identity::from_pem(pem, key_pem).unwrap();
699        let der_identity = Identity::from_der(&[identity.cert().der()], &key_der).unwrap();
700        assert_eq!(identity.cert(), der_identity.cert());
701        let client = ClientIdentity::from_pem(pem, key_pem).unwrap();
702        let der_client = ClientIdentity::from_der(&[identity.cert().der()], &key_der).unwrap();
703        assert_eq!(client.chain(), der_client.chain());
704        assert_eq!(client.cert(), identity.cert());
705        assert_eq!(
706            client.private_key().public_key_der(),
707            identity.cert().public_key_der()
708        );
709        assert!(format!("{:?}", client.private_key()).contains("<redacted>"));
710        let key = client.private_key().clone();
711        drop(client);
712        let message = b"portable identity signing";
713        let signature = key
714            .sign(SignScheme::RsaPss(HashAlg::Sha256), message)
715            .unwrap();
716        assert!(matches!(
717            key.sign(SignScheme::Ecdsa(HashAlg::Sha256), message),
718            Err(Error::Unsupported(_))
719        ));
720        #[cfg(not(windows))]
721        assert_eq!(key.export_pkcs8_der().unwrap(), &*key_der);
722        #[cfg(windows)]
723        assert!(matches!(key.export_pkcs8_der(), Err(Error::Unsupported(_))));
724        #[cfg(feature = "rustls")]
725        {
726            use tls_rustls::pki_types::CertificateDer;
727            let certified = crate::rustls::certified_key(&identity).unwrap();
728            certified.keys_match().unwrap();
729            let cert_der = CertificateDer::from(identity.cert().der().to_vec());
730            let cert = webpki::EndEntityCert::try_from(&cert_der).unwrap();
731            let provider = tls_rustls::crypto::ring::default_provider();
732            let algorithms = provider
733                .signature_verification_algorithms
734                .mapping
735                .iter()
736                .find(|(scheme, _)| *scheme == tls_rustls::SignatureScheme::RSA_PSS_SHA256)
737                .unwrap()
738                .1;
739            assert!(
740                algorithms
741                    .iter()
742                    .any(|alg| cert.verify_signature(*alg, message, &signature).is_ok())
743            );
744            let signer = certified
745                .key
746                .choose_scheme(&[tls_rustls::SignatureScheme::RSA_PSS_SHA256])
747                .unwrap();
748            drop(certified);
749            drop(identity);
750            let signature = signer.sign(message).unwrap();
751            assert!(
752                algorithms
753                    .iter()
754                    .any(|alg| cert.verify_signature(*alg, message, &signature).is_ok())
755            );
756        }
757        #[cfg(all(not(windows), not(feature = "rustls"), feature = "openssl"))]
758        {
759            use tls_openssl::{
760                hash::MessageDigest,
761                rsa::Padding,
762                sign::{RsaPssSaltlen, Verifier},
763                ssl,
764            };
765            let mut context = ssl::SslContextBuilder::new(ssl::SslMethod::tls()).unwrap();
766            crate::openssl::set_identity(&mut context, &identity).unwrap();
767            let public =
768                tls_openssl::pkey::PKey::public_key_from_der(key.public_key_der()).unwrap();
769            let mut verifier = Verifier::new(MessageDigest::sha256(), &public).unwrap();
770            verifier.set_rsa_padding(Padding::PKCS1_PSS).unwrap();
771            verifier.set_rsa_mgf1_md(MessageDigest::sha256()).unwrap();
772            verifier
773                .set_rsa_pss_saltlen(RsaPssSaltlen::DIGEST_LENGTH)
774                .unwrap();
775            assert!(verifier.verify_oneshot(&signature, message).unwrap());
776        }
777        #[cfg(all(windows, not(feature = "rustls")))]
778        assert!(!signature.is_empty());
779    }
780
781    /// STATEMENT: Identity loading rejects mismatched, absent and malformed key material.
782    ///
783    /// HOW:
784    /// - Pair an EC certificate with the RSA fixture key and reject both facades.
785    /// - Reject empty certificate lists, corrupt DER, missing keys and multiple PEM keys.
786    /// - Reject invalid UTF-8/base64, missing end markers and unsupported key labels.
787    #[test]
788    fn invalid_pem_der_identities() {
789        crate::x509::test_provider();
790        let pem = include_bytes!("../../examples/cert.pem");
791        let key = include_bytes!("../../examples/key.pem");
792        let wrong = include_bytes!("../../tests/testdata/leaf.pem");
793        assert!(matches!(
794            Identity::from_pem(wrong, key),
795            Err(Error::InvalidIdentity(_))
796        ));
797        assert!(matches!(
798            ClientIdentity::from_pem(wrong, key),
799            Err(Error::InvalidIdentity(_))
800        ));
801        assert!(Identity::from_der(&[], &pkcs8_pem(key).unwrap()).is_err());
802        assert!(ClientIdentity::from_der(&[], &pkcs8_pem(key).unwrap()).is_err());
803        let cert = Certificate::from_pem(pem).unwrap();
804        assert!(Identity::from_der(&[cert.der()], b"invalid").is_err());
805        assert!(ClientIdentity::from_der(&[b"invalid"], &pkcs8_pem(key).unwrap()).is_err());
806        assert!(Identity::from_pem(pem, b"").is_err());
807        let repeated = [key.as_slice(), key.as_slice()].concat();
808        assert!(matches!(
809            Identity::from_pem(pem, &repeated),
810            Err(Error::InvalidPem(_))
811        ));
812        let mixed = [key.as_slice(), b"-----BEGIN RSA PRIVATE KEY-----"].concat();
813        assert!(matches!(
814            Identity::from_pem(pem, &mixed),
815            Err(Error::InvalidPem(_))
816        ));
817        for invalid in [
818            b"\xff".as_slice(),
819            b"-----BEGIN PRIVATE KEY-----\nAAAA",
820            b"-----BEGIN PRIVATE KEY-----\n!\n-----END PRIVATE KEY-----",
821            b"-----BEGIN ENCRYPTED PRIVATE KEY-----\nAAAA\n-----END ENCRYPTED PRIVATE KEY-----",
822            b"-----BEGIN EC PRIVATE KEY-----\nAAAA\n-----END EC PRIVATE KEY-----",
823        ] {
824            assert!(matches!(
825                Identity::from_pem(pem, invalid),
826                Err(Error::InvalidPem(_))
827            ));
828            assert!(matches!(
829                ClientIdentity::from_pem(pem, invalid),
830                Err(Error::InvalidPem(_))
831            ));
832        }
833    }
834
835    /// STATEMENT: PEM file-loading errors identify the missing input for both identity facades.
836    ///
837    /// HOW:
838    /// - Use an existing certificate file and a nonexistent child path as each input in turn.
839    /// - Check both loaders return `NotFound` with the failing path, without creating files.
840    #[test]
841    fn pem_source_missing_files() {
842        let certificate = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/cert.pem");
843        let missing = format!("{certificate}/missing");
844        for (certificates, private_key) in [
845            (missing.as_str(), certificate),
846            (certificate, missing.as_str()),
847        ] {
848            let source = IdentitySource::PemFiles {
849                certificates: certificates.into(),
850                private_key: private_key.into(),
851            };
852            for result in [
853                source.load().map(|_| ()),
854                ClientIdentity::load(&source).map(|_| ()),
855            ] {
856                assert!(
857                    matches!(result, Err(Error::NotFound(message)) if message.contains(&missing))
858                );
859            }
860        }
861    }
862
863    /// STATEMENT: Combined PEM documents and file sources load the same portable identity.
864    ///
865    /// HOW:
866    /// - Load both facades from checked-in PEM files and a combined in-memory document.
867    /// - Compare their certificates and check the client adapter retains the ordered chain.
868    #[test]
869    fn pem_sources() {
870        crate::x509::test_provider();
871        let source = IdentitySource::PemFiles {
872            certificates: concat!(env!("CARGO_MANIFEST_DIR"), "/examples/cert.pem").into(),
873            private_key: concat!(env!("CARGO_MANIFEST_DIR"), "/examples/key.pem").into(),
874        };
875        let identity = source.load().unwrap();
876        let client = ClientIdentity::load(&source).unwrap();
877        let combined = zeroize::Zeroizing::new(
878            [
879                include_bytes!("../../examples/cert.pem").as_slice(),
880                include_bytes!("../../examples/key.pem").as_slice(),
881            ]
882            .concat(),
883        );
884        assert_eq!(
885            Identity::from_pem(&combined, &combined).unwrap().cert(),
886            identity.cert()
887        );
888        assert_eq!(
889            ClientIdentity::from_pem(&combined, &combined)
890                .unwrap()
891                .cert(),
892            client.cert()
893        );
894        #[cfg(feature = "rustls")]
895        {
896            let certified = crate::rustls::client_certified_key(&client);
897            certified.keys_match().unwrap();
898            assert_eq!(certified.cert[0].as_ref(), identity.cert().der().as_ref());
899        }
900    }
901
902    /// STATEMENT: Shared chain ordering follows signatures and optionally includes the root.
903    ///
904    /// HOW:
905    /// - Order fixed root/intermediate/leaf fixtures with an unrelated certificate first.
906    /// - Check the root flag, a missing issuer and a corrupted intermediate signature.
907    #[test]
908    fn portable_chain_ordering() {
909        crate::x509::test_provider();
910        let leaf = Certificate::from_pem(include_bytes!("../../tests/testdata/leaf.pem")).unwrap();
911        let inter =
912            Certificate::from_pem(include_bytes!("../../tests/testdata/inter.pem")).unwrap();
913        let root = Certificate::from_pem(include_bytes!("../../tests/testdata/root.pem")).unwrap();
914        let other =
915            Certificate::from_pem(include_bytes!("../../tests/testdata/other.pem")).unwrap();
916        let extras = vec![other, root.clone(), inter.clone()];
917        assert_eq!(
918            order_chain(&leaf, extras.clone(), false).unwrap(),
919            vec![inter.clone()]
920        );
921        assert_eq!(
922            order_chain(&leaf, extras, true).unwrap(),
923            vec![inter.clone(), root.clone()]
924        );
925        assert_eq!(
926            order_chain(&leaf, vec![root.clone()], true).unwrap(),
927            Vec::<Certificate>::new()
928        );
929        let mut der = inter.der().to_vec();
930        *der.last_mut().unwrap() ^= 1;
931        let corrupt = Certificate::from_der(der).unwrap();
932        assert_eq!(
933            order_chain(&corrupt, vec![root], true).unwrap(),
934            Vec::<Certificate>::new()
935        );
936    }
937
938    /// STATEMENT: PKCS#12 has no implicit OpenSSL dependency in rustls-only builds.
939    ///
940    /// HOW:
941    /// - Call both PKCS#12 constructors without the OpenSSL feature.
942    /// - Check capability errors are returned before attempting archive decoding.
943    #[cfg(all(not(windows), not(feature = "openssl")))]
944    #[test]
945    fn pkcs12_requires_openssl() {
946        assert!(matches!(
947            Identity::from_pkcs12(b"archive", ""),
948            Err(Error::Unsupported(_))
949        ));
950        assert!(matches!(
951            ClientIdentity::from_pkcs12(b"archive", ""),
952            Err(Error::Unsupported(_))
953        ));
954    }
955
956    /// STATEMENT: Identity-source parsing preserves paths and rejects unavailable store sources.
957    ///
958    /// HOW:
959    /// - Parse paths, thumbprint and subject selectors, and malformed store strings.
960    /// - Check native selectors on Windows and unsupported errors elsewhere.
961    #[test]
962    fn source_from_str() {
963        assert_eq!(
964            "/etc/cert.pfx".parse::<IdentitySource>().unwrap(),
965            IdentitySource::Pkcs12File("/etc/cert.pfx".into())
966        );
967        let store = "CERT:LocalMachine\\MY\\0102030405060708090a0b0c0d0e0f1011121314"
968            .parse::<IdentitySource>();
969        let subject = "cert:/CurrentUser/My/subject=gw.example.com".parse::<IdentitySource>();
970        if cfg!(windows) {
971            assert_eq!(
972                store.unwrap(),
973                IdentitySource::Store {
974                    location: StoreLocation::LocalMachine,
975                    store: "MY".into(),
976                    select: StoreSelector::Thumbprint([
977                        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
978                    ]),
979                }
980            );
981            assert_eq!(
982                subject.unwrap(),
983                IdentitySource::Store {
984                    location: StoreLocation::CurrentUser,
985                    store: "My".into(),
986                    select: StoreSelector::Subject("gw.example.com".into()),
987                }
988            );
989            for bad in [
990                "cert:Other\\MY\\00",
991                "cert:LocalMachine\\MY",
992                "cert:LocalMachine\\MY\\abc",
993                "cert:LocalMachine\\MY\\subject=",
994            ] {
995                assert!(
996                    matches!(
997                        bad.parse::<IdentitySource>(),
998                        Err(Error::InvalidIdentity(_))
999                    ),
1000                    "{bad}"
1001                );
1002            }
1003        } else {
1004            assert!(matches!(store, Err(Error::Unsupported(_))));
1005            assert!(matches!(subject, Err(Error::Unsupported(_))));
1006        }
1007    }
1008
1009    /// STATEMENT: PKCS#12 imports preserve the matching key and extra certificates.
1010    ///
1011    /// HOW:
1012    /// - Generate and load a password-protected archive, comparing certificates and keys.
1013    /// - Reject a wrong password, malformed archive, and nonexistent source file.
1014    /// - On Windows the chain comes from the platform chain engine, which may not find the
1015    ///   untrusted root, so only its contents are checked.
1016    #[cfg(any(windows, all(unix, feature = "openssl")))]
1017    #[test]
1018    fn pkcs12_roundtrip() {
1019        let san = San::default();
1020        let rk = testgen::key();
1021        let root = testgen::cert("root", true, None, &rk, &san, (-1, 10));
1022        let lk = testgen::key();
1023        let leaf = testgen::cert("leaf", false, Some((&root, &rk)), &lk, &san, (-1, 10));
1024        let pfx = testgen::pkcs12(&leaf, &lk, &[&root], "pw");
1025
1026        let id = Identity::from_pkcs12(&pfx, "pw").unwrap();
1027        assert_eq!(id.cert(), &leaf);
1028        if cfg!(windows) {
1029            assert!(id.chain().len() <= 1);
1030            assert!(id.chain().iter().all(|c| c == &root));
1031        } else {
1032            assert_eq!(id.chain(), [root]);
1033        }
1034        assert_eq!(id.private_key().public_key_der(), leaf.public_key_der());
1035        #[cfg(not(windows))]
1036        assert!(!format!("{id:?}").contains(&hex::encode(id.private_key_pkcs8_der())));
1037        assert!(format!("{id:?}").contains("<redacted>"));
1038
1039        assert!(matches!(
1040            Identity::from_pkcs12(&pfx, "wrong"),
1041            Err(Error::InvalidIdentity(_))
1042        ));
1043        assert!(matches!(
1044            Identity::from_pkcs12(b"garbage", ""),
1045            Err(Error::InvalidDer(_))
1046        ));
1047        assert!(matches!(
1048            ClientIdentity::from_pkcs12(b"garbage", ""),
1049            Err(Error::InvalidDer(_))
1050        ));
1051        assert!(matches!(
1052            IdentitySource::Pkcs12File("/nonexistent/x.pfx".into()).load(),
1053            Err(Error::NotFound(_))
1054        ));
1055    }
1056
1057    /// STATEMENT: Client identities present ordered intermediates without roots or unrelated certs;
1058    /// `Identity::ordered_chain` appends the root only on request.
1059    ///
1060    /// HOW:
1061    /// - Load unordered certificates and check the presented chain and private key.
1062    /// - Check `Identity::ordered_chain` with and without the root.
1063    /// - With OpenSSL, load the same chain from a PKCS#12 file and install it into a context.
1064    /// - Check self-signed leaves and missing intermediates yield no additional certificates.
1065    #[test]
1066    fn client_identity_chain() {
1067        let san = San::default();
1068        let rk = testgen::key();
1069        let root = testgen::cert("root", true, None, &rk, &san, (-1, 10));
1070        let ik = testgen::key();
1071        let inter = testgen::cert("inter", true, Some((&root, &rk)), &ik, &san, (-1, 10));
1072        let lk = testgen::key();
1073        let leaf = testgen::cert("leaf", false, Some((&inter, &ik)), &lk, &san, (-1, 10));
1074        let other = testgen::cert("other", true, None, &testgen::key(), &san, (-1, 10));
1075
1076        // input order is unrelated to chain order, unrelated certificates are dropped
1077        let id = testgen::client(&leaf, &lk, &[&root, &other, &inter]);
1078        assert_eq!(id.chain(), [leaf.clone(), inter.clone()]);
1079        assert_eq!(id.cert(), &leaf);
1080        assert_eq!(id.cert().key_type(), Some(crate::x509::KeyType::EcP256));
1081        assert!(format!("{id:?}").contains("<redacted>"));
1082
1083        let certs = [&leaf, &root, &other, &inter].map(|c| c.der().as_ref());
1084        let id = Identity::from_der(&certs, &lk.pkcs8_der()).unwrap();
1085        assert_eq!(
1086            id.ordered_chain(false).unwrap(),
1087            std::slice::from_ref(&inter)
1088        );
1089        assert_eq!(
1090            id.ordered_chain(true).unwrap(),
1091            [inter.clone(), root.clone()]
1092        );
1093
1094        #[cfg(all(unix, feature = "openssl"))]
1095        {
1096            let pfx = testgen::pkcs12(&leaf, &lk, &[&root, &other, &inter], "");
1097            let path = std::env::temp_dir()
1098                .join(format!("ntex-tls-x509-client-{}.pfx", std::process::id()));
1099            std::fs::write(&path, &pfx).unwrap();
1100            let src = IdentitySource::Pkcs12File(path.to_string_lossy().into());
1101            let id = ClientIdentity::load(&src).unwrap();
1102            std::fs::remove_file(&path).unwrap();
1103            assert_eq!(id.chain(), [leaf.clone(), inter.clone()]);
1104            assert_eq!(id.private_key_pkcs8_der(), lk.pkcs8_der());
1105
1106            let id = Identity::from_pkcs12(&pfx, "").unwrap();
1107            assert_eq!(
1108                id.ordered_chain(true).unwrap(),
1109                [inter.clone(), root.clone()]
1110            );
1111            let mut context =
1112                tls_openssl::ssl::SslContextBuilder::new(tls_openssl::ssl::SslMethod::tls())
1113                    .unwrap();
1114            crate::openssl::set_identity(&mut context, &id).unwrap();
1115            let context = context.build();
1116            let der = |c: &tls_openssl::x509::X509Ref| c.to_der().unwrap();
1117            assert_eq!(der(context.certificate().unwrap()), leaf.der().as_ref());
1118            assert_eq!(context.extra_chain_certs().len(), 1);
1119            assert_eq!(der(&context.extra_chain_certs()[0]), inter.der().as_ref());
1120        }
1121
1122        // self-signed leaf: no chain
1123        assert_eq!(
1124            order_chain(&root, vec![inter.clone()], true).unwrap(),
1125            Vec::<Certificate>::new()
1126        );
1127        // missing intermediate: nothing to present
1128        let leaf2 = testgen::cert(
1129            "leaf2",
1130            false,
1131            Some((&inter, &ik)),
1132            &testgen::key(),
1133            &san,
1134            (-1, 10),
1135        );
1136        assert_eq!(
1137            order_chain(&leaf2, vec![root.clone()], true).unwrap(),
1138            Vec::<Certificate>::new()
1139        );
1140    }
1141
1142    /// STATEMENT: OpenSSL issuer matching retains canonical distinguished-name comparison.
1143    ///
1144    /// HOW:
1145    /// - Create same-key roots whose names differ only by case and whitespace.
1146    /// - Order a leaf signed with one root using the other as its issuer.
1147    #[cfg(all(unix, feature = "openssl", not(feature = "rustls")))]
1148    #[test]
1149    fn openssl_canonical_issuer() {
1150        let san = San::default();
1151        let key = testgen::key();
1152        let root = testgen::cert("Root CA", true, None, &key, &san, (-1, 10));
1153        let alias = testgen::cert(" root  ca ", true, None, &key, &san, (-1, 10));
1154        let leaf = testgen::cert(
1155            "leaf",
1156            false,
1157            Some((&root, &key)),
1158            &testgen::key(),
1159            &san,
1160            (-1, 10),
1161        );
1162        assert_eq!(
1163            order_chain(&leaf, vec![alias.clone()], true).unwrap(),
1164            vec![alias]
1165        );
1166    }
1167
1168    /// STATEMENT: Self-issued rollover certificates remain in the presented chain.
1169    ///
1170    /// HOW:
1171    /// - Generate a CA with its parent's subject but a new EC key, using EC and RSA parent keys.
1172    /// - Generate a leaf signed by the rollover CA.
1173    /// - Load identities with and without the old root and check the rollover CA is retained.
1174    /// - Load the rollover CA as the identity leaf and check its issuer is still followed.
1175    #[test]
1176    fn client_identity_rollover() {
1177        let san = San::default();
1178        for rk in [testgen::key(), testgen::rsa_key()] {
1179            let root = testgen::cert("root", true, None, &rk, &san, (-1, 10));
1180            let ik = testgen::key();
1181            let inter = testgen::cert("root", true, Some((&root, &rk)), &ik, &san, (-1, 10));
1182            let lk = testgen::key();
1183            let leaf = testgen::cert("leaf", false, Some((&inter, &ik)), &lk, &san, (-1, 10));
1184            for extras in [&[&root, &inter][..], &[&inter][..]] {
1185                let id = testgen::client(&leaf, &lk, extras);
1186                assert_eq!(id.chain(), [leaf.clone(), inter.clone()]);
1187            }
1188
1189            // A self-issued leaf can also have a non-root issuer with the same subject.
1190            let next_key = testgen::key();
1191            let next = testgen::cert("root", true, Some((&inter, &ik)), &next_key, &san, (-1, 10));
1192            let id = testgen::client(&next, &next_key, &[&root, &inter]);
1193            assert_eq!(id.chain(), [next, inter]);
1194        }
1195    }
1196
1197    /// STATEMENT: Same-subject issuer candidates are selected by key, not archive order.
1198    ///
1199    /// HOW:
1200    /// - Generate same-subject intermediates with different EC/RSA keys and no AKI extension.
1201    /// - Sign a leaf with one key and load both input orders.
1202    /// - Check only the intermediate whose key verifies the leaf is presented.
1203    #[test]
1204    fn client_identity_same_subject_issuers() {
1205        let san = San::default();
1206        let rk = testgen::key();
1207        let root = testgen::cert("root", true, None, &rk, &san, (-1, 10));
1208        let ik = testgen::key();
1209        let inter = testgen::cert("inter", true, Some((&root, &rk)), &ik, &san, (-1, 10));
1210        let lk = testgen::key();
1211        let leaf = testgen::cert("leaf", false, Some((&inter, &ik)), &lk, &san, (-1, 10));
1212        for key in [testgen::key(), testgen::rsa_key()] {
1213            let other = testgen::cert("inter", true, Some((&root, &rk)), &key, &san, (-1, 10));
1214            for extras in [[&other, &inter, &root], [&inter, &other, &root]] {
1215                let id = testgen::client(&leaf, &lk, &extras);
1216                assert_eq!(id.chain(), [leaf.clone(), inter.clone()]);
1217            }
1218        }
1219    }
1220
1221    /// STATEMENT: CNG raw ECDSA signatures are encoded as canonical DER integers.
1222    ///
1223    /// HOW:
1224    /// - Compare 256/384/521-bit raw signatures with OpenSSL's DER encoder.
1225    /// - Include leading zeros and high-bit padding in both components.
1226    #[cfg(all(unix, feature = "openssl"))]
1227    #[test]
1228    fn ecdsa_der() {
1229        use tls_openssl::bn::BigNum;
1230        use tls_openssl::ecdsa::EcdsaSig;
1231
1232        let check = |raw: Vec<u8>| {
1233            let (r, s) = raw.split_at(raw.len() / 2);
1234            let expected = EcdsaSig::from_private_components(
1235                BigNum::from_slice(r).unwrap(),
1236                BigNum::from_slice(s).unwrap(),
1237            )
1238            .unwrap()
1239            .to_der()
1240            .unwrap();
1241            assert_eq!(ecdsa_p1363_to_der(&raw), expected, "{}", hex::encode(&raw));
1242        };
1243        for len in [32, 48, 66] {
1244            check(vec![0x11; len * 2]);
1245            check(vec![0xff; len * 2]);
1246            let mut raw = vec![0u8; len * 2];
1247            raw[len - 1] = 1;
1248            raw[len] = 0x80;
1249            raw[2 * len - 1] = 7;
1250            check(raw);
1251        }
1252    }
1253}