1//! TLS configuration and types
2//!
3//! A `Client` will use transport layer security (TLS) by default to connect to
4//! HTTPS destinations.
5//!
6//! # Backends
7//!
8//! reqwest supports several TLS backends, enabled with Cargo features.
9//!
10//! ## default-tls
11//!
12//! reqwest will pick a TLS backend by default. This is true when the
13//! `default-tls` feature is enabled.
14//!
15//! While it currently uses `native-tls`, the feature set is designed to only
16//! enable configuration that is shared among available backends. This allows
17//! reqwest to change the default to `rustls` (or another) at some point in the
18//! future.
19//!
20//! <div class="warning">This feature is enabled by default, and takes
21//! precedence if any other crate enables it. This is true even if you declare
22//! `features = []`. You must set `default-features = false` instead.</div>
23//!
24//! Since Cargo features are additive, other crates in your dependency tree can
25//! cause the default backend to be enabled. If you wish to ensure your
26//! `Client` uses a specific backend, call the appropriate builder methods
27//! (such as [`use_rustls_tls()`][]).
28//!
29//! [`use_rustls_tls()`]: crate::ClientBuilder::use_rustls_tls()
30//!
31//! ## native-tls
32//!
33//! This backend uses the [native-tls][] crate. That will try to use the system
34//! TLS on Windows and Mac, and OpenSSL on Linux targets.
35//!
36//! Enabling the feature explicitly allows for `native-tls`-specific
37//! configuration options.
38//!
39//! [native-tls]: https://crates.io/crates/native-tls
40//!
41//! ## rustls-tls
42//!
43//! This backend uses the [rustls][] crate, a TLS library written in Rust.
44//!
45//! [rustls]: https://crates.io/crates/rustls
46
47#[cfg(feature = "__rustls")]
48use rustls::{
49 client::HandshakeSignatureValid, client::ServerCertVerified, client::ServerCertVerifier,
50 DigitallySignedStruct, Error as TLSError, ServerName,
51};
52use std::{
53 fmt,
54 io::{BufRead, BufReader},
55};
56
57/// Represents a server X509 certificate.
58#[derive(Clone)]
59pub struct Certificate {
60 #[cfg(feature = "native-tls-crate")]
61 native: native_tls_crate::Certificate,
62 #[cfg(feature = "__rustls")]
63 original: Cert,
64}
65
66#[cfg(feature = "__rustls")]
67#[derive(Clone)]
68enum Cert {
69 Der(Vec<u8>),
70 Pem(Vec<u8>),
71}
72
73/// Represents a private key and X509 cert as a client certificate.
74#[derive(Clone)]
75pub struct Identity {
76 #[cfg_attr(not(any(feature = "native-tls", feature = "__rustls")), allow(unused))]
77 inner: ClientCert,
78}
79
80#[derive(Clone)]
81enum ClientCert {
82 #[cfg(feature = "native-tls")]
83 Pkcs12(native_tls_crate::Identity),
84 #[cfg(feature = "native-tls")]
85 Pkcs8(native_tls_crate::Identity),
86 #[cfg(feature = "__rustls")]
87 Pem {
88 key: rustls::PrivateKey,
89 certs: Vec<rustls::Certificate>,
90 },
91}
92
93impl Certificate {
94 /// Create a `Certificate` from a binary DER encoded certificate
95 ///
96 /// # Examples
97 ///
98 /// ```
99 /// # use std::fs::File;
100 /// # use std::io::Read;
101 /// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
102 /// let mut buf = Vec::new();
103 /// File::open("my_cert.der")?
104 /// .read_to_end(&mut buf)?;
105 /// let cert = reqwest::Certificate::from_der(&buf)?;
106 /// # drop(cert);
107 /// # Ok(())
108 /// # }
109 /// ```
110 pub fn from_der(der: &[u8]) -> crate::Result<Certificate> {
111 Ok(Certificate {
112 #[cfg(feature = "native-tls-crate")]
113 native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?,
114 #[cfg(feature = "__rustls")]
115 original: Cert::Der(der.to_owned()),
116 })
117 }
118
119 /// Create a `Certificate` from a PEM encoded certificate
120 ///
121 /// # Examples
122 ///
123 /// ```
124 /// # use std::fs::File;
125 /// # use std::io::Read;
126 /// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
127 /// let mut buf = Vec::new();
128 /// File::open("my_cert.pem")?
129 /// .read_to_end(&mut buf)?;
130 /// let cert = reqwest::Certificate::from_pem(&buf)?;
131 /// # drop(cert);
132 /// # Ok(())
133 /// # }
134 /// ```
135 pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> {
136 Ok(Certificate {
137 #[cfg(feature = "native-tls-crate")]
138 native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?,
139 #[cfg(feature = "__rustls")]
140 original: Cert::Pem(pem.to_owned()),
141 })
142 }
143
144 /// Create a collection of `Certificate`s from a PEM encoded certificate bundle.
145 /// Example byte sources may be `.crt`, `.cer` or `.pem` files.
146 ///
147 /// # Examples
148 ///
149 /// ```
150 /// # use std::fs::File;
151 /// # use std::io::Read;
152 /// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
153 /// let mut buf = Vec::new();
154 /// File::open("ca-bundle.crt")?
155 /// .read_to_end(&mut buf)?;
156 /// let certs = reqwest::Certificate::from_pem_bundle(&buf)?;
157 /// # drop(certs);
158 /// # Ok(())
159 /// # }
160 /// ```
161 pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<Certificate>> {
162 let mut reader = BufReader::new(pem_bundle);
163
164 Self::read_pem_certs(&mut reader)?
165 .iter()
166 .map(|cert_vec| Certificate::from_der(&cert_vec))
167 .collect::<crate::Result<Vec<Certificate>>>()
168 }
169
170 #[cfg(feature = "native-tls-crate")]
171 pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) {
172 tls.add_root_certificate(self.native);
173 }
174
175 #[cfg(feature = "__rustls")]
176 pub(crate) fn add_to_rustls(
177 self,
178 root_cert_store: &mut rustls::RootCertStore,
179 ) -> crate::Result<()> {
180 use std::io::Cursor;
181
182 match self.original {
183 Cert::Der(buf) => root_cert_store
184 .add(&rustls::Certificate(buf))
185 .map_err(crate::error::builder)?,
186 Cert::Pem(buf) => {
187 let mut reader = Cursor::new(buf);
188 let certs = Self::read_pem_certs(&mut reader)?;
189 for c in certs {
190 root_cert_store
191 .add(&rustls::Certificate(c))
192 .map_err(crate::error::builder)?;
193 }
194 }
195 }
196 Ok(())
197 }
198
199 fn read_pem_certs(reader: &mut impl BufRead) -> crate::Result<Vec<Vec<u8>>> {
200 rustls_pemfile::certs(reader)
201 .map_err(|_| crate::error::builder("invalid certificate encoding"))
202 }
203}
204
205impl Identity {
206 /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key.
207 ///
208 /// The archive should contain a leaf certificate and its private key, as well any intermediate
209 /// certificates that allow clients to build a chain to a trusted root.
210 /// The chain certificates should be in order from the leaf certificate towards the root.
211 ///
212 /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created
213 /// with the OpenSSL `pkcs12` tool:
214 ///
215 /// ```bash
216 /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem
217 /// ```
218 ///
219 /// # Examples
220 ///
221 /// ```
222 /// # use std::fs::File;
223 /// # use std::io::Read;
224 /// # fn pkcs12() -> Result<(), Box<dyn std::error::Error>> {
225 /// let mut buf = Vec::new();
226 /// File::open("my-ident.pfx")?
227 /// .read_to_end(&mut buf)?;
228 /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;
229 /// # drop(pkcs12);
230 /// # Ok(())
231 /// # }
232 /// ```
233 ///
234 /// # Optional
235 ///
236 /// This requires the `native-tls` Cargo feature enabled.
237 #[cfg(feature = "native-tls")]
238 pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
239 Ok(Identity {
240 inner: ClientCert::Pkcs12(
241 native_tls_crate::Identity::from_pkcs12(der, password)
242 .map_err(crate::error::builder)?,
243 ),
244 })
245 }
246
247 /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
248 /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
249 ///
250 /// The certificate chain should contain any intermediate cerficates that should be sent to
251 /// clients to allow them to build a chain to a trusted root.
252 ///
253 /// A certificate chain here means a series of PEM encoded certificates concatenated together.
254 ///
255 /// # Examples
256 ///
257 /// ```
258 /// # use std::fs;
259 /// # fn pkcs8() -> Result<(), Box<dyn std::error::Error>> {
260 /// let cert = fs::read("client.pem")?;
261 /// let key = fs::read("key.pem")?;
262 /// let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?;
263 /// # drop(pkcs8);
264 /// # Ok(())
265 /// # }
266 /// ```
267 ///
268 /// # Optional
269 ///
270 /// This requires the `native-tls` Cargo feature enabled.
271 #[cfg(feature = "native-tls")]
272 pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result<Identity> {
273 Ok(Identity {
274 inner: ClientCert::Pkcs8(
275 native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?,
276 ),
277 })
278 }
279
280 /// Parses PEM encoded private key and certificate.
281 ///
282 /// The input should contain a PEM encoded private key
283 /// and at least one PEM encoded certificate.
284 ///
285 /// Note: The private key must be in RSA, SEC1 Elliptic Curve or PKCS#8 format.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// # use std::fs::File;
291 /// # use std::io::Read;
292 /// # fn pem() -> Result<(), Box<dyn std::error::Error>> {
293 /// let mut buf = Vec::new();
294 /// File::open("my-ident.pem")?
295 /// .read_to_end(&mut buf)?;
296 /// let id = reqwest::Identity::from_pem(&buf)?;
297 /// # drop(id);
298 /// # Ok(())
299 /// # }
300 /// ```
301 ///
302 /// # Optional
303 ///
304 /// This requires the `rustls-tls(-...)` Cargo feature enabled.
305 #[cfg(feature = "__rustls")]
306 pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
307 use std::io::Cursor;
308
309 let (key, certs) = {
310 let mut pem = Cursor::new(buf);
311 let mut sk = Vec::<rustls::PrivateKey>::new();
312 let mut certs = Vec::<rustls::Certificate>::new();
313
314 for item in std::iter::from_fn(|| rustls_pemfile::read_one(&mut pem).transpose()) {
315 match item.map_err(|_| {
316 crate::error::builder(TLSError::General(String::from(
317 "Invalid identity PEM file",
318 )))
319 })? {
320 rustls_pemfile::Item::X509Certificate(cert) => {
321 certs.push(rustls::Certificate(cert))
322 }
323 rustls_pemfile::Item::PKCS8Key(key) => sk.push(rustls::PrivateKey(key)),
324 rustls_pemfile::Item::RSAKey(key) => sk.push(rustls::PrivateKey(key)),
325 rustls_pemfile::Item::ECKey(key) => sk.push(rustls::PrivateKey(key)),
326 _ => {
327 return Err(crate::error::builder(TLSError::General(String::from(
328 "No valid certificate was found",
329 ))))
330 }
331 }
332 }
333
334 if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
335 (sk, certs)
336 } else {
337 return Err(crate::error::builder(TLSError::General(String::from(
338 "private key or certificate not found",
339 ))));
340 }
341 };
342
343 Ok(Identity {
344 inner: ClientCert::Pem { key, certs },
345 })
346 }
347
348 #[cfg(feature = "native-tls")]
349 pub(crate) fn add_to_native_tls(
350 self,
351 tls: &mut native_tls_crate::TlsConnectorBuilder,
352 ) -> crate::Result<()> {
353 match self.inner {
354 ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => {
355 tls.identity(id);
356 Ok(())
357 }
358 #[cfg(feature = "__rustls")]
359 ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")),
360 }
361 }
362
363 #[cfg(feature = "__rustls")]
364 pub(crate) fn add_to_rustls(
365 self,
366 config_builder: rustls::ConfigBuilder<
367 rustls::ClientConfig,
368 rustls::client::WantsTransparencyPolicyOrClientCert,
369 >,
370 ) -> crate::Result<rustls::ClientConfig> {
371 match self.inner {
372 ClientCert::Pem { key, certs } => config_builder
373 .with_client_auth_cert(certs, key)
374 .map_err(crate::error::builder),
375 #[cfg(feature = "native-tls")]
376 ClientCert::Pkcs12(..) | ClientCert::Pkcs8(..) => {
377 Err(crate::error::builder("incompatible TLS identity type"))
378 }
379 }
380 }
381}
382
383impl fmt::Debug for Certificate {
384 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
385 f.debug_struct(name:"Certificate").finish()
386 }
387}
388
389impl fmt::Debug for Identity {
390 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
391 f.debug_struct(name:"Identity").finish()
392 }
393}
394
395/// A TLS protocol version.
396#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
397pub struct Version(InnerVersion);
398
399#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
400#[non_exhaustive]
401enum InnerVersion {
402 Tls1_0,
403 Tls1_1,
404 Tls1_2,
405 Tls1_3,
406}
407
408// These could perhaps be From/TryFrom implementations, but those would be
409// part of the public API so let's be careful
410impl Version {
411 /// Version 1.0 of the TLS protocol.
412 pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0);
413 /// Version 1.1 of the TLS protocol.
414 pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1);
415 /// Version 1.2 of the TLS protocol.
416 pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2);
417 /// Version 1.3 of the TLS protocol.
418 pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3);
419
420 #[cfg(feature = "default-tls")]
421 pub(crate) fn to_native_tls(self) -> Option<native_tls_crate::Protocol> {
422 match self.0 {
423 InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10),
424 InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11),
425 InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12),
426 InnerVersion::Tls1_3 => None,
427 }
428 }
429
430 #[cfg(feature = "__rustls")]
431 pub(crate) fn from_rustls(version: rustls::ProtocolVersion) -> Option<Self> {
432 match version {
433 rustls::ProtocolVersion::SSLv2 => None,
434 rustls::ProtocolVersion::SSLv3 => None,
435 rustls::ProtocolVersion::TLSv1_0 => Some(Self(InnerVersion::Tls1_0)),
436 rustls::ProtocolVersion::TLSv1_1 => Some(Self(InnerVersion::Tls1_1)),
437 rustls::ProtocolVersion::TLSv1_2 => Some(Self(InnerVersion::Tls1_2)),
438 rustls::ProtocolVersion::TLSv1_3 => Some(Self(InnerVersion::Tls1_3)),
439 _ => None,
440 }
441 }
442}
443
444pub(crate) enum TlsBackend {
445 // This is the default and HTTP/3 feature does not use it so suppress it.
446 #[allow(dead_code)]
447 #[cfg(feature = "default-tls")]
448 Default,
449 #[cfg(feature = "native-tls")]
450 BuiltNativeTls(native_tls_crate::TlsConnector),
451 #[cfg(feature = "__rustls")]
452 Rustls,
453 #[cfg(feature = "__rustls")]
454 BuiltRustls(rustls::ClientConfig),
455 #[cfg(any(feature = "native-tls", feature = "__rustls",))]
456 UnknownPreconfigured,
457}
458
459impl fmt::Debug for TlsBackend {
460 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
461 match self {
462 #[cfg(feature = "default-tls")]
463 TlsBackend::Default => write!(f, "Default"),
464 #[cfg(feature = "native-tls")]
465 TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"),
466 #[cfg(feature = "__rustls")]
467 TlsBackend::Rustls => write!(f, "Rustls"),
468 #[cfg(feature = "__rustls")]
469 TlsBackend::BuiltRustls(_) => write!(f, "BuiltRustls"),
470 #[cfg(any(feature = "native-tls", feature = "__rustls",))]
471 TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"),
472 }
473 }
474}
475
476impl Default for TlsBackend {
477 fn default() -> TlsBackend {
478 #[cfg(all(feature = "default-tls", not(feature = "http3")))]
479 {
480 TlsBackend::Default
481 }
482
483 #[cfg(any(
484 all(feature = "__rustls", not(feature = "default-tls")),
485 feature = "http3"
486 ))]
487 {
488 TlsBackend::Rustls
489 }
490 }
491}
492
493#[cfg(feature = "__rustls")]
494pub(crate) struct NoVerifier;
495
496#[cfg(feature = "__rustls")]
497impl ServerCertVerifier for NoVerifier {
498 fn verify_server_cert(
499 &self,
500 _end_entity: &rustls::Certificate,
501 _intermediates: &[rustls::Certificate],
502 _server_name: &ServerName,
503 _scts: &mut dyn Iterator<Item = &[u8]>,
504 _ocsp_response: &[u8],
505 _now: std::time::SystemTime,
506 ) -> Result<ServerCertVerified, TLSError> {
507 Ok(ServerCertVerified::assertion())
508 }
509
510 fn verify_tls12_signature(
511 &self,
512 _message: &[u8],
513 _cert: &rustls::Certificate,
514 _dss: &DigitallySignedStruct,
515 ) -> Result<HandshakeSignatureValid, TLSError> {
516 Ok(HandshakeSignatureValid::assertion())
517 }
518
519 fn verify_tls13_signature(
520 &self,
521 _message: &[u8],
522 _cert: &rustls::Certificate,
523 _dss: &DigitallySignedStruct,
524 ) -> Result<HandshakeSignatureValid, TLSError> {
525 Ok(HandshakeSignatureValid::assertion())
526 }
527}
528
529/// Hyper extension carrying extra TLS layer information.
530/// Made available to clients on responses when `tls_info` is set.
531#[derive(Clone)]
532pub struct TlsInfo {
533 pub(crate) peer_certificate: Option<Vec<u8>>,
534}
535
536impl TlsInfo {
537 /// Get the DER encoded leaf certificate of the peer.
538 pub fn peer_certificate(&self) -> Option<&[u8]> {
539 self.peer_certificate.as_ref().map(|der: &Vec| &der[..])
540 }
541}
542
543impl std::fmt::Debug for TlsInfo {
544 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
545 f.debug_struct(name:"TlsInfo").finish()
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[cfg(feature = "default-tls")]
554 #[test]
555 fn certificate_from_der_invalid() {
556 Certificate::from_der(b"not der").unwrap_err();
557 }
558
559 #[cfg(feature = "default-tls")]
560 #[test]
561 fn certificate_from_pem_invalid() {
562 Certificate::from_pem(b"not pem").unwrap_err();
563 }
564
565 #[cfg(feature = "native-tls")]
566 #[test]
567 fn identity_from_pkcs12_der_invalid() {
568 Identity::from_pkcs12_der(b"not der", "nope").unwrap_err();
569 }
570
571 #[cfg(feature = "native-tls")]
572 #[test]
573 fn identity_from_pkcs8_pem_invalid() {
574 Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err();
575 }
576
577 #[cfg(feature = "__rustls")]
578 #[test]
579 fn identity_from_pem_invalid() {
580 Identity::from_pem(b"not pem").unwrap_err();
581 }
582
583 #[cfg(feature = "__rustls")]
584 #[test]
585 fn identity_from_pem_pkcs1_key() {
586 let pem = b"-----BEGIN CERTIFICATE-----\n\
587 -----END CERTIFICATE-----\n\
588 -----BEGIN RSA PRIVATE KEY-----\n\
589 -----END RSA PRIVATE KEY-----\n";
590
591 Identity::from_pem(pem).unwrap();
592 }
593
594 #[test]
595 fn certificates_from_pem_bundle() {
596 const PEM_BUNDLE: &[u8] = b"
597 -----BEGIN CERTIFICATE-----
598 MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
599 MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
600 Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
601 A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
602 Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
603 ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
604 QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
605 ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
606 BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
607 YyRIHN8wfdVoOw==
608 -----END CERTIFICATE-----
609
610 -----BEGIN CERTIFICATE-----
611 MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
612 MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
613 Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
614 A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
615 Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
616 9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
617 M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
618 /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
619 MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
620 CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
621 1KyLa2tJElMzrdfkviT8tQp21KW8EA==
622 -----END CERTIFICATE-----
623 ";
624
625 assert!(Certificate::from_pem_bundle(PEM_BUNDLE).is_ok())
626 }
627}
628