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