Skip to main content
This is unreleased documentation for the main (development) branch of crypto-glue.

crypto_glue/x509/
chain.rs

1use crate::x509::{oiddb::rfc5912, AlgorithmIdentifier, BasicConstraints, Certificate, KeyUsage};
2use crate::{
3    ecdsa_p256::{EcdsaP256DerSignature, EcdsaP256PublicKey, EcdsaP256VerifyingKey},
4    ecdsa_p384::{EcdsaP384DerSignature, EcdsaP384PublicKey, EcdsaP384VerifyingKey},
5    rsa::{RS256PublicKey, RS256Signature, RS256VerifyingKey},
6    s256::{Sha256, Sha256Output},
7    traits::{hazmat::PrehashVerifier, Digest, Verifier},
8};
9use der::referenced::OwnedToRef;
10use der::Encode;
11use std::time::{Duration, SystemTime};
12use tracing::{debug, error, trace, warn};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum X509VerificationError {
16    InvalidSystemTime,
17    BasicConstraintsNotPresent,
18    LeafMustNotBeCA,
19    KeyUsageNotValid,
20    ExtensionFailure,
21    NotBefore,
22    NotAfter,
23    NoMatchingIssuer,
24    InvalidIssuer,
25    ExcessivePathLength,
26    CaNotMarkedAsSuch,
27    PathLengthExceeded,
28    SignatureAlgorithmMismatch,
29    SignatureAlgorithmNotImplemented,
30    DerSignatureInvalid,
31    VerifyingKeyFromSpki,
32    SignatureVerificationFailed,
33    CertificateSerialisation,
34    KeyUsageNotPresent,
35    SubjectPublicKeyInformationInvalid,
36}
37
38pub struct X509Store {
39    store: Vec<Certificate>,
40    leaf_basic_constraints_required: bool,
41}
42
43impl X509Store {
44    pub fn new(ca_roots: &[Certificate]) -> Self {
45        Self {
46            store: ca_roots.iter().map(|c| (*c).clone()).collect(),
47            leaf_basic_constraints_required: true,
48        }
49    }
50
51    pub fn leaf_basic_constraints_required(&mut self, enable: bool) {
52        self.leaf_basic_constraints_required = enable;
53    }
54
55    pub fn verify(
56        &self,
57        leaf: &Certificate,
58        intermediates: &[Certificate],
59        current_time: SystemTime,
60    ) -> Result<&Certificate, X509VerificationError> {
61        // To verify this, we need to get the "rightmost" certificate that we then
62        // check is valid wrt to our store.
63        //
64        // Our caller has passed in:
65        //
66        // [ leaf, inter, inter, ... ]
67        //
68        // where the signing flows right to left
69        //
70        // [ leaf <- inter <- inter, ... ]
71        //
72        // So the initial stage is to validate the intermediate chain, and determine
73        // the intermediate closest to the root.
74
75        let current_time_unix = current_time
76            .duration_since(SystemTime::UNIX_EPOCH)
77            .map_err(|_| X509VerificationError::InvalidSystemTime)?;
78
79        let mut certificate_to_validate = leaf;
80        self.validate_leaf(certificate_to_validate, current_time_unix)?;
81
82        // PATH LENGTH
83        let mut path_length = 0;
84
85        for intermediate in intermediates {
86            // validate that intermediate signed the current certificate we
87            // are scrutinising.
88
89            self.validate_pair(
90                certificate_to_validate,
91                intermediate,
92                current_time_unix,
93                path_length,
94            )?;
95
96            // If it was valid, we now need to check the intermediate next.
97            certificate_to_validate = intermediate;
98
99            // The path length now increments by one as we added a CA to the path.
100            path_length = path_length
101                .checked_add(1)
102                .ok_or(X509VerificationError::ExcessivePathLength)?;
103        }
104
105        // Now, the certificate_to_validate is positioned. We have either validated
106        // the chain of intermediates to the leaf, or the leaf was the only certificate
107        // present.
108
109        // At this point, we now can check that our ca_store actually contains
110        // something that validates this certificate.
111
112        let authority_cert = self.locate_authority_certificate(certificate_to_validate)?;
113
114        self.validate_pair(
115            certificate_to_validate,
116            authority_cert,
117            current_time_unix,
118            path_length,
119        )?;
120
121        // At this point we have established the chain back to the CA is valid along
122        // the path of intermediates.
123
124        // That's it! Return the CA that ultimately signed this chain.
125        Ok(authority_cert)
126    }
127
128    fn validate_leaf(
129        &self,
130        certificate_to_validate: &Certificate,
131        current_time: Duration,
132    ) -> Result<(), X509VerificationError> {
133        // Client Leaf Cert
134        //   Basic Constraints: critical
135        //     CA:FALSE
136        let maybe_basic_constraints = certificate_to_validate
137            .tbs_certificate
138            .get::<BasicConstraints>()
139            .map_err(|_err| X509VerificationError::ExtensionFailure)?;
140        // If not present, we act as if this is not a CA.
141        // .ok_or(
142
143        if let Some((_critical, basic_constraints)) = maybe_basic_constraints {
144            if basic_constraints.ca {
145                return Err(X509VerificationError::LeafMustNotBeCA);
146            }
147        } else if self.leaf_basic_constraints_required {
148            return Err(X509VerificationError::BasicConstraintsNotPresent);
149        };
150
151        let maybe_keyusage = certificate_to_validate
152            .tbs_certificate
153            .get::<KeyUsage>()
154            .map_err(|_err| X509VerificationError::ExtensionFailure)?;
155
156        if let Some((_critical, key_usage)) = maybe_keyusage {
157            //   Key Usage: critical
158            //     Digital Signature
159            if !key_usage.digital_signature() {
160                return Err(X509VerificationError::KeyUsageNotValid);
161            }
162        }
163
164        // Valid time range.
165        let not_before = certificate_to_validate
166            .tbs_certificate
167            .validity
168            .not_before
169            .to_unix_duration();
170
171        if not_before > current_time {
172            trace!(?not_before, ?current_time);
173            return Err(X509VerificationError::NotBefore);
174        }
175
176        let not_after = certificate_to_validate
177            .tbs_certificate
178            .validity
179            .not_after
180            .to_unix_duration();
181
182        if current_time > not_after {
183            debug!(?current_time, ?not_after);
184            return Err(X509VerificationError::NotAfter);
185        }
186
187        Ok(())
188    }
189
190    fn validate_pair(
191        &self,
192        certificate_to_validate: &Certificate,
193        authority: &Certificate,
194        current_time: Duration,
195        path_length: u8,
196    ) -> Result<(), X509VerificationError> {
197        if authority.tbs_certificate.subject != certificate_to_validate.tbs_certificate.issuer {
198            return Err(X509VerificationError::InvalidIssuer);
199        }
200
201        // Intermediate:
202        //   Basic Constraints: critical
203        //     CA:TRUE
204        //     pathlen:0  // indicates no subordinate CA's
205
206        let (_critical, basic_constraints) = authority
207            .tbs_certificate
208            .get::<BasicConstraints>()
209            .map_err(|_err| X509VerificationError::ExtensionFailure)?
210            // You are a CA, you must have this.
211            .ok_or(X509VerificationError::BasicConstraintsNotPresent)?;
212
213        if !basic_constraints.ca {
214            return Err(X509VerificationError::CaNotMarkedAsSuch);
215        }
216
217        if let Some(ca_pathlen) = basic_constraints.path_len_constraint {
218            // The current depth of the validation path exceeds that of the
219            // allowed path length of the certificate.
220            if path_length > ca_pathlen {
221                return Err(X509VerificationError::PathLengthExceeded);
222            }
223        }
224
225        let (_critical, key_usage) = authority
226            .tbs_certificate
227            .get::<KeyUsage>()
228            .map_err(|_err| X509VerificationError::ExtensionFailure)?
229            .ok_or(X509VerificationError::KeyUsageNotPresent)?;
230
231        if !key_usage.key_cert_sign() {
232            return Err(X509VerificationError::KeyUsageNotValid);
233        }
234
235        // Valid time range.
236        let not_before = authority
237            .tbs_certificate
238            .validity
239            .not_before
240            .to_unix_duration();
241
242        if not_before > current_time {
243            return Err(X509VerificationError::NotBefore);
244        }
245
246        let not_after = authority
247            .tbs_certificate
248            .validity
249            .not_after
250            .to_unix_duration();
251
252        if current_time > not_after {
253            return Err(X509VerificationError::NotAfter);
254        }
255
256        // Now validate the signature of the certificate_to_validate
257        // A reasonable person would assume that a CA can only issue certificates using the same
258        // algorithm that it declares that it uses. However, that's simply just false, some
259        // CA's, especially that use RSA, may use a different digest.
260        if certificate_to_validate.signature_algorithm != authority.tbs_certificate.signature {
261            warn!(?certificate_to_validate.signature_algorithm, ?authority.tbs_certificate.signature);
262            // return Err(X509VerificationError::SignatureAlgorithmMismatch);
263        }
264
265        let cert_to_validate_data = certificate_to_validate
266            .tbs_certificate
267            .to_der()
268            .map_err(|_err| X509VerificationError::CertificateSerialisation)?;
269
270        let cert_to_validate_signature = certificate_to_validate
271            .signature
272            .as_bytes()
273            .ok_or(X509VerificationError::DerSignatureInvalid)?;
274
275        verify_der_signature(
276            &cert_to_validate_data,
277            cert_to_validate_signature,
278            &certificate_to_validate.signature_algorithm,
279            authority,
280        )?;
281
282        Ok(())
283    }
284
285    fn locate_authority_certificate(
286        &self,
287        certificate_to_validate: &Certificate,
288    ) -> Result<&Certificate, X509VerificationError> {
289        self.store
290            .iter()
291            .find(|ca_cert| {
292                ca_cert.tbs_certificate.subject == certificate_to_validate.tbs_certificate.issuer
293            })
294            .ok_or(X509VerificationError::NoMatchingIssuer)
295    }
296}
297
298// We can't use the generic x509_verify_signature here because the format
299// of these signatures within an x509 cert is DER and different than the
300// "generic" signatures you may get on something like a JWT
301fn verify_der_signature(
302    data: &[u8],
303    signature: &[u8],
304    signature_algorithm: &AlgorithmIdentifier<der::Any>,
305    certificate: &Certificate,
306) -> Result<(), X509VerificationError> {
307    let subject_public_key_info = certificate
308        .tbs_certificate
309        .subject_public_key_info
310        .owned_to_ref();
311
312    let (spki_alg_oid, spki_alg_params) = subject_public_key_info
313        .algorithm
314        .oids()
315        .map_err(|_| X509VerificationError::SubjectPublicKeyInformationInvalid)?;
316
317    trace!(?signature_algorithm.oid, ?spki_alg_oid, ?spki_alg_params);
318
319    match (signature_algorithm.oid, spki_alg_oid, spki_alg_params) {
320        (rfc5912::ECDSA_WITH_SHA_256, rfc5912::ID_EC_PUBLIC_KEY, Some(rfc5912::SECP_256_R_1)) => {
321            let signature = EcdsaP256DerSignature::try_from(signature).map_err(|err| {
322                error!(?err);
323                X509VerificationError::DerSignatureInvalid
324            })?;
325
326            let verifier = EcdsaP256PublicKey::try_from(subject_public_key_info)
327                .map(EcdsaP256VerifyingKey::from)
328                .map_err(|_err| X509VerificationError::VerifyingKeyFromSpki)?;
329
330            verifier
331                .verify(data, &signature)
332                .map_err(|_err| X509VerificationError::SignatureVerificationFailed)?;
333        }
334        (rfc5912::ECDSA_WITH_SHA_256, rfc5912::ID_EC_PUBLIC_KEY, Some(rfc5912::SECP_384_R_1)) => {
335            let signature = EcdsaP384DerSignature::try_from(signature)
336                .map_err(|_err| X509VerificationError::DerSignatureInvalid)?;
337
338            let verifier = EcdsaP384PublicKey::try_from(subject_public_key_info)
339                .map(EcdsaP384VerifyingKey::from)
340                .map_err(|_err| X509VerificationError::VerifyingKeyFromSpki)?;
341
342            // let
343            let mut hasher = Sha256::new();
344            hasher.update(data);
345            let out: Sha256Output = hasher.finalize();
346
347            verifier
348                .verify_prehash(out.as_slice(), &signature)
349                .map_err(|_err| X509VerificationError::SignatureVerificationFailed)?;
350        }
351        (rfc5912::ECDSA_WITH_SHA_384, rfc5912::ID_EC_PUBLIC_KEY, Some(rfc5912::SECP_384_R_1)) => {
352            let signature = EcdsaP384DerSignature::try_from(signature)
353                .map_err(|_err| X509VerificationError::DerSignatureInvalid)?;
354
355            let verifier = EcdsaP384PublicKey::try_from(subject_public_key_info)
356                .map(EcdsaP384VerifyingKey::from)
357                .map_err(|_err| X509VerificationError::VerifyingKeyFromSpki)?;
358
359            verifier
360                .verify(data, &signature)
361                .map_err(|_err| X509VerificationError::SignatureVerificationFailed)?;
362        }
363        (rfc5912::SHA_256_WITH_RSA_ENCRYPTION, rfc5912::RSA_ENCRYPTION, None) => {
364            let signature = RS256Signature::try_from(signature)
365                .map_err(|_err| X509VerificationError::DerSignatureInvalid)?;
366
367            let verifier = RS256PublicKey::try_from(subject_public_key_info)
368                .map(RS256VerifyingKey::new)
369                .map_err(|_err| X509VerificationError::VerifyingKeyFromSpki)?;
370
371            verifier
372                .verify(data, &signature)
373                .map_err(|_err| X509VerificationError::SignatureVerificationFailed)?;
374        }
375        (signature_algorithm_oid, spki_alg_oid, spki_alg_params) => {
376            error!(?signature_algorithm_oid, ?spki_alg_oid, ?spki_alg_params);
377            return Err(X509VerificationError::SignatureAlgorithmNotImplemented);
378        }
379    }
380
381    Ok(())
382}
383
384#[cfg(test)]
385mod tests {
386    use super::X509Store;
387    use crate::ecdsa_p384::{
388        EcdsaP384PublicKey,
389        // EcdsaP384DerSignature,
390        EcdsaP384Signature,
391        EcdsaP384VerifyingKey,
392    };
393    use crate::test_ca::*;
394    use crate::traits::{DecodePem, Signer, Verifier};
395    use crate::x509::{Certificate, Name, Time, X509Display};
396    use der::referenced::OwnedToRef;
397    use std::str::FromStr;
398    use std::time::Duration;
399    use std::time::SystemTime;
400
401    #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
402    use wasm_bindgen_test::*;
403    #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
404    wasm_bindgen_test_configure!(run_in_browser);
405
406    #[test]
407    #[cfg_attr(
408        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
409        wasm_bindgen_test
410    )]
411    fn x509_chain_verify_basic() {
412        let _ = tracing_subscriber::fmt::try_init();
413
414        let now = now();
415        let not_before = Time::try_from(now).expect("Failed to convert SystemTime to Time");
416        let not_after = Time::try_from(now + Duration::new(3600, 0))
417            .expect("Failed to convert SystemTime to Time");
418
419        let (root_signing_key, root_ca_cert) = build_test_ca_root(not_before, not_after);
420
421        let (int_signing_key, int_ca_cert) =
422            build_test_ca_int(not_before, not_after, &root_signing_key, &root_ca_cert);
423
424        let subject = Name::from_str("CN=localhost").expect("Failed to parse subject name");
425        let (server_key, server_csr) = build_test_csr(&subject);
426
427        let server_cert = test_ca_sign_server_csr(
428            not_before,
429            not_after,
430            &server_csr,
431            &int_signing_key,
432            &int_ca_cert,
433        );
434
435        tracing::debug!(cert = %X509Display::from(&server_cert));
436
437        // Also sign some data to validate.
438        let test_data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
439
440        let test_data_signature: EcdsaP384Signature = server_key
441            .try_sign(&test_data)
442            // .map(|sig: EcdsaP384Signature | sig.to_der())
443            .expect("Unable to sign test data");
444
445        // Certs setup, validate now.
446        let ca_store = X509Store::new(&[root_ca_cert]);
447
448        let leaf = &server_cert;
449        let chain = [int_ca_cert];
450
451        assert!(ca_store.verify(leaf, &chain, now).is_ok());
452
453        // Now validate our data signature.
454        let subject_public_key_info = server_cert
455            .tbs_certificate
456            .subject_public_key_info
457            .owned_to_ref();
458
459        let verifier = EcdsaP384PublicKey::try_from(subject_public_key_info)
460            .map(EcdsaP384VerifyingKey::from)
461            .expect("Failed to create EcdsaP384VerifyingKey");
462
463        verifier
464            .verify(&test_data, &test_data_signature)
465            .expect("Failed to verify test data signature");
466    }
467
468    #[test]
469    #[cfg_attr(
470        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
471        wasm_bindgen_test
472    )]
473    fn x509_chain_verify_rsa_fido_mds() {
474        let _ = tracing_subscriber::fmt::try_init();
475
476        let global_sign_root_cert =
477            Certificate::from_pem(GLOBAL_SIGN_ROOT).expect("Failed to parse GlobalSign Root cert");
478        let mds_cert = Certificate::from_pem(FIDO_MDS).expect("Failed to parse FIDO MDS cert");
479
480        let ca_store = X509Store::new(&[global_sign_root_cert]);
481
482        // To check before expiry
483        let now = SystemTime::UNIX_EPOCH + Duration::from_secs(1753733820);
484        let leaf = &mds_cert;
485        let chain = [];
486
487        let result = ca_store.verify(leaf, &chain, now);
488        assert!(result.is_ok());
489
490        // Check expiry causes this to be invalid
491        let now = SystemTime::UNIX_EPOCH + Duration::from_secs(1753733825);
492        let result = ca_store.verify(leaf, &chain, now);
493        assert!(result.is_err());
494    }
495
496    #[test]
497    #[cfg_attr(
498        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
499        wasm_bindgen_test
500    )]
501    fn x509_chain_verify_rsa_yubico_u2f() {
502        let _ = tracing_subscriber::fmt::try_init();
503
504        let yubico_u2f_root_cert =
505            Certificate::from_pem(YUBICO_U2F_ROOT).expect("Failed to parse Yubico U2F Root cert");
506        let yubico_device_attest = Certificate::from_pem(YUBICO_DEVICE_ATTEST)
507            .expect("Failed to parse Yubico Device Attest cert");
508
509        let ca_store = X509Store::new(&[yubico_u2f_root_cert]);
510
511        let now = now();
512        let leaf = &yubico_device_attest;
513        let chain = [];
514
515        assert!(ca_store.verify(leaf, &chain, now).is_ok());
516    }
517
518    const YUBICO_U2F_ROOT: &str = r#"-----BEGIN CERTIFICATE-----
519MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
520dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
521MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290
522IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
523AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk
5245N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep
5258EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw
526nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT
5279nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw
528LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ
529hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN
530BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4
531MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt
532hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k
533LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U
534sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc
535U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==
536-----END CERTIFICATE-----"#;
537
538    const YUBICO_DEVICE_ATTEST: &str = r#"-----BEGIN CERTIFICATE-----
539MIICvTCCAaWgAwIBAgIEGKxGwDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
540dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
541MDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1
542YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUG
543A1UEAwweWXViaWNvIFUyRiBFRSBTZXJpYWwgNDEzOTQzNDg4MFkwEwYHKoZIzj0C
544AQYIKoZIzj0DAQcDQgAEeeo7LHxJcBBiIwzSP+tg5SkxcdSD8QC+hZ1rD4OXAwG1
545Rs3Ubs/K4+PzD4Hp7WK9Jo1MHr03s7y+kqjCrutOOqNsMGowIgYJKwYBBAGCxAoC
546BBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCBSAwIQYL
547KwYBBAGC5RwBAQQEEgQQy2lIHo/3QDmT7AonKaFUqDAMBgNVHRMBAf8EAjAAMA0G
548CSqGSIb3DQEBCwUAA4IBAQCXnQOX2GD4LuFdMRx5brr7Ivqn4ITZurTGG7tX8+a0
549wYpIN7hcPE7b5IND9Nal2bHO2orh/tSRKSFzBY5e4cvda9rAdVfGoOjTaCW6FZ5/
550ta2M2vgEhoz5Do8fiuoXwBa1XCp61JfIlPtx11PXm5pIS2w3bXI7mY0uHUMGvxAz
551ta74zKXLslaLaSQibSKjWKt9h+SsXy4JGqcVefOlaQlJfXL1Tga6wcO0QTu6Xq+U
552w7ZPNPnrpBrLauKDd202RlN4SP7ohL3d9bG6V5hUz/3OusNEBZUn5W3VmPj1ZnFa
553vkMB3RkRMOa58MZAORJT4imAPzrvJ0vtv94/y71C6tZ5
554-----END CERTIFICATE-----"#;
555
556    const GLOBAL_SIGN_ROOT: &str = r#"-----BEGIN CERTIFICATE-----
557MIIEYTCCA0mgAwIBAgIOSKQC3SeSDaIINJ3RmXswDQYJKoZIhvcNAQELBQAwTDEg
558MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2Jh
559bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTYwOTIxMDAwMDAwWhcNMjYw
560OTIxMDAwMDAwWjBiMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBu
561di1zYTE4MDYGA1UEAxMvR2xvYmFsU2lnbiBFeHRlbmRlZCBWYWxpZGF0aW9uIENB
562IC0gU0hBMjU2IC0gRzMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr
563awNnVNXcEfvFohPBjBkn3BB04mGDPfqO24+lD+SpvkY/Ar5EpAkcJjOfR0iBFYhW
564N80HzpXYy2tIA7mbXpKu2JpmYdU1xcoQpQK0ujE/we+vEDyjyjmtf76LLqbOfuq3
565xZbSqUqAY+MOvA67nnpdawvkHgJBFVPnxui45XH4BwTwbtDucx+Mo7EK4mS0Ti+P
5661NzARxFNCUFM8Wxc32wxXKff6WU4TbqUx/UJm485ttkFqu0Ox4wTUUbn0uuzK7yV
5673Y986EtGzhKBraMH36MekSYlE473GqHetRi9qbNG5pM++Sa+WjR9E1e0Yws16CGq
568smVKwAqg4uc43eBTFUhVAgMBAAGjggEpMIIBJTAOBgNVHQ8BAf8EBAMCAQYwEgYD
569VR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU3bPnbagu6MVObs905nU8lBXO6B0w
570HwYDVR0jBBgwFoAUj/BLf6guRSSuTVD6Y5qL3uLdG7wwPgYIKwYBBQUHAQEEMjAw
571MC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcDIuZ2xvYmFsc2lnbi5jb20vcm9vdHIz
572MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9v
573dC1yMy5jcmwwRwYDVR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBz
574Oi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUA
575A4IBAQBVaJzl0J/i0zUV38iMXIQ+Q/yht+JZZ5DW1otGL5OYV0LZ6ZE6xh+WuvWJ
576J4hrDbhfo6khUEaFtRUnurqzutvVyWgW8msnoP0gtMZO11cwPUMUuUV8iGyIOuIB
5770flo6G+XbV74SZuR5v5RAgqgGXucYUPZWvv9AfzMMQhRQkr/MO/WR2XSdiBrXHoD
578L2xk4DmjA4K6iPI+1+qMhyrkUM/2ZEdA8ldqwl8nQDkKS7vq6sUZ5LPVdfpxJZZu
5795JBj4y7FNFTVW1OMlCUvwt5H8aFgBMLFik9xqK6JFHpYxYmf4t2sLLxN0LlCthJE
580abvp10ZlOtfu8hL5gCXcxnwGxzSb
581-----END CERTIFICATE-----"#;
582
583    const FIDO_MDS: &str = r#"-----BEGIN CERTIFICATE-----
584MIIHGTCCBgGgAwIBAgIMIa7sY/5SFH8UYph5MA0GCSqGSIb3DQEBCwUAMGIxCzAJ
585BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTgwNgYDVQQDEy9H
586bG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBTSEEyNTYgLSBHMzAe
587Fw0yNDA2MjYyMDE3MDRaFw0yNTA3MjgyMDE3MDNaMIHSMR0wGwYDVQQPDBRQcml2
588YXRlIE9yZ2FuaXphdGlvbjEQMA4GA1UEBRMHMzQ1NDI4NDETMBEGCysGAQQBgjc8
589AgEDEwJVUzEbMBkGCysGAQQBgjc8AgECEwpDYWxpZm9ybmlhMQswCQYDVQQGEwJV
590UzEPMA0GA1UECBMGT3JlZ29uMRIwEAYDVQQHEwlCZWF2ZXJ0b24xHDAaBgNVBAoT
591E0ZJRE8gQUxMSUFOQ0UsIElOQy4xHTAbBgNVBAMTFG1kcy5maWRvYWxsaWFuY2Uu
592b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6AskoQ0bFp93JQQd
593p1b8nFCmB67dTNUptwkKtnHj0Y18DWopH8CKORM1LjAHyjMTPoOGXb5/rt1wDfOK
594b0chqSG9llrBzp/N0BuLL0ZFyZEAYt4th8Y0Ooc3FQtXZ99T6HNW+fmXaLbYxxnG
595nsxAxjVQmHwCZBnx+WPKgi6BqaYcY05M8uzWkgSp1nE4jD+JQ9HN0HSFhzHe3LW4
596v0th2Jz1OQmMhwia0SD/V6YXIqkXkqmmFenhCfSG+/LiLgWxmeIwApJ5oe10Dvmi
597JYeaaFkgbEc/b7/6PMaa4X/0aZZ1J7C0EHvn5lUHb8hfBbzGhsBKOpQW1uOhiK+y
598I9oKQQIDAQABo4IDXDCCA1gwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAw
599gZYGCCsGAQUFBwEBBIGJMIGGMEcGCCsGAQUFBzAChjtodHRwOi8vc2VjdXJlLmds
600b2JhbHNpZ24uY29tL2NhY2VydC9nc2V4dGVuZHZhbHNoYTJnM3IzLmNydDA7Bggr
601BgEFBQcwAYYvaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzZXh0ZW5kdmFs
602c2hhMmczcjMwVQYDVR0gBE4wTDBBBgkrBgEEAaAyAQEwNDAyBggrBgEFBQcCARYm
603aHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wBwYFZ4EMAQEw
604RQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybC5nbG9iYWxzaWduLmNvbS9ncy9n
605c2V4dGVuZHZhbHNoYTJnM3IzLmNybDAfBgNVHREEGDAWghRtZHMuZmlkb2FsbGlh
606bmNlLm9yZzAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgw
607FoAU3bPnbagu6MVObs905nU8lBXO6B0wHQYDVR0OBBYEFMaN4X1b9AHuWDPJK1AY
608dg2MQGhxMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgAS8U40vVNyTIQGGcOP
609P3oT+Oe1YoeInG0wBYTr5YYmOgAAAZBWMd/sAAAEAwBHMEUCIQDLehoLcAsQrMOG
610NzpCOEewntO7/FGYjM1BJwLaooEZeAIgKXVD02S4x8C+5zfxgVFbin3VHlP4l+FU
611925i66QhsVoAdgAN4fIwK9MNwUBiEgnqVS78R3R8sdfpMO8OQh60fk6qNAAAAZBW
612Md0DAAAEAwBHMEUCIFQEuBdgAXVF0joEul6oLwpIrz818XXZWbtg3LWJvInhAiEA
613iibo7o9oSc8UUnUUf6/4QhxBZ1DGGN34Qv1t8Cp+a5UAdwDm0jFjQHeMwRBBBtdx
614uc7B0kD2loSG+7qHMh39HjeOUAAAAZBWMd8eAAAEAwBIMEYCIQDQZGnntKA3LnHj
615V76+Fq55Nypv1BsHZLfhG736TcspLwIhANHF8kMePNAIooXltURI5i+sNF96x2zR
616PA6Ly2D/DezDMA0GCSqGSIb3DQEBCwUAA4IBAQBxWM7olfKF6bhJ8SzVKIKgfeV+
617YDqQS1Z9r453X5ZFv3jfD74uhsGjg2fI5vMulZzlFwXNTta0bf0TzaC0rkhuAcnc
618Rfi0rk9MmI6HMuG4qaEO+6JJxst/OH/1k/GC8gh2MgwX6Aq9b33kaMTEnGeByFEH
619Qf/4ZcuhoOkVeQ7MX+p0BNdaNdp6v6au4WDf0JJgTPPV//VJykqOCV6zgTt3hra0
620HR9+f1CMFvtSC1OpP197c7XGNdK2Rnn/6Z2y7Ak9G3iYhGhS/Ssz9zsOUTi7b+SY
621ywLlY2y0vY1svPUSJEWjhMtVDL9b2/DvIhNqp0kGCiXCGmtzW5DxgXE1ckkh
622-----END CERTIFICATE-----"#;
623}