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