tlsn_core/
connection.rs

1//! TLS connection types.
2//!
3//! ## Commitment
4//!
5//! During the TLS handshake the Notary receives the Server's ephemeral public
6//! key, and this key serves as a binding commitment to the identity of the
7//! Server. The ephemeral key itself does not reveal the Server's identity, but
8//! it is bound to it via a signature created using the Server's
9//! X.509 certificate.
10//!
11//! A Prover can withhold the Server's signature and certificate chain from the
12//! Notary to improve privacy and censorship resistance.
13//!
14//! ## Proving the Server's identity
15//!
16//! A Prover can prove the Server's identity to a Verifier by sending a
17//! [`ServerIdentityProof`]. This proof contains all the information required to
18//! establish the link between the TLS connection and the Server's X.509
19//! certificate. A Verifier checks the Server's certificate against their own
20//! trust anchors, the same way a typical TLS client would.
21
22mod commit;
23mod proof;
24
25use std::fmt;
26
27use serde::{Deserialize, Serialize};
28use tls_core::{
29    msgs::{
30        codec::Codec,
31        enums::NamedGroup,
32        handshake::{DigitallySignedStruct, ServerECDHParams},
33    },
34    verify::ServerCertVerifier as _,
35};
36use web_time::{Duration, UNIX_EPOCH};
37
38use crate::{hash::impl_domain_separator, CryptoProvider};
39
40pub use commit::{ServerCertCommitment, ServerCertOpening};
41pub use proof::{ServerIdentityProof, ServerIdentityProofError};
42
43/// TLS version.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46pub enum TlsVersion {
47    /// TLS 1.2.
48    V1_2,
49    /// TLS 1.3.
50    V1_3,
51}
52
53impl TryFrom<tls_core::msgs::enums::ProtocolVersion> for TlsVersion {
54    type Error = &'static str;
55
56    fn try_from(value: tls_core::msgs::enums::ProtocolVersion) -> Result<Self, Self::Error> {
57        Ok(match value {
58            tls_core::msgs::enums::ProtocolVersion::TLSv1_2 => TlsVersion::V1_2,
59            tls_core::msgs::enums::ProtocolVersion::TLSv1_3 => TlsVersion::V1_3,
60            _ => return Err("unsupported TLS version"),
61        })
62    }
63}
64
65/// Server's name, a.k.a. the DNS name.
66#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub struct ServerName(String);
68
69impl ServerName {
70    /// Creates a new server name.
71    pub fn new(name: String) -> Self {
72        Self(name)
73    }
74
75    /// Returns the name as a string.
76    pub fn as_str(&self) -> &str {
77        &self.0
78    }
79}
80
81impl From<&str> for ServerName {
82    fn from(name: &str) -> Self {
83        Self(name.to_string())
84    }
85}
86
87impl AsRef<str> for ServerName {
88    fn as_ref(&self) -> &str {
89        &self.0
90    }
91}
92
93impl fmt::Display for ServerName {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{}", self.0)
96    }
97}
98
99/// Type of a public key.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
101#[serde(rename_all = "lowercase")]
102#[non_exhaustive]
103#[allow(non_camel_case_types)]
104pub enum KeyType {
105    /// secp256r1.
106    SECP256R1 = 0x0017,
107}
108
109/// Signature scheme on the key exchange parameters.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
111#[serde(rename_all = "lowercase")]
112#[allow(non_camel_case_types, missing_docs)]
113pub enum SignatureScheme {
114    RSA_PKCS1_SHA1 = 0x0201,
115    ECDSA_SHA1_Legacy = 0x0203,
116    RSA_PKCS1_SHA256 = 0x0401,
117    ECDSA_NISTP256_SHA256 = 0x0403,
118    RSA_PKCS1_SHA384 = 0x0501,
119    ECDSA_NISTP384_SHA384 = 0x0503,
120    RSA_PKCS1_SHA512 = 0x0601,
121    ECDSA_NISTP521_SHA512 = 0x0603,
122    RSA_PSS_SHA256 = 0x0804,
123    RSA_PSS_SHA384 = 0x0805,
124    RSA_PSS_SHA512 = 0x0806,
125    ED25519 = 0x0807,
126}
127
128impl TryFrom<tls_core::msgs::enums::SignatureScheme> for SignatureScheme {
129    type Error = &'static str;
130
131    fn try_from(value: tls_core::msgs::enums::SignatureScheme) -> Result<Self, Self::Error> {
132        use tls_core::msgs::enums::SignatureScheme as Core;
133        use SignatureScheme::*;
134        Ok(match value {
135            Core::RSA_PKCS1_SHA1 => RSA_PKCS1_SHA1,
136            Core::ECDSA_SHA1_Legacy => ECDSA_SHA1_Legacy,
137            Core::RSA_PKCS1_SHA256 => RSA_PKCS1_SHA256,
138            Core::ECDSA_NISTP256_SHA256 => ECDSA_NISTP256_SHA256,
139            Core::RSA_PKCS1_SHA384 => RSA_PKCS1_SHA384,
140            Core::ECDSA_NISTP384_SHA384 => ECDSA_NISTP384_SHA384,
141            Core::RSA_PKCS1_SHA512 => RSA_PKCS1_SHA512,
142            Core::ECDSA_NISTP521_SHA512 => ECDSA_NISTP521_SHA512,
143            Core::RSA_PSS_SHA256 => RSA_PSS_SHA256,
144            Core::RSA_PSS_SHA384 => RSA_PSS_SHA384,
145            Core::RSA_PSS_SHA512 => RSA_PSS_SHA512,
146            Core::ED25519 => ED25519,
147            _ => return Err("unsupported signature scheme"),
148        })
149    }
150}
151
152impl From<SignatureScheme> for tls_core::msgs::enums::SignatureScheme {
153    fn from(value: SignatureScheme) -> Self {
154        use tls_core::msgs::enums::SignatureScheme::*;
155        match value {
156            SignatureScheme::RSA_PKCS1_SHA1 => RSA_PKCS1_SHA1,
157            SignatureScheme::ECDSA_SHA1_Legacy => ECDSA_SHA1_Legacy,
158            SignatureScheme::RSA_PKCS1_SHA256 => RSA_PKCS1_SHA256,
159            SignatureScheme::ECDSA_NISTP256_SHA256 => ECDSA_NISTP256_SHA256,
160            SignatureScheme::RSA_PKCS1_SHA384 => RSA_PKCS1_SHA384,
161            SignatureScheme::ECDSA_NISTP384_SHA384 => ECDSA_NISTP384_SHA384,
162            SignatureScheme::RSA_PKCS1_SHA512 => RSA_PKCS1_SHA512,
163            SignatureScheme::ECDSA_NISTP521_SHA512 => ECDSA_NISTP521_SHA512,
164            SignatureScheme::RSA_PSS_SHA256 => RSA_PSS_SHA256,
165            SignatureScheme::RSA_PSS_SHA384 => RSA_PSS_SHA384,
166            SignatureScheme::RSA_PSS_SHA512 => RSA_PSS_SHA512,
167            SignatureScheme::ED25519 => ED25519,
168        }
169    }
170}
171
172/// X.509 certificate, DER encoded.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct Certificate(pub Vec<u8>);
175
176impl From<tls_core::key::Certificate> for Certificate {
177    fn from(cert: tls_core::key::Certificate) -> Self {
178        Self(cert.0)
179    }
180}
181
182/// Server's signature of the key exchange parameters.
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct ServerSignature {
185    /// Signature scheme.
186    pub scheme: SignatureScheme,
187    /// Signature data.
188    pub sig: Vec<u8>,
189}
190
191/// Server's ephemeral public key.
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193pub struct ServerEphemKey {
194    /// Type of the public key.
195    #[serde(rename = "type")]
196    pub typ: KeyType,
197    /// Public key data.
198    pub key: Vec<u8>,
199}
200
201impl_domain_separator!(ServerEphemKey);
202
203impl ServerEphemKey {
204    /// Encodes the key exchange parameters as in TLS.
205    pub(crate) fn kx_params(&self) -> Vec<u8> {
206        let group = match self.typ {
207            KeyType::SECP256R1 => NamedGroup::secp256r1,
208        };
209
210        let mut kx_params = Vec::new();
211        ServerECDHParams::new(group, &self.key).encode(&mut kx_params);
212
213        kx_params
214    }
215}
216
217impl TryFrom<tls_core::key::PublicKey> for ServerEphemKey {
218    type Error = &'static str;
219
220    fn try_from(value: tls_core::key::PublicKey) -> Result<Self, Self::Error> {
221        let tls_core::msgs::enums::NamedGroup::secp256r1 = value.group else {
222            return Err("unsupported key type");
223        };
224
225        Ok(ServerEphemKey {
226            typ: KeyType::SECP256R1,
227            key: value.key,
228        })
229    }
230}
231
232/// TLS session information.
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct ConnectionInfo {
235    /// UNIX time when the TLS connection started.
236    pub time: u64,
237    /// TLS version used in the connection.
238    pub version: TlsVersion,
239    /// Transcript length.
240    pub transcript_length: TranscriptLength,
241}
242
243impl_domain_separator!(ConnectionInfo);
244
245/// Transcript length information.
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247pub struct TranscriptLength {
248    /// Number of bytes sent by the Prover to the Server.
249    pub sent: u32,
250    /// Number of bytes received by the Prover from the Server.
251    pub received: u32,
252}
253
254/// TLS 1.2 handshake data.
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct HandshakeDataV1_2 {
257    /// Client random.
258    pub client_random: [u8; 32],
259    /// Server random.
260    pub server_random: [u8; 32],
261    /// Server's ephemeral public key.
262    pub server_ephemeral_key: ServerEphemKey,
263}
264
265/// TLS handshake data.
266#[derive(Debug, Clone, Serialize, Deserialize)]
267#[serde(rename_all = "snake_case")]
268#[non_exhaustive]
269pub enum HandshakeData {
270    /// TLS 1.2 handshake data.
271    V1_2(HandshakeDataV1_2),
272}
273
274impl_domain_separator!(HandshakeData);
275
276/// Server certificate and handshake data.
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct ServerCertData {
279    /// Certificate chain.
280    pub certs: Vec<Certificate>,
281    /// Server signature of the key exchange parameters.
282    pub sig: ServerSignature,
283    /// TLS handshake data.
284    pub handshake: HandshakeData,
285}
286
287impl ServerCertData {
288    /// Verifies the server certificate data.
289    ///
290    /// # Arguments
291    ///
292    /// * `provider` - The crypto provider to use for verification.
293    /// * `time` - The time of the connection.
294    /// * `server_ephemeral_key` - The server's ephemeral key.
295    /// * `server_name` - The server name.
296    pub fn verify_with_provider(
297        &self,
298        provider: &CryptoProvider,
299        time: u64,
300        server_ephemeral_key: &ServerEphemKey,
301        server_name: &ServerName,
302    ) -> Result<(), CertificateVerificationError> {
303        #[allow(irrefutable_let_patterns)]
304        let HandshakeData::V1_2(HandshakeDataV1_2 {
305            client_random,
306            server_random,
307            server_ephemeral_key: expected_server_ephemeral_key,
308        }) = &self.handshake
309        else {
310            unreachable!("only TLS 1.2 is implemented")
311        };
312
313        if server_ephemeral_key != expected_server_ephemeral_key {
314            return Err(CertificateVerificationError::InvalidServerEphemeralKey);
315        }
316
317        // Verify server name.
318        let server_name = tls_core::dns::ServerName::try_from(server_name.as_ref())
319            .map_err(|_| CertificateVerificationError::InvalidIdentity(server_name.clone()))?;
320
321        // Verify server certificate.
322        let cert_chain = self
323            .certs
324            .clone()
325            .into_iter()
326            .map(|cert| tls_core::key::Certificate(cert.0))
327            .collect::<Vec<_>>();
328
329        let (end_entity, intermediates) = cert_chain
330            .split_first()
331            .ok_or(CertificateVerificationError::MissingCerts)?;
332
333        // Verify the end entity cert is valid for the provided server name
334        // and that it chains to at least one of the roots we trust.
335        provider
336            .cert
337            .verify_server_cert(
338                end_entity,
339                intermediates,
340                &server_name,
341                &mut [].into_iter(),
342                &[],
343                UNIX_EPOCH + Duration::from_secs(time),
344            )
345            .map_err(|_| CertificateVerificationError::InvalidCert)?;
346
347        // Verify the signature matches the certificate and key exchange parameters.
348        let mut message = Vec::new();
349        message.extend_from_slice(client_random);
350        message.extend_from_slice(server_random);
351        message.extend_from_slice(&server_ephemeral_key.kx_params());
352
353        let dss = DigitallySignedStruct::new(self.sig.scheme.into(), self.sig.sig.clone());
354
355        provider
356            .cert
357            .verify_tls12_signature(&message, end_entity, &dss)
358            .map_err(|_| CertificateVerificationError::InvalidServerSignature)?;
359
360        Ok(())
361    }
362}
363
364/// Errors that can occur when verifying a certificate chain or signature.
365#[derive(Debug, thiserror::Error)]
366#[allow(missing_docs)]
367pub enum CertificateVerificationError {
368    #[error("invalid server identity: {0}")]
369    InvalidIdentity(ServerName),
370    #[error("missing server certificates")]
371    MissingCerts,
372    #[error("invalid server certificate")]
373    InvalidCert,
374    #[error("invalid server signature")]
375    InvalidServerSignature,
376    #[error("invalid server ephemeral key")]
377    InvalidServerEphemeralKey,
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    use crate::{
384        fixtures::ConnectionFixture, provider::default_cert_verifier, transcript::Transcript,
385    };
386
387    use hex::FromHex;
388    use rstest::*;
389    use tls_core::verify::WebPkiVerifier;
390    use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
391
392    #[fixture]
393    #[once]
394    fn crypto_provider() -> CryptoProvider {
395        let mut store = default_cert_verifier().root_store().clone();
396
397        // Add a cert which is no longer included in the Mozilla root store.
398        let cert = tls_core::key::Certificate(
399            appliedzkp()
400                .server_cert_data
401                .certs
402                .last()
403                .expect("chain is valid")
404                .0
405                .clone(),
406        );
407
408        store.add(&cert).unwrap();
409
410        CryptoProvider {
411            hash: Default::default(),
412            cert: WebPkiVerifier::new(store, None),
413            signer: Default::default(),
414            signature: Default::default(),
415        }
416    }
417
418    fn tlsnotary() -> ConnectionFixture {
419        ConnectionFixture::tlsnotary(Transcript::new(GET_WITH_HEADER, OK_JSON).length())
420    }
421
422    fn appliedzkp() -> ConnectionFixture {
423        ConnectionFixture::appliedzkp(Transcript::new(GET_WITH_HEADER, OK_JSON).length())
424    }
425
426    /// Expect chain verification to succeed.
427    #[rstest]
428    #[case::tlsnotary(tlsnotary())]
429    #[case::appliedzkp(appliedzkp())]
430    fn test_verify_cert_chain_sucess_ca_implicit(
431        crypto_provider: &CryptoProvider,
432        #[case] mut data: ConnectionFixture,
433    ) {
434        // Remove the CA cert
435        data.server_cert_data.certs.pop();
436
437        assert!(data
438            .server_cert_data
439            .verify_with_provider(
440                crypto_provider,
441                data.connection_info.time,
442                data.server_ephemeral_key(),
443                &ServerName::from(data.server_name.as_ref()),
444            )
445            .is_ok());
446    }
447
448    /// Expect chain verification to succeed even when a trusted CA is provided
449    /// among the intermediate certs. webpki handles such cases properly.
450    #[rstest]
451    #[case::tlsnotary(tlsnotary())]
452    #[case::appliedzkp(appliedzkp())]
453    fn test_verify_cert_chain_success_ca_explicit(
454        crypto_provider: &CryptoProvider,
455        #[case] data: ConnectionFixture,
456    ) {
457        assert!(data
458            .server_cert_data
459            .verify_with_provider(
460                crypto_provider,
461                data.connection_info.time,
462                data.server_ephemeral_key(),
463                &ServerName::from(data.server_name.as_ref()),
464            )
465            .is_ok());
466    }
467
468    /// Expect to fail since the end entity cert was not valid at the time.
469    #[rstest]
470    #[case::tlsnotary(tlsnotary())]
471    #[case::appliedzkp(appliedzkp())]
472    fn test_verify_cert_chain_fail_bad_time(
473        crypto_provider: &CryptoProvider,
474        #[case] data: ConnectionFixture,
475    ) {
476        // unix time when the cert chain was NOT valid
477        let bad_time: u64 = 1571465711;
478
479        let err = data.server_cert_data.verify_with_provider(
480            crypto_provider,
481            bad_time,
482            data.server_ephemeral_key(),
483            &ServerName::from(data.server_name.as_ref()),
484        );
485
486        assert!(matches!(
487            err.unwrap_err(),
488            CertificateVerificationError::InvalidCert
489        ));
490    }
491
492    /// Expect to fail when no intermediate cert provided.
493    #[rstest]
494    #[case::tlsnotary(tlsnotary())]
495    #[case::appliedzkp(appliedzkp())]
496    fn test_verify_cert_chain_fail_no_interm_cert(
497        crypto_provider: &CryptoProvider,
498        #[case] mut data: ConnectionFixture,
499    ) {
500        // Remove the CA cert
501        data.server_cert_data.certs.pop();
502        // Remove the intermediate cert
503        data.server_cert_data.certs.pop();
504
505        let err = data.server_cert_data.verify_with_provider(
506            crypto_provider,
507            data.connection_info.time,
508            data.server_ephemeral_key(),
509            &ServerName::from(data.server_name.as_ref()),
510        );
511
512        assert!(matches!(
513            err.unwrap_err(),
514            CertificateVerificationError::InvalidCert
515        ));
516    }
517
518    /// Expect to fail when no intermediate cert provided even if a trusted CA
519    /// cert is provided.
520    #[rstest]
521    #[case::tlsnotary(tlsnotary())]
522    #[case::appliedzkp(appliedzkp())]
523    fn test_verify_cert_chain_fail_no_interm_cert_with_ca_cert(
524        crypto_provider: &CryptoProvider,
525        #[case] mut data: ConnectionFixture,
526    ) {
527        // Remove the intermediate cert
528        data.server_cert_data.certs.remove(1);
529
530        let err = data.server_cert_data.verify_with_provider(
531            crypto_provider,
532            data.connection_info.time,
533            data.server_ephemeral_key(),
534            &ServerName::from(data.server_name.as_ref()),
535        );
536
537        assert!(matches!(
538            err.unwrap_err(),
539            CertificateVerificationError::InvalidCert
540        ));
541    }
542
543    /// Expect to fail because end-entity cert is wrong.
544    #[rstest]
545    #[case::tlsnotary(tlsnotary())]
546    #[case::appliedzkp(appliedzkp())]
547    fn test_verify_cert_chain_fail_bad_ee_cert(
548        crypto_provider: &CryptoProvider,
549        #[case] mut data: ConnectionFixture,
550    ) {
551        let ee: &[u8] = include_bytes!("./fixtures/data/unknown/ee.der");
552
553        // Change the end entity cert
554        data.server_cert_data.certs[0] = Certificate(ee.to_vec());
555
556        let err = data.server_cert_data.verify_with_provider(
557            crypto_provider,
558            data.connection_info.time,
559            data.server_ephemeral_key(),
560            &ServerName::from(data.server_name.as_ref()),
561        );
562
563        assert!(matches!(
564            err.unwrap_err(),
565            CertificateVerificationError::InvalidCert
566        ));
567    }
568
569    /// Expect sig verification to fail because client_random is wrong.
570    #[rstest]
571    #[case::tlsnotary(tlsnotary())]
572    #[case::appliedzkp(appliedzkp())]
573    fn test_verify_sig_ke_params_fail_bad_client_random(
574        crypto_provider: &CryptoProvider,
575        #[case] mut data: ConnectionFixture,
576    ) {
577        let HandshakeData::V1_2(HandshakeDataV1_2 { client_random, .. }) =
578            &mut data.server_cert_data.handshake;
579        client_random[31] = client_random[31].wrapping_add(1);
580
581        let err = data.server_cert_data.verify_with_provider(
582            crypto_provider,
583            data.connection_info.time,
584            data.server_ephemeral_key(),
585            &ServerName::from(data.server_name.as_ref()),
586        );
587
588        assert!(matches!(
589            err.unwrap_err(),
590            CertificateVerificationError::InvalidServerSignature
591        ));
592    }
593
594    /// Expect sig verification to fail because the sig is wrong.
595    #[rstest]
596    #[case::tlsnotary(tlsnotary())]
597    #[case::appliedzkp(appliedzkp())]
598    fn test_verify_sig_ke_params_fail_bad_sig(
599        crypto_provider: &CryptoProvider,
600        #[case] mut data: ConnectionFixture,
601    ) {
602        data.server_cert_data.sig.sig[31] = data.server_cert_data.sig.sig[31].wrapping_add(1);
603
604        let err = data.server_cert_data.verify_with_provider(
605            crypto_provider,
606            data.connection_info.time,
607            data.server_ephemeral_key(),
608            &ServerName::from(data.server_name.as_ref()),
609        );
610
611        assert!(matches!(
612            err.unwrap_err(),
613            CertificateVerificationError::InvalidServerSignature
614        ));
615    }
616
617    /// Expect to fail because the dns name is not in the cert.
618    #[rstest]
619    #[case::tlsnotary(tlsnotary())]
620    #[case::appliedzkp(appliedzkp())]
621    fn test_check_dns_name_present_in_cert_fail_bad_host(
622        crypto_provider: &CryptoProvider,
623        #[case] data: ConnectionFixture,
624    ) {
625        let bad_name = ServerName::from("badhost.com");
626
627        let err = data.server_cert_data.verify_with_provider(
628            crypto_provider,
629            data.connection_info.time,
630            data.server_ephemeral_key(),
631            &bad_name,
632        );
633
634        assert!(matches!(
635            err.unwrap_err(),
636            CertificateVerificationError::InvalidCert
637        ));
638    }
639
640    /// Expect to fail because the ephemeral key provided is wrong.
641    #[rstest]
642    #[case::tlsnotary(tlsnotary())]
643    #[case::appliedzkp(appliedzkp())]
644    fn test_invalid_ephemeral_key(
645        crypto_provider: &CryptoProvider,
646        #[case] data: ConnectionFixture,
647    ) {
648        let wrong_ephemeral_key = ServerEphemKey {
649            typ: KeyType::SECP256R1,
650            key: Vec::<u8>::from_hex(include_bytes!("./fixtures/data/unknown/pubkey")).unwrap(),
651        };
652
653        let err = data.server_cert_data.verify_with_provider(
654            crypto_provider,
655            data.connection_info.time,
656            &wrong_ephemeral_key,
657            &ServerName::from(data.server_name.as_ref()),
658        );
659
660        assert!(matches!(
661            err.unwrap_err(),
662            CertificateVerificationError::InvalidServerEphemeralKey
663        ));
664    }
665
666    /// Expect to fail when no cert provided.
667    #[rstest]
668    #[case::tlsnotary(tlsnotary())]
669    #[case::appliedzkp(appliedzkp())]
670    fn test_verify_cert_chain_fail_no_cert(
671        crypto_provider: &CryptoProvider,
672        #[case] mut data: ConnectionFixture,
673    ) {
674        // Empty certs
675        data.server_cert_data.certs = Vec::new();
676
677        let err = data.server_cert_data.verify_with_provider(
678            crypto_provider,
679            data.connection_info.time,
680            data.server_ephemeral_key(),
681            &ServerName::from(data.server_name.as_ref()),
682        );
683
684        assert!(matches!(
685            err.unwrap_err(),
686            CertificateVerificationError::MissingCerts
687        ));
688    }
689}