wtransport/
tls.rs

1use error::InvalidCertificate;
2use error::InvalidDigest;
3use error::PemLoadError;
4use pem::encode as pem_encode;
5use pem::Pem;
6use rustls::RootCertStore;
7use rustls_pki_types::pem::PemObject;
8use rustls_pki_types::CertificateDer;
9use rustls_pki_types::PrivateKeyDer;
10use rustls_pki_types::PrivatePkcs8KeyDer;
11use sha2::Digest;
12use sha2::Sha256;
13use std::fmt::Debug;
14use std::fmt::Display;
15use std::path::Path;
16use std::str::FromStr;
17use std::sync::Arc;
18use x509_parser::certificate::X509Certificate;
19use x509_parser::prelude::FromDer;
20
21pub use wtransport_proto::WEBTRANSPORT_ALPN;
22
23/// Represents an X.509 certificate.
24#[derive(Clone)]
25pub struct Certificate(CertificateDer<'static>);
26
27impl Certificate {
28    pub(crate) fn from_rustls_pki(cert: CertificateDer<'static>) -> Self {
29        Self(cert)
30    }
31
32    /// Constructs a new `Certificate` from DER-encoded binary data.
33    pub fn from_der(der: Vec<u8>) -> Result<Self, InvalidCertificate> {
34        X509Certificate::from_der(&der).map_err(|error| InvalidCertificate(error.to_string()))?;
35        Ok(Self(CertificateDer::from(der)))
36    }
37
38    /// Loads the *first* certificate found in a PEM-encoded file.
39    ///
40    /// Filters out any PEM sections that are not certificate.
41    ///
42    /// Returns a [`PemLoadError::NoCertificateSection`] if no certificate is found in the file.
43    pub async fn load_pemfile(filepath: impl AsRef<Path>) -> Result<Self, PemLoadError> {
44        let file_data = tokio::fs::read(filepath.as_ref())
45            .await
46            .map_err(|io_error| PemLoadError::FileError {
47                file: filepath.as_ref().to_path_buf(),
48                error: io_error,
49            })?;
50
51        let der = CertificateDer::from_pem_slice(&file_data).map_err(|error| {
52            if let rustls_pki_types::pem::Error::NoItemsFound = error {
53                PemLoadError::NoCertificateSection
54            } else {
55                PemLoadError::FileError {
56                    file: filepath.as_ref().to_path_buf(),
57                    error: std::io::Error::other(error),
58                }
59            }
60        })?;
61
62        Self::from_der(der.to_vec()).map_err(|invalid_certificate| {
63            PemLoadError::InvalidCertificateChain {
64                index: 0,
65                error: invalid_certificate,
66            }
67        })
68    }
69
70    /// Stores the certificate in PEM format into a file asynchronously.
71    ///
72    /// If the file does not exist, it will be created. If the file exists, its contents
73    /// will be truncated before writing.
74    pub async fn store_pemfile(&self, filepath: impl AsRef<Path>) -> std::io::Result<()> {
75        use tokio::io::AsyncWriteExt;
76
77        let mut file = tokio::fs::File::create(filepath).await?;
78        file.write_all(self.to_pem().as_bytes()).await?;
79
80        Ok(())
81    }
82
83    /// Returns a reference to the DER-encoded binary data of the certificate.
84    pub fn der(&self) -> &[u8] {
85        &self.0
86    }
87
88    /// Retrieves the serial number of the certificate as a string.
89    pub fn serial(&self) -> String {
90        X509Certificate::from_der(&self.0)
91            .expect("valid der")
92            .1
93            .raw_serial_as_string()
94    }
95
96    /// Converts the X.509 certificate to the PEM (Privacy-Enhanced Mail) format.
97    ///
98    /// # Returns
99    /// A `String` containing the PEM-encoded representation of the certificate.
100    pub fn to_pem(&self) -> String {
101        pem_encode(&Pem::new("CERTIFICATE", self.der()))
102    }
103
104    /// Computes certificate's *hash*.
105    ///
106    /// The hash is the *SHA-256* of the DER encoding of the certificate.
107    ///
108    /// This function can be used to make a *web* client accept a self-signed
109    /// certificate by using the [`WebTransportOptions.serverCertificateHashes`] W3C API.
110    ///
111    /// [`WebTransportOptions.serverCertificateHashes`]: https://www.w3.org/TR/webtransport/#dom-webtransportoptions-servercertificatehashes
112    pub fn hash(&self) -> Sha256Digest {
113        // TODO(biagio): you might consider use crypto provider from new rustls version
114        Sha256Digest(Sha256::digest(self.der()).into())
115    }
116}
117
118impl Debug for Certificate {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        f.debug_struct("Certificate")
121            .field("serial", &self.serial())
122            .finish()
123    }
124}
125
126/// Represents a private key.
127#[derive(Debug)]
128pub struct PrivateKey(PrivateKeyDer<'static>);
129
130impl PrivateKey {
131    /// Constructs a new `PrivateKey` from DER-encoded PKCS#8 binary data.
132    pub fn from_der_pkcs8(der: Vec<u8>) -> Self {
133        PrivateKey(PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(der)))
134    }
135
136    /// Loads the *first* private key found in a PEM-encoded file.
137    ///
138    /// Filters out any PEM sections that are not private key.
139    ///
140    /// Returns a [`PemLoadError::NoPrivateKeySection`] if no private key is found in the file.
141    pub async fn load_pemfile(filepath: impl AsRef<Path>) -> Result<Self, PemLoadError> {
142        let file_data = tokio::fs::read(filepath.as_ref())
143            .await
144            .map_err(|io_error| PemLoadError::FileError {
145                file: filepath.as_ref().to_path_buf(),
146                error: io_error,
147            })?;
148
149        let private_key = PrivateKeyDer::from_pem_slice(&file_data).map_err(|error| {
150            if let rustls_pki_types::pem::Error::NoItemsFound = error {
151                PemLoadError::NoPrivateKeySection
152            } else {
153                PemLoadError::FileError {
154                    file: filepath.as_ref().to_path_buf(),
155                    error: std::io::Error::other(error),
156                }
157            }
158        })?;
159
160        Ok(Self(private_key))
161    }
162
163    /// Stores the private key in PEM format into a file asynchronously.
164    ///
165    /// If the file does not exist, it will be created. If the file exists,
166    /// its contents will be truncated before writing.
167    pub async fn store_secret_pemfile(&self, filepath: impl AsRef<Path>) -> std::io::Result<()> {
168        tokio::fs::write(filepath, self.to_secret_pem()).await
169    }
170
171    /// Returns a reference to the DER-encoded binary data of the private key.
172    pub fn secret_der(&self) -> &[u8] {
173        self.0.secret_der()
174    }
175
176    /// Converts the private key to PEM format.
177    pub fn to_secret_pem(&self) -> String {
178        pem_encode(&Pem::new("PRIVATE KEY", self.secret_der()))
179    }
180
181    /// Clones this private key.
182    ///
183    /// # Note
184    /// `PrivateKey` does not implement `Clone` directly to ensure that sensitive information
185    /// is not cloned inadvertently.
186    /// Implementing `Clone` directly could potentially lead to unintended cloning of sensitive data.
187    pub fn clone_key(&self) -> Self {
188        Self(self.0.clone_key())
189    }
190}
191
192/// A collection of [`Certificate`].
193#[derive(Clone, Debug)]
194pub struct CertificateChain(Vec<Certificate>);
195
196impl CertificateChain {
197    /// Constructs a new `CertificateChain` from a vector of certificates.
198    pub fn new(certificates: Vec<Certificate>) -> Self {
199        Self(certificates)
200    }
201
202    /// Constructs a new `CertificateChain` with a single certificate.
203    pub fn single(certificate: Certificate) -> Self {
204        Self::new(vec![certificate])
205    }
206
207    /// Loads a certificate chain from a PEM-encoded file.
208    ///
209    /// Filters out any PEM sections that are not certificates and yields error
210    /// if a problem occurs while trying to parse any certificate.
211    ///
212    /// *Note*: if the PEM file does not contain any certificate section this
213    /// will return an empty chain.
214    pub async fn load_pemfile(filepath: impl AsRef<Path>) -> Result<Self, PemLoadError> {
215        let file_data = tokio::fs::read(filepath.as_ref())
216            .await
217            .map_err(|io_error| PemLoadError::FileError {
218                file: filepath.as_ref().to_path_buf(),
219                error: io_error,
220            })?;
221
222        let certificates = CertificateDer::pem_slice_iter(&file_data)
223            .enumerate()
224            .map(|(index, maybe_der)| match maybe_der {
225                Ok(der) => Certificate::from_der(der.to_vec()).map_err(|invalid_certificate| {
226                    PemLoadError::InvalidCertificateChain {
227                        index,
228                        error: invalid_certificate,
229                    }
230                }),
231                Err(error) => Err(PemLoadError::FileError {
232                    file: filepath.as_ref().to_path_buf(),
233                    error: std::io::Error::other(error),
234                }),
235            })
236            .collect::<Result<Vec<_>, _>>()?;
237
238        Ok(Self(certificates))
239    }
240
241    /// Stores the certificate chain in PEM format into a file asynchronously.
242    ///
243    /// If the file does not exist, it will be created. If the file exists, its contents
244    /// will be truncated before writing.
245    pub async fn store_pemfile(&self, filepath: impl AsRef<Path>) -> std::io::Result<()> {
246        use tokio::io::AsyncWriteExt;
247
248        let mut file = tokio::fs::File::create(filepath).await?;
249
250        for cert in self.0.iter() {
251            file.write_all(cert.to_pem().as_bytes()).await?;
252        }
253
254        Ok(())
255    }
256
257    /// Returns a slice containing references to the certificates in the chain.
258    pub fn as_slice(&self) -> &[Certificate] {
259        &self.0
260    }
261}
262
263impl AsRef<[Certificate]> for CertificateChain {
264    fn as_ref(&self) -> &[Certificate] {
265        self.as_slice()
266    }
267}
268
269impl FromIterator<Certificate> for CertificateChain {
270    fn from_iter<T: IntoIterator<Item = Certificate>>(iter: T) -> Self {
271        Self(iter.into_iter().collect())
272    }
273}
274
275/// Represents an TLS identity consisting of a certificate chain and a private key.
276#[derive(Debug)]
277pub struct Identity {
278    certificate_chain: CertificateChain,
279    private_key: PrivateKey,
280}
281
282impl Identity {
283    /// Constructs a new `Identity` with the given certificate chain and private key.
284    pub fn new(certificate_chain: CertificateChain, private_key: PrivateKey) -> Self {
285        Self {
286            certificate_chain,
287            private_key,
288        }
289    }
290
291    /// Loads an identity from PEM-encoded certificate and private key files.
292    pub async fn load_pemfiles(
293        cert_pemfile: impl AsRef<Path>,
294        private_key_pemfile: impl AsRef<Path>,
295    ) -> Result<Self, PemLoadError> {
296        let certificate_chain = CertificateChain::load_pemfile(cert_pemfile).await?;
297        let private_key = PrivateKey::load_pemfile(private_key_pemfile).await?;
298
299        Ok(Self::new(certificate_chain, private_key))
300    }
301
302    /// Generates a self-signed certificate and private key for new identity.
303    ///
304    /// The certificate conforms to the W3C WebTransport specifications as follows:
305    ///
306    /// - The certificate MUST be an *X.509v3* certificate as defined in *RFC5280*.
307    /// - The key used in the Subject Public Key field MUST be one of the allowed public key algorithms.
308    ///   This function uses the `ECDSA P-256` algorithm.
309    /// - The current time MUST be within the validity period of the certificate as defined
310    ///   in Section 4.1.2.5 of *RFC5280*.
311    /// - The total length of the validity period MUST NOT exceed two weeks.
312    ///
313    /// # Arguments
314    ///
315    /// * `subject_alt_names` - An iterator of strings representing subject alternative names (SANs).
316    ///   They can be both hostnames or IP addresses. An error is returned
317    ///   if DNS are not valid ASN.1 strings.
318    ///
319    /// # Examples
320    ///
321    /// ```
322    /// use wtransport::Identity;
323    ///
324    /// let identity = Identity::self_signed(&["localhost", "127.0.0.1", "::1"]).unwrap();
325    /// ```
326    ///
327    /// The following example will return an error as *subject alternative name* is an invalid
328    /// string.
329    /// ```should_panic
330    /// use wtransport::Identity;
331    ///
332    /// Identity::self_signed(&["❤️"]).unwrap();
333    /// ```
334    ///
335    /// # Features
336    ///
337    /// This function is only available when the `self-signed` feature is enabled.
338    #[cfg(feature = "self-signed")]
339    #[cfg_attr(docsrs, doc(cfg(feature = "self-signed")))]
340    pub fn self_signed<I, S>(subject_alt_names: I) -> Result<Self, error::InvalidSan>
341    where
342        I: IntoIterator<Item = S>,
343        S: AsRef<str>,
344    {
345        self_signed::SelfSignedIdentityBuilder::new()
346            .subject_alt_names(subject_alt_names)
347            .from_now_utc()
348            .validity_days(14)
349            .build()
350    }
351
352    /// Creates a new [`SelfSignedIdentityBuilder`][1] instance.
353    ///
354    /// This function provides a convenient way to create a self-signed identity
355    /// builder, and thus generate a custom self-signed identity.
356    ///
357    /// # Example
358    ///
359    /// ```
360    /// use wtransport::Identity;
361    ///
362    /// let identity = Identity::self_signed_builder()
363    ///     .subject_alt_names(&["localhost", "127.0.0.1", "::1"])
364    ///     .from_now_utc()
365    ///     .validity_days(14)
366    ///     .build()
367    ///     .unwrap();
368    /// ```
369    ///
370    /// # Features
371    ///
372    /// This function is only available when the `self-signed` feature is enabled.
373    ///
374    /// [1]: self_signed::SelfSignedIdentityBuilder
375    #[cfg(feature = "self-signed")]
376    #[cfg_attr(docsrs, doc(cfg(feature = "self-signed")))]
377    pub fn self_signed_builder(
378    ) -> self_signed::SelfSignedIdentityBuilder<self_signed::states::WantsSans> {
379        self_signed::SelfSignedIdentityBuilder::new()
380    }
381
382    /// Returns a reference to the certificate chain associated with the identity.
383    pub fn certificate_chain(&self) -> &CertificateChain {
384        &self.certificate_chain
385    }
386
387    /// Returns a reference to the private key associated with the identity.
388    pub fn private_key(&self) -> &PrivateKey {
389        &self.private_key
390    }
391
392    /// Clones this identity.
393    ///
394    /// # Note
395    /// `Identity` does not implement `Clone` directly to ensure that sensitive information,
396    /// specifically the inner *private key*, is not cloned inadvertently.
397    /// Implementing `Clone` directly could potentially lead to unintended cloning of sensitive data.
398    pub fn clone_identity(&self) -> Self {
399        Self::new(self.certificate_chain.clone(), self.private_key.clone_key())
400    }
401}
402
403/// Represents the formatting options for displaying a SHA-256 digest.
404#[derive(Debug, Copy, Clone)]
405pub enum Sha256DigestFmt {
406    /// Represents the SHA-256 digest as an array of bytes.
407    ///
408    /// The string-format is as follows: `"[b0, b1, b2, ..., b31]"`,
409    /// where `b` is the byte represented as a *decimal* integer.
410    BytesArray,
411
412    /// Represents the SHA-256 digest as a dotted hexadecimal string.
413    ///
414    /// The string-format is as follows: `"x0:x1:x2:...:x31"` where `x`
415    /// is the byte represented as a *hexadecimal* integer.
416    DottedHex,
417}
418
419/// Represents a *SHA-256* digest, which is a fixed-size array of 32 bytes.
420///
421/// For example, you can obtain the certificate digest with [`Certificate::hash`].
422#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
423pub struct Sha256Digest([u8; 32]);
424
425impl Sha256Digest {
426    /// Creates a new instance from a given array of 32 bytes.
427    ///
428    /// # Examples
429    ///
430    /// ```
431    /// use wtransport::tls::Sha256Digest;
432    ///
433    /// // Create a new SHA-256 digest instance.
434    /// let digest = Sha256Digest::new([97; 32]);
435    /// ```
436    pub fn new(bytes: [u8; 32]) -> Self {
437        Self(bytes)
438    }
439
440    /// Attempts to create a new instance from a string representation.
441    ///
442    /// This method parses the string representation of the digest according to the provided
443    /// format (`fmt`) and constructs a new `Sha256Digest` instance if the parsing is successful.
444    ///
445    /// # Examples
446    ///
447    /// ```
448    /// use wtransport::tls::Sha256Digest;
449    /// use wtransport::tls::Sha256DigestFmt;
450    ///
451    /// const HASH_ARRAY: &str = "[234, 204, 110, 153, 82, 177, 87, 232, 180, 125, \
452    ///                           234, 158, 66, 129, 212, 250, 217, 48, 47, 32, 83, \
453    ///                           133, 23, 44, 79, 198, 70, 118, 25, 153, 146, 142]";
454    ///
455    /// let digest_bytes_array = Sha256Digest::from_str_fmt(HASH_ARRAY, Sha256DigestFmt::BytesArray);
456    /// assert!(digest_bytes_array.is_ok());
457    ///
458    /// const HASH_HEX: &str = "e3:4e:c7:de:b8:da:2d:b8:3c:86:a0:11:76:40:75:b3:\
459    ///                         b9:ba:9d:00:e0:04:b3:38:72:cd:a1:af:4e:e3:11:26";
460    ///
461    /// let digest_dotted_hex = Sha256Digest::from_str_fmt(HASH_HEX, Sha256DigestFmt::DottedHex);
462    /// assert!(digest_dotted_hex.is_ok());
463    ///
464    /// let invalid_digest = Sha256Digest::from_str_fmt("invalid_digest", Sha256DigestFmt::BytesArray);
465    /// assert!(invalid_digest.is_err());
466    /// ```
467    pub fn from_str_fmt<S>(s: S, fmt: Sha256DigestFmt) -> Result<Self, InvalidDigest>
468    where
469        S: AsRef<str>,
470    {
471        let bytes = match fmt {
472            Sha256DigestFmt::BytesArray => s
473                .as_ref()
474                .trim_start_matches('[')
475                .trim_end_matches(']')
476                .split(',')
477                .map(|byte| byte.trim().parse::<u8>().map_err(|_| InvalidDigest))
478                .collect::<Result<Vec<u8>, _>>()?,
479
480            Sha256DigestFmt::DottedHex => s
481                .as_ref()
482                .split(':')
483                .map(|hex| u8::from_str_radix(hex.trim(), 16).map_err(|_| InvalidDigest))
484                .collect::<Result<Vec<u8>, _>>()?,
485        };
486
487        Ok(Self(bytes.try_into().map_err(|_| InvalidDigest)?))
488    }
489
490    /// Formats the digest into a human-readable representation based on the specified format.
491    ///
492    /// # Examples
493    ///
494    /// ```
495    /// use wtransport::tls::Sha256Digest;
496    /// use wtransport::tls::Sha256DigestFmt;
497    ///
498    /// let digest = Sha256Digest::new([
499    ///     97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115,
500    ///     116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 64,
501    /// ]);
502    ///
503    /// // Represent the digest as a byte array string.
504    /// let bytes_array_fmt = digest.fmt(Sha256DigestFmt::BytesArray);
505    /// assert_eq!(
506    ///     bytes_array_fmt,
507    ///     "[97, 98, 99, 100, 101, 102, 103, 104, 105, 106, \
508    ///       107, 108, 109, 110, 111, 112, 113, 114, 115, 116, \
509    ///       117, 118, 119, 120, 121, 122, 123, 124, 125, 126, \
510    ///       127, 64]"
511    /// );
512    ///
513    /// // Represent the digest as a dotted hexadecimal string.
514    /// let dotted_hex_fmt = digest.fmt(Sha256DigestFmt::DottedHex);
515    /// assert_eq!(
516    ///     dotted_hex_fmt,
517    ///     "61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:\
518    ///      71:72:73:74:75:76:77:78:79:7a:7b:7c:7d:7e:7f:40"
519    /// );
520    /// ```
521    pub fn fmt(&self, fmt: Sha256DigestFmt) -> String {
522        match fmt {
523            Sha256DigestFmt::BytesArray => {
524                format!("{:?}", self.0)
525            }
526            Sha256DigestFmt::DottedHex => self
527                .0
528                .iter()
529                .map(|byte| format!("{byte:02x}"))
530                .collect::<Vec<_>>()
531                .join(":"),
532        }
533    }
534}
535
536impl From<[u8; 32]> for Sha256Digest {
537    fn from(value: [u8; 32]) -> Self {
538        Self::new(value)
539    }
540}
541
542impl AsRef<[u8; 32]> for Sha256Digest {
543    fn as_ref(&self) -> &[u8; 32] {
544        &self.0
545    }
546}
547
548impl Display for Sha256Digest {
549    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
550        Display::fmt(&self.fmt(Sha256DigestFmt::DottedHex), f)
551    }
552}
553
554/// Represents data related to a TLS handshake process.
555#[derive(Clone, Debug)]
556pub struct HandshakeData {
557    pub(crate) alpn: Option<Vec<u8>>,
558    pub(crate) server_name: Option<String>,
559}
560
561impl HandshakeData {
562    /// Application-Layer Protocol Negotiation (ALPN) data.
563    pub fn alpn(&self) -> Option<&[u8]> {
564        self.alpn.as_deref()
565    }
566
567    /// The server name associated with the handshake data.
568    pub fn server_name(&self) -> Option<&str> {
569        self.server_name.as_deref()
570    }
571}
572
573impl FromStr for Sha256Digest {
574    type Err = InvalidDigest;
575
576    fn from_str(s: &str) -> Result<Self, Self::Err> {
577        Sha256Digest::from_str_fmt(s, Sha256DigestFmt::BytesArray)
578            .or_else(|_| Sha256Digest::from_str_fmt(s, Sha256DigestFmt::DottedHex))
579    }
580}
581
582/// Builds [`rustls::RootCertStore`] by using platform’s native certificate store.
583///
584/// This function works on a *best-effort* basis, attempting to load every possible
585/// root certificate found on the platform. It skips anchors that produce errors during loading.
586/// Therefore, it might produce an *empty* root store.
587///
588///
589/// It's important to note that this function can be expensive, as it might involve loading
590/// all root certificates from the filesystem platform. Therefore, it's advisable to use it
591/// sporadically and judiciously.
592pub fn build_native_cert_store() -> RootCertStore {
593    let _vars_restore_guard = utils::remove_vars_tmp(["SSL_CERT_FILE", "SSL_CERT_DIR"]);
594
595    let mut root_store = RootCertStore::empty();
596
597    let rustls_native_certs::CertificateResult { certs, .. } =
598        rustls_native_certs::load_native_certs();
599
600    for c in certs {
601        let _ = root_store.add(c);
602    }
603
604    root_store
605}
606
607fn default_crypto_provider() -> rustls::crypto::CryptoProvider {
608    #[cfg(feature = "ring")]
609    {
610        rustls::crypto::ring::default_provider()
611    }
612
613    #[cfg(not(feature = "ring"))]
614    {
615        rustls::crypto::aws_lc_rs::default_provider()
616    }
617}
618
619/// TLS configurations and utilities server-side.
620pub mod server {
621    use super::*;
622    use rustls::ServerConfig as TlsServerConfig;
623
624    /// Builds a default TLS server configuration with safe defaults.
625    ///
626    /// This function constructs a TLS server configuration with safe defaults.
627    /// The configuration utilizes the identity (certificate and private key) specified
628    /// in the input argument of the function.
629    ///
630    /// Client authentication is not required in this configuration.
631    ///
632    /// # Arguments
633    ///
634    /// - `identity`: A reference to the identity containing the certificate chain and private key.
635    pub fn build_default_tls_config(identity: Identity) -> TlsServerConfig {
636        let provider = Arc::new(default_crypto_provider());
637
638        let certificates = identity
639            .certificate_chain
640            .0
641            .into_iter()
642            .map(|cert| cert.0)
643            .collect();
644
645        let private_key = identity.private_key.0;
646
647        let mut tls_config = TlsServerConfig::builder_with_provider(provider)
648            .with_protocol_versions(&[&rustls::version::TLS13])
649            .expect("valid version")
650            .with_no_client_auth()
651            .with_single_cert(certificates, private_key)
652            .expect("Certificate and private key should be already validated");
653
654        tls_config.alpn_protocols = [WEBTRANSPORT_ALPN.to_vec()].to_vec();
655
656        tls_config
657    }
658}
659
660/// TLS configurations and utilities client-side.
661pub mod client {
662    use super::*;
663    use rustls::client::danger::ServerCertVerified;
664    use rustls::client::danger::ServerCertVerifier;
665    use rustls::crypto::WebPkiSupportedAlgorithms;
666    use rustls::ClientConfig as TlsClientConfig;
667
668    /// Builds a default TLS client configuration with safe defaults.
669    ///
670    /// This function constructs a TLS client configuration with safe defaults.
671    /// It utilizes the provided `RootCertStore` to validate server certificates during the TLS handshake.
672    ///
673    /// If a custom [`ServerCertVerifier`] is provided, it will be used for certificate validation; otherwise,
674    /// it will use the standard safe default mechanism (using the Web PKI mechanism).
675    ///
676    /// Client authentication is not required in this configuration.
677    ///
678    /// # Arguments
679    ///
680    /// - `root_store`: An `Arc` containing the [`RootCertStore`] with trusted root certificates.
681    ///   To obtain a `RootCertStore`, one can use the [`build_native_cert_store`]
682    ///   function, which loads the platform's certificate authorities (CAs).
683    /// - `custom_verifier`: An optional `Arc` containing a custom implementation of the [`ServerCertVerifier`]
684    ///   trait for custom certificate verification. If `None` is provided, the default
685    ///   Web PKI mechanism will be used.
686    ///
687    /// # Note
688    ///
689    /// If a custom `ServerCertVerifier` is provided, exercise caution as it could potentially compromise the
690    /// certificate validation process if not implemented correctly.
691    pub fn build_default_tls_config(
692        root_store: Arc<RootCertStore>,
693        custom_verifier: Option<Arc<dyn ServerCertVerifier>>,
694    ) -> TlsClientConfig {
695        let provider = Arc::new(default_crypto_provider());
696
697        let mut config = TlsClientConfig::builder_with_provider(provider)
698            .with_protocol_versions(&[&rustls::version::TLS13])
699            .expect("valid version")
700            .with_root_certificates(root_store)
701            .with_no_client_auth();
702
703        if let Some(custom_verifier) = custom_verifier {
704            config.dangerous().set_certificate_verifier(custom_verifier);
705        }
706
707        config.alpn_protocols = [WEBTRANSPORT_ALPN.to_vec()].to_vec();
708        config
709    }
710
711    /// A custom **insecure** [`ServerCertVerifier`] implementation.
712    ///
713    /// This verifier is configured to skip all server's certificate validation.
714    ///
715    /// Therefore, it's advisable to use it judiciously, and avoid using it in
716    /// production environments.
717    #[derive(Debug)]
718    pub struct NoServerVerification {
719        supported_algorithms: WebPkiSupportedAlgorithms,
720    }
721
722    impl Default for NoServerVerification {
723        fn default() -> Self {
724            Self::new()
725        }
726    }
727
728    impl NoServerVerification {
729        /// Creates a new instance of `NoServerVerification`.
730        pub fn new() -> NoServerVerification {
731            NoServerVerification {
732                supported_algorithms: default_crypto_provider().signature_verification_algorithms,
733            }
734        }
735    }
736
737    impl ServerCertVerifier for NoServerVerification {
738        fn verify_server_cert(
739            &self,
740            _end_entity: &CertificateDer,
741            _intermediates: &[CertificateDer],
742            _server_name: &rustls_pki_types::ServerName,
743            _ocsp_response: &[u8],
744            _now: rustls_pki_types::UnixTime,
745        ) -> Result<ServerCertVerified, rustls::Error> {
746            Ok(ServerCertVerified::assertion())
747        }
748
749        fn verify_tls12_signature(
750            &self,
751            message: &[u8],
752            cert: &CertificateDer<'_>,
753            dss: &rustls::DigitallySignedStruct,
754        ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
755            rustls::crypto::verify_tls12_signature(message, cert, dss, &self.supported_algorithms)
756        }
757
758        fn verify_tls13_signature(
759            &self,
760            message: &[u8],
761            cert: &CertificateDer<'_>,
762            dss: &rustls::DigitallySignedStruct,
763        ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
764            rustls::crypto::verify_tls13_signature(message, cert, dss, &self.supported_algorithms)
765        }
766
767        fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
768            self.supported_algorithms.supported_schemes()
769        }
770    }
771
772    /// A custom [`ServerCertVerifier`] implementation.
773    ///
774    /// Configures the client to skip *some* server certificates validation.
775    ///
776    /// This verifier is configured to accept server certificates
777    /// whose digests match the specified *SHA-256* hashes and fulfill
778    /// some additional constraints (*see notes below*).
779    ///
780    /// This is useful for scenarios where clients need to accept known
781    /// self-signed certificates or certificates from non-standard authorities.
782    ///
783    /// # Notes
784    ///
785    /// - The current time MUST be within the validity period of the certificate.
786    /// - The total length of the validity period MUST NOT exceed *two* weeks.
787    /// - Only certificates for which the public key algorithm is *ECDSA* with the *secp256r1* are accepted.
788    #[derive(Debug)]
789    pub struct ServerHashVerification {
790        hashes: std::collections::BTreeSet<Sha256Digest>,
791        supported_algorithms: WebPkiSupportedAlgorithms,
792    }
793
794    impl ServerHashVerification {
795        const SELF_MAX_VALIDITY: time::Duration = time::Duration::days(14);
796
797        /// Creates a new instance of `ServerHashVerification`.
798        ///
799        /// # Arguments
800        ///
801        /// - `hashes`: An iterator yielding `Sha256Digest` instances representing the
802        ///   accepted certificate hashes.
803        pub fn new<H>(hashes: H) -> Self
804        where
805            H: IntoIterator<Item = Sha256Digest>,
806        {
807            use std::collections::BTreeSet;
808
809            Self {
810                hashes: BTreeSet::from_iter(hashes),
811                supported_algorithms: default_crypto_provider().signature_verification_algorithms,
812            }
813        }
814
815        /// Adds a `digest` to the list of accepted certificates.
816        pub fn add(&mut self, digest: Sha256Digest) {
817            self.hashes.insert(digest);
818        }
819    }
820
821    impl FromIterator<Sha256Digest> for ServerHashVerification {
822        fn from_iter<T: IntoIterator<Item = Sha256Digest>>(iter: T) -> Self {
823            Self::new(iter)
824        }
825    }
826
827    impl ServerCertVerifier for ServerHashVerification {
828        fn verify_server_cert(
829            &self,
830            end_entity: &CertificateDer,
831            _intermediates: &[CertificateDer],
832            _server_name: &rustls_pki_types::ServerName,
833            _ocsp_response: &[u8],
834            now: rustls_pki_types::UnixTime,
835        ) -> Result<ServerCertVerified, rustls::Error> {
836            use time::OffsetDateTime;
837            use x509_parser::oid_registry::OID_EC_P256;
838            use x509_parser::oid_registry::OID_KEY_TYPE_EC_PUBLIC_KEY;
839            use x509_parser::time::ASN1Time;
840
841            let now = ASN1Time::new(
842                now.as_secs()
843                    .try_into()
844                    .ok()
845                    .and_then(|time| OffsetDateTime::from_unix_timestamp(time).ok())
846                    .expect("time overflow"),
847            );
848
849            let x509 = X509Certificate::from_der(end_entity.as_ref())
850                .map_err(|_| rustls::CertificateError::BadEncoding)?
851                .1;
852
853            match x509.validity() {
854                x if now < x.not_before => {
855                    return Err(rustls::CertificateError::NotValidYet.into());
856                }
857                x if now > x.not_after => {
858                    return Err(rustls::CertificateError::Expired.into());
859                }
860                _ => {}
861            }
862
863            let validity_period = x509.validity().not_after - x509.validity.not_before;
864            if !matches!(validity_period, Some(x) if x <= Self::SELF_MAX_VALIDITY) {
865                return Err(rustls::CertificateError::UnknownIssuer.into());
866            }
867
868            if x509.public_key().algorithm.algorithm != OID_KEY_TYPE_EC_PUBLIC_KEY {
869                return Err(rustls::CertificateError::UnknownIssuer.into());
870            }
871
872            if !matches!(x509.public_key().algorithm.parameters.as_ref().map(|any| any.as_oid()),
873                         Some(Ok(oid)) if oid == OID_EC_P256)
874            {
875                return Err(rustls::CertificateError::UnknownIssuer.into());
876            }
877
878            // TODO: Duplicates logic in `Certificate::from_der`, to avoid allocating
879            X509Certificate::from_der(end_entity.as_ref())
880                .map_err(|_| rustls::CertificateError::BadEncoding)?;
881            // TODO: Duplicates logic in `Certificate::hash`, to avoid allocating
882            let end_entity_hash = Sha256Digest(Sha256::digest(end_entity.as_ref()).into());
883
884            if self.hashes.contains(&end_entity_hash) {
885                Ok(ServerCertVerified::assertion())
886            } else {
887                Err(rustls::CertificateError::UnknownIssuer.into())
888            }
889        }
890
891        fn verify_tls12_signature(
892            &self,
893            message: &[u8],
894            cert: &CertificateDer<'_>,
895            dss: &rustls::DigitallySignedStruct,
896        ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
897            rustls::crypto::verify_tls12_signature(message, cert, dss, &self.supported_algorithms)
898        }
899
900        fn verify_tls13_signature(
901            &self,
902            message: &[u8],
903            cert: &CertificateDer<'_>,
904            dss: &rustls::DigitallySignedStruct,
905        ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
906            rustls::crypto::verify_tls13_signature(message, cert, dss, &self.supported_algorithms)
907        }
908
909        fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
910            self.supported_algorithms.supported_schemes()
911        }
912    }
913}
914
915/// TLS errors definitions module.
916pub mod error {
917    use std::path::PathBuf;
918
919    /// Represents an error indicating an invalid certificate parsing.
920    #[derive(Debug, thiserror::Error)]
921    #[error("invalid certificate: {0}")]
922    pub struct InvalidCertificate(pub(super) String);
923
924    /// Represents an error failure to parse a string as a [`Sha256Digest`](super::Sha256Digest).
925    ///
926    /// See [`Sha256Digest::from_str_fmt`](super::Sha256Digest::from_str_fmt).
927    #[derive(Debug, thiserror::Error)]
928    #[error("cannot parse string as sha256 digest")]
929    pub struct InvalidDigest;
930
931    /// Error during PEM load operation.
932    #[derive(Debug, thiserror::Error)]
933    pub enum PemLoadError {
934        /// Invalid certificate during chain load.
935        #[error("invalid certificate in the PEM chain (index: {}): {}", .index, .error)]
936        InvalidCertificateChain {
937            /// The index of the certificate in the PEM file.
938            index: usize,
939            /// Additional error information.
940            error: InvalidCertificate,
941        },
942
943        /// Cannot load the private key as the PEM file does not contain it.
944        #[error("no private key section found in PEM file")]
945        NoPrivateKeySection,
946
947        /// Cannot load the certificate as the PEM file does not contain it.
948        #[error("no certificate section found in PEM file")]
949        NoCertificateSection,
950
951        /// I/O operation encoding/decoding PEM file failed.
952        #[error("error on file '{}': {}", .file.display(), error)]
953        FileError {
954            /// Filename of the operation.
955            file: PathBuf,
956
957            /// io error details.
958            error: std::io::Error,
959        },
960    }
961
962    /// Certificate SANs are not valid DNS.
963    ///
964    /// This error might happen during self signed certificate generation
965    /// [`Identity::self_signed`](super::Identity::self_signed).
966    /// In particular, *Subject Alternative Names* passed for the generation of the
967    /// certificate are not valid DNS *IA5* strings.
968    ///
969    /// DNS strings support the [International Alphabet No. 5 (IA5)] character encoding, i.e.
970    /// the 128 characters of the ASCII alphabet.
971    ///
972    /// [International Alphabet No. 5 (IA5)]: https://en.wikipedia.org/wiki/T.50_(standard)
973    #[cfg(feature = "self-signed")]
974    #[cfg_attr(docsrs, doc(cfg(feature = "self-signed")))]
975    #[derive(Debug, thiserror::Error)]
976    #[error("invalid SANs for the self certificate")]
977    pub struct InvalidSan;
978}
979
980/// Module for generating self-signed [`Identity`].
981///
982/// This module provides a builder pattern for constructing self-signed
983/// identities. These certificates are primarily used for local testing or
984/// development where a full certificate authority (CA) infrastructure isn't
985/// necessary.
986///
987/// The feature is enabled when the `self-signed` feature flag is active.
988#[cfg(feature = "self-signed")]
989#[cfg_attr(docsrs, doc(cfg(feature = "self-signed")))]
990pub mod self_signed {
991    use super::*;
992    use error::InvalidSan;
993    use rcgen::CertificateParams;
994    use rcgen::DistinguishedName;
995    use rcgen::DnType;
996    use rcgen::KeyPair;
997    use rcgen::PKCS_ECDSA_P256_SHA256;
998    use time::OffsetDateTime;
999
1000    /// The builder for creating self-signed [`Identity`].
1001    ///
1002    /// This struct uses state-based typing to enforce that the appropriate methods
1003    /// are called in the right order.
1004    pub struct SelfSignedIdentityBuilder<State>(State);
1005
1006    impl SelfSignedIdentityBuilder<states::WantsSans> {
1007        /// Creates a new `SelfSignedIdentityBuilder` instance.
1008        ///
1009        /// The builder starts in the `WantsSans` state, meaning it requires subject
1010        /// alternative names (SANs) to be specified before continuing.
1011        ///
1012        /// # Example
1013        ///
1014        /// ```
1015        /// use wtransport::tls::self_signed::SelfSignedIdentityBuilder;
1016        ///
1017        /// let builder = SelfSignedIdentityBuilder::new();
1018        /// ```
1019        ///
1020        /// **Note**: You can conveniently create a new builder with [`Identity::self_signed_builder()`].
1021        ///
1022        /// # Example
1023        ///
1024        /// ```
1025        /// use wtransport::Identity;
1026        ///
1027        /// let builder = Identity::self_signed_builder();
1028        /// ```
1029        pub fn new() -> Self {
1030            Self(states::WantsSans {})
1031        }
1032
1033        /// Specifies the subject alternative names (SANs) for the certificate.
1034        ///
1035        /// The SANs can be provided as a collection of strings, such as hostnames or IP addresses.
1036        ///
1037        /// # Arguments
1038        ///
1039        /// * `subject_alt_names` - An iterator of strings representing subject alternative names (SANs).
1040        ///   They can be both hostnames or IP addresses.
1041        ///
1042        /// # Example
1043        ///
1044        /// ```
1045        /// use wtransport::tls::self_signed::SelfSignedIdentityBuilder;
1046        ///
1047        /// let builder =
1048        ///     SelfSignedIdentityBuilder::new().subject_alt_names(&["localhost", "127.0.0.1", "::1"]);
1049        /// ```
1050        pub fn subject_alt_names<I, S>(
1051            self,
1052            subject_alt_names: I,
1053        ) -> SelfSignedIdentityBuilder<states::WantsValidityPeriod>
1054        where
1055            I: IntoIterator<Item = S>,
1056            S: AsRef<str>,
1057        {
1058            let sans = subject_alt_names
1059                .into_iter()
1060                .map(|s| s.as_ref().to_string())
1061                .collect();
1062
1063            SelfSignedIdentityBuilder(states::WantsValidityPeriod { sans })
1064        }
1065    }
1066
1067    impl SelfSignedIdentityBuilder<states::WantsValidityPeriod> {
1068        /// Sets the certificate's `not_before` time to the current UTC time.
1069        ///
1070        /// After this, the builder is in the `WantsNotAfter` state, requiring a `not_after` time to be set.
1071        #[allow(clippy::wrong_self_convention)]
1072        pub fn from_now_utc(self) -> SelfSignedIdentityBuilder<states::WantsNotAfter> {
1073            let not_before = OffsetDateTime::now_utc();
1074            self.not_before(not_before)
1075        }
1076
1077        /// Sets the `not_before` time of the certificate.
1078        ///
1079        /// # Parameters
1080        ///
1081        /// - `not_before`: The starting time of the certificate's validity period.
1082        ///
1083        /// After this, the builder is in the `WantsNotAfter` state, requiring a `not_after` time to be set.
1084        pub fn not_before(
1085            self,
1086            not_before: OffsetDateTime,
1087        ) -> SelfSignedIdentityBuilder<states::WantsNotAfter> {
1088            SelfSignedIdentityBuilder(states::WantsNotAfter {
1089                sans: self.0.sans,
1090                not_before,
1091            })
1092        }
1093
1094        /// Specifies the validity period for the certificate.
1095        ///
1096        /// # Parameters
1097        ///
1098        /// - `not_before`: The starting time of the certificate's validity period.
1099        /// - `not_after`: The ending time of the certificate's validity period.
1100        ///
1101        /// # Example
1102        ///
1103        /// ```
1104        /// use wtransport::tls::self_signed::time::OffsetDateTime;
1105        /// use wtransport::tls::self_signed::SelfSignedIdentityBuilder;
1106        ///
1107        /// let builder = SelfSignedIdentityBuilder::new()
1108        ///     .subject_alt_names(&["localhost"])
1109        ///     .validity_period(
1110        ///         OffsetDateTime::now_utc(),
1111        ///         OffsetDateTime::now_utc() + time::Duration::days(7),
1112        ///     );
1113        /// ```
1114        pub fn validity_period(
1115            self,
1116            not_before: OffsetDateTime,
1117            not_after: OffsetDateTime,
1118        ) -> SelfSignedIdentityBuilder<states::ReadyToBuild> {
1119            self.not_before(not_before).not_after(not_after)
1120        }
1121    }
1122
1123    impl SelfSignedIdentityBuilder<states::WantsNotAfter> {
1124        /// Specifies the `not_after` time of the certificate.
1125        ///
1126        /// # Parameters
1127        ///
1128        /// - `not_after`: The ending time of the certificate's validity.
1129        pub fn not_after(
1130            self,
1131            not_after: OffsetDateTime,
1132        ) -> SelfSignedIdentityBuilder<states::ReadyToBuild> {
1133            SelfSignedIdentityBuilder(states::ReadyToBuild {
1134                sans: self.0.sans,
1135                not_before: self.0.not_before,
1136                not_after,
1137            })
1138        }
1139
1140        /// Sets the `not_after` time of the certificate to an offset from the `not_before` time.
1141        ///
1142        /// # Parameters
1143        ///
1144        /// - `offset`: A time duration that specifies how far `not_after` should be from `not_before`.
1145        ///
1146        /// # Example
1147        ///
1148        /// ```
1149        /// use wtransport::tls::self_signed::time::OffsetDateTime;
1150        /// use wtransport::tls::self_signed::SelfSignedIdentityBuilder;
1151        ///
1152        /// let builder = SelfSignedIdentityBuilder::new()
1153        ///     .subject_alt_names(&["localhost"])
1154        ///     .not_before(OffsetDateTime::now_utc())
1155        ///     .offset_from_not_before(time::Duration::days(7)); // now + 7 days
1156        /// ```
1157        pub fn offset_from_not_before(
1158            self,
1159            offset: time::Duration,
1160        ) -> SelfSignedIdentityBuilder<states::ReadyToBuild> {
1161            let not_after = self.0.not_before + offset;
1162            self.not_after(not_after)
1163        }
1164
1165        /// Sets the certificate's validity to a specified number of days from `not_before`.
1166        ///
1167        /// # Parameters
1168        ///
1169        /// - `days`: The number of days for which the certificate should be valid.
1170        ///
1171        /// # Example
1172        ///
1173        /// ```
1174        /// use wtransport::tls::self_signed::SelfSignedIdentityBuilder;
1175        ///
1176        /// let builder = SelfSignedIdentityBuilder::new()
1177        ///     .subject_alt_names(&["localhost"])
1178        ///     .from_now_utc()
1179        ///     .validity_days(14);
1180        /// ```
1181        pub fn validity_days(self, days: u32) -> SelfSignedIdentityBuilder<states::ReadyToBuild> {
1182            self.offset_from_not_before(time::Duration::days(days as i64))
1183        }
1184    }
1185
1186    impl SelfSignedIdentityBuilder<states::ReadyToBuild> {
1187        /// Generates a self-signed certificate and private key for new identity.
1188        ///
1189        /// # Returns
1190        ///
1191        /// Returns an [`Identity`] containing the certificate and private key,
1192        /// or an error if the SANs are invalid (they are not valid *ASN.1* strings).
1193        ///
1194        /// # Specifications
1195        ///
1196        /// The certificate will be conforms to the following specifications:
1197        ///
1198        /// - The certificate is an *X.509v3* certificate as defined in *RFC5280*.
1199        /// - The key used in the Subject Public Key field uses `ECDSA P-256` algorithm.
1200        ///
1201        /// # Example
1202        ///
1203        /// ```
1204        /// use wtransport::tls::self_signed::SelfSignedIdentityBuilder;
1205        ///
1206        /// let identity = SelfSignedIdentityBuilder::new()
1207        ///     .subject_alt_names(&["localhost", "127.0.0.1"])
1208        ///     .from_now_utc()
1209        ///     .validity_days(7)
1210        ///     .build()
1211        ///     .unwrap();
1212        /// ```
1213        pub fn build(self) -> Result<Identity, InvalidSan> {
1214            let mut dname = DistinguishedName::new();
1215            dname.push(DnType::CommonName, "wtransport self-signed");
1216
1217            let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)
1218                .expect("algorithm for key pair is supported");
1219
1220            let mut cert_params = CertificateParams::new(self.0.sans).map_err(|_| InvalidSan)?;
1221            cert_params.distinguished_name = dname;
1222            cert_params.not_before = self.0.not_before;
1223            cert_params.not_after = self.0.not_after;
1224            let cert = cert_params
1225                .self_signed(&key_pair)
1226                .expect("inner params are valid");
1227
1228            Ok(Identity::new(
1229                CertificateChain::single(Certificate(cert.der().clone())),
1230                PrivateKey::from_der_pkcs8(key_pair.serialize_der()),
1231            ))
1232        }
1233    }
1234
1235    impl Default for SelfSignedIdentityBuilder<states::WantsSans> {
1236        fn default() -> Self {
1237            Self::new()
1238        }
1239    }
1240
1241    /// State-types for [`SelfSignedIdentityBuilder`] builder.
1242    pub mod states {
1243        use super::*;
1244
1245        /// Initial state, requiring subject alternative names (SANs).
1246        pub struct WantsSans {}
1247
1248        /// State after SANs have been set, requiring a validity period.
1249        pub struct WantsValidityPeriod {
1250            pub(super) sans: Vec<String>,
1251        }
1252
1253        /// State after `not_before` is set, requiring `not_after` to be specified.
1254        pub struct WantsNotAfter {
1255            pub(super) sans: Vec<String>,
1256            pub(super) not_before: OffsetDateTime,
1257        }
1258
1259        /// Final state where all data is ready and the certificate can be built.
1260        pub struct ReadyToBuild {
1261            pub(super) sans: Vec<String>,
1262            pub(super) not_before: OffsetDateTime,
1263            pub(super) not_after: OffsetDateTime,
1264        }
1265    }
1266
1267    pub use time;
1268}
1269
1270pub use rustls;
1271
1272mod utils {
1273    use std::env;
1274    use std::ffi::OsStr;
1275    use std::ffi::OsString;
1276
1277    pub struct VarsRestoreGuard(Vec<(OsString, Option<OsString>)>);
1278
1279    impl Drop for VarsRestoreGuard {
1280        fn drop(&mut self) {
1281            for (k, v) in std::mem::take(&mut self.0) {
1282                if let Some(v) = v {
1283                    env::set_var(k, v);
1284                }
1285            }
1286        }
1287    }
1288
1289    pub fn remove_vars_tmp<I, K>(keys: I) -> VarsRestoreGuard
1290    where
1291        I: IntoIterator<Item = K>,
1292        K: AsRef<OsStr>,
1293    {
1294        let table = keys
1295            .into_iter()
1296            .map(|k| {
1297                let k = k.as_ref().to_os_string();
1298                let v = env::var_os(&k);
1299                env::remove_var(&k);
1300                (k, v)
1301            })
1302            .collect();
1303
1304        VarsRestoreGuard(table)
1305    }
1306}
1307
1308#[cfg(test)]
1309mod tests {
1310    use super::*;
1311
1312    #[test]
1313    fn invalid_certificate() {
1314        assert!(matches!(
1315            Certificate::from_der(b"invalid-certificate".to_vec()),
1316            Err(InvalidCertificate(_))
1317        ));
1318    }
1319
1320    #[test]
1321    fn idempotence_digest() {
1322        let digest = Sha256Digest::new([
1323            97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
1324            115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 64,
1325        ]);
1326
1327        let d = Sha256Digest::from_str_fmt(
1328            digest.fmt(Sha256DigestFmt::BytesArray),
1329            Sha256DigestFmt::BytesArray,
1330        )
1331        .unwrap();
1332
1333        assert_eq!(d, digest);
1334
1335        let d = Sha256Digest::from_str_fmt(
1336            digest.fmt(Sha256DigestFmt::DottedHex),
1337            Sha256DigestFmt::DottedHex,
1338        )
1339        .unwrap();
1340
1341        assert_eq!(d, digest);
1342    }
1343
1344    #[test]
1345    fn digest_from_str() {
1346        assert!(matches!(
1347            "invalid".parse::<Sha256Digest>(),
1348            Err(InvalidDigest)
1349        ));
1350
1351        assert!(matches!(
1352        "[97, 98, 99, 100, 101, 102, 103, 104, 105, 106, \
1353         107, 108, 109, 110, 111, 112, 113, 114, 115, 116, \
1354         117, 118, 119, 120, 121, 122, 123, 124, 125, 126, \
1355         127, 64]"
1356            .parse::<Sha256Digest>(),
1357
1358        Ok(digest) if digest == Sha256Digest::new([
1359            97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
1360            114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 64
1361        ])));
1362
1363        assert!(matches!(
1364            "61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70: \
1365          71:72:73:74:75:76:77:78:79:7a:7b:7c:7d:7e:7f:40"
1366                .parse::<Sha256Digest>(),
1367
1368        Ok(digest) if digest == Sha256Digest::new([
1369            97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
1370            114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 64
1371        ])));
1372    }
1373}