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}