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 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 let mut path_length = 0;
84
85 for intermediate in intermediates {
86 self.validate_pair(
90 certificate_to_validate,
91 intermediate,
92 current_time_unix,
93 path_length,
94 )?;
95
96 certificate_to_validate = intermediate;
98
99 path_length = path_length
101 .checked_add(1)
102 .ok_or(X509VerificationError::ExcessivePathLength)?;
103 }
104
105 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 Ok(authority_cert)
126 }
127
128 fn validate_leaf(
129 &self,
130 certificate_to_validate: &Certificate,
131 current_time: Duration,
132 ) -> Result<(), X509VerificationError> {
133 let maybe_basic_constraints = certificate_to_validate
137 .tbs_certificate
138 .get::<BasicConstraints>()
139 .map_err(|_err| X509VerificationError::ExtensionFailure)?;
140 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 if !key_usage.digital_signature() {
160 return Err(X509VerificationError::KeyUsageNotValid);
161 }
162 }
163
164 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 let (_critical, basic_constraints) = authority
207 .tbs_certificate
208 .get::<BasicConstraints>()
209 .map_err(|_err| X509VerificationError::ExtensionFailure)?
210 .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 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 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 if certificate_to_validate.signature_algorithm != authority.tbs_certificate.signature {
261 warn!(?certificate_to_validate.signature_algorithm, ?authority.tbs_certificate.signature);
262 }
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
298fn 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 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 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 #[test]
402 fn x509_chain_verify_basic() {
403 let _ = tracing_subscriber::fmt::try_init();
404
405 let now = SystemTime::now();
406 let not_before = Time::try_from(now).unwrap();
407 let not_after = Time::try_from(now + Duration::new(3600, 0)).unwrap();
408
409 let (root_signing_key, root_ca_cert) = build_test_ca_root(not_before, not_after);
410
411 let (int_signing_key, int_ca_cert) =
412 build_test_ca_int(not_before, not_after, &root_signing_key, &root_ca_cert);
413
414 let subject = Name::from_str("CN=localhost").unwrap();
415 let (server_key, server_csr) = build_test_csr(&subject);
416
417 let server_cert = test_ca_sign_server_csr(
418 not_before,
419 not_after,
420 &server_csr,
421 &int_signing_key,
422 &int_ca_cert,
423 );
424
425 tracing::debug!(cert = %X509Display::from(&server_cert));
426
427 let test_data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
429
430 let test_data_signature: EcdsaP384Signature = server_key
431 .try_sign(&test_data)
432 .expect("Unable to sign test data");
434
435 let ca_store = X509Store::new(&[root_ca_cert]);
437
438 let leaf = &server_cert;
439 let chain = [int_ca_cert];
440
441 assert!(ca_store.verify(leaf, &chain, now).is_ok());
442
443 let subject_public_key_info = server_cert
445 .tbs_certificate
446 .subject_public_key_info
447 .owned_to_ref();
448
449 let verifier = EcdsaP384PublicKey::try_from(subject_public_key_info)
450 .map(EcdsaP384VerifyingKey::from)
451 .unwrap();
452
453 verifier.verify(&test_data, &test_data_signature).unwrap();
454 }
455
456 #[test]
457 fn x509_chain_verify_rsa_fido_mds() {
458 let _ = tracing_subscriber::fmt::try_init();
459
460 let global_sign_root_cert = Certificate::from_pem(GLOBAL_SIGN_ROOT).unwrap();
461 let mds_cert = Certificate::from_pem(FIDO_MDS).unwrap();
462
463 let ca_store = X509Store::new(&[global_sign_root_cert]);
464
465 let now = SystemTime::UNIX_EPOCH + Duration::from_secs(1753733820);
467 let leaf = &mds_cert;
468 let chain = [];
469
470 let result = ca_store.verify(leaf, &chain, now);
471 assert!(result.is_ok());
472
473 let now = SystemTime::UNIX_EPOCH + Duration::from_secs(1753733825);
475 let result = ca_store.verify(leaf, &chain, now);
476 assert!(result.is_err());
477 }
478
479 #[test]
480 fn x509_chain_verify_rsa_yubico_u2f() {
481 let _ = tracing_subscriber::fmt::try_init();
482
483 let yubico_u2f_root_cert = Certificate::from_pem(YUBICO_U2F_ROOT).unwrap();
484 let yubico_device_attest = Certificate::from_pem(YUBICO_DEVICE_ATTEST).unwrap();
485
486 let ca_store = X509Store::new(&[yubico_u2f_root_cert]);
487
488 let now = SystemTime::now();
489 let leaf = &yubico_device_attest;
490 let chain = [];
491
492 assert!(ca_store.verify(leaf, &chain, now).is_ok());
493 }
494
495 const YUBICO_U2F_ROOT: &str = r#"-----BEGIN CERTIFICATE-----
496MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
497dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
498MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290
499IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
500AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk
5015N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep
5028EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw
503nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT
5049nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw
505LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ
506hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN
507BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4
508MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt
509hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k
510LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U
511sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc
512U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==
513-----END CERTIFICATE-----"#;
514
515 const YUBICO_DEVICE_ATTEST: &str = r#"-----BEGIN CERTIFICATE-----
516MIICvTCCAaWgAwIBAgIEGKxGwDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
517dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
518MDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1
519YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUG
520A1UEAwweWXViaWNvIFUyRiBFRSBTZXJpYWwgNDEzOTQzNDg4MFkwEwYHKoZIzj0C
521AQYIKoZIzj0DAQcDQgAEeeo7LHxJcBBiIwzSP+tg5SkxcdSD8QC+hZ1rD4OXAwG1
522Rs3Ubs/K4+PzD4Hp7WK9Jo1MHr03s7y+kqjCrutOOqNsMGowIgYJKwYBBAGCxAoC
523BBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCBSAwIQYL
524KwYBBAGC5RwBAQQEEgQQy2lIHo/3QDmT7AonKaFUqDAMBgNVHRMBAf8EAjAAMA0G
525CSqGSIb3DQEBCwUAA4IBAQCXnQOX2GD4LuFdMRx5brr7Ivqn4ITZurTGG7tX8+a0
526wYpIN7hcPE7b5IND9Nal2bHO2orh/tSRKSFzBY5e4cvda9rAdVfGoOjTaCW6FZ5/
527ta2M2vgEhoz5Do8fiuoXwBa1XCp61JfIlPtx11PXm5pIS2w3bXI7mY0uHUMGvxAz
528ta74zKXLslaLaSQibSKjWKt9h+SsXy4JGqcVefOlaQlJfXL1Tga6wcO0QTu6Xq+U
529w7ZPNPnrpBrLauKDd202RlN4SP7ohL3d9bG6V5hUz/3OusNEBZUn5W3VmPj1ZnFa
530vkMB3RkRMOa58MZAORJT4imAPzrvJ0vtv94/y71C6tZ5
531-----END CERTIFICATE-----"#;
532
533 const GLOBAL_SIGN_ROOT: &str = r#"-----BEGIN CERTIFICATE-----
534MIIEYTCCA0mgAwIBAgIOSKQC3SeSDaIINJ3RmXswDQYJKoZIhvcNAQELBQAwTDEg
535MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2Jh
536bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTYwOTIxMDAwMDAwWhcNMjYw
537OTIxMDAwMDAwWjBiMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBu
538di1zYTE4MDYGA1UEAxMvR2xvYmFsU2lnbiBFeHRlbmRlZCBWYWxpZGF0aW9uIENB
539IC0gU0hBMjU2IC0gRzMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr
540awNnVNXcEfvFohPBjBkn3BB04mGDPfqO24+lD+SpvkY/Ar5EpAkcJjOfR0iBFYhW
541N80HzpXYy2tIA7mbXpKu2JpmYdU1xcoQpQK0ujE/we+vEDyjyjmtf76LLqbOfuq3
542xZbSqUqAY+MOvA67nnpdawvkHgJBFVPnxui45XH4BwTwbtDucx+Mo7EK4mS0Ti+P
5431NzARxFNCUFM8Wxc32wxXKff6WU4TbqUx/UJm485ttkFqu0Ox4wTUUbn0uuzK7yV
5443Y986EtGzhKBraMH36MekSYlE473GqHetRi9qbNG5pM++Sa+WjR9E1e0Yws16CGq
545smVKwAqg4uc43eBTFUhVAgMBAAGjggEpMIIBJTAOBgNVHQ8BAf8EBAMCAQYwEgYD
546VR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU3bPnbagu6MVObs905nU8lBXO6B0w
547HwYDVR0jBBgwFoAUj/BLf6guRSSuTVD6Y5qL3uLdG7wwPgYIKwYBBQUHAQEEMjAw
548MC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcDIuZ2xvYmFsc2lnbi5jb20vcm9vdHIz
549MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9v
550dC1yMy5jcmwwRwYDVR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBz
551Oi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUA
552A4IBAQBVaJzl0J/i0zUV38iMXIQ+Q/yht+JZZ5DW1otGL5OYV0LZ6ZE6xh+WuvWJ
553J4hrDbhfo6khUEaFtRUnurqzutvVyWgW8msnoP0gtMZO11cwPUMUuUV8iGyIOuIB
5540flo6G+XbV74SZuR5v5RAgqgGXucYUPZWvv9AfzMMQhRQkr/MO/WR2XSdiBrXHoD
555L2xk4DmjA4K6iPI+1+qMhyrkUM/2ZEdA8ldqwl8nQDkKS7vq6sUZ5LPVdfpxJZZu
5565JBj4y7FNFTVW1OMlCUvwt5H8aFgBMLFik9xqK6JFHpYxYmf4t2sLLxN0LlCthJE
557abvp10ZlOtfu8hL5gCXcxnwGxzSb
558-----END CERTIFICATE-----"#;
559
560 const FIDO_MDS: &str = r#"-----BEGIN CERTIFICATE-----
561MIIHGTCCBgGgAwIBAgIMIa7sY/5SFH8UYph5MA0GCSqGSIb3DQEBCwUAMGIxCzAJ
562BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTgwNgYDVQQDEy9H
563bG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBTSEEyNTYgLSBHMzAe
564Fw0yNDA2MjYyMDE3MDRaFw0yNTA3MjgyMDE3MDNaMIHSMR0wGwYDVQQPDBRQcml2
565YXRlIE9yZ2FuaXphdGlvbjEQMA4GA1UEBRMHMzQ1NDI4NDETMBEGCysGAQQBgjc8
566AgEDEwJVUzEbMBkGCysGAQQBgjc8AgECEwpDYWxpZm9ybmlhMQswCQYDVQQGEwJV
567UzEPMA0GA1UECBMGT3JlZ29uMRIwEAYDVQQHEwlCZWF2ZXJ0b24xHDAaBgNVBAoT
568E0ZJRE8gQUxMSUFOQ0UsIElOQy4xHTAbBgNVBAMTFG1kcy5maWRvYWxsaWFuY2Uu
569b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6AskoQ0bFp93JQQd
570p1b8nFCmB67dTNUptwkKtnHj0Y18DWopH8CKORM1LjAHyjMTPoOGXb5/rt1wDfOK
571b0chqSG9llrBzp/N0BuLL0ZFyZEAYt4th8Y0Ooc3FQtXZ99T6HNW+fmXaLbYxxnG
572nsxAxjVQmHwCZBnx+WPKgi6BqaYcY05M8uzWkgSp1nE4jD+JQ9HN0HSFhzHe3LW4
573v0th2Jz1OQmMhwia0SD/V6YXIqkXkqmmFenhCfSG+/LiLgWxmeIwApJ5oe10Dvmi
574JYeaaFkgbEc/b7/6PMaa4X/0aZZ1J7C0EHvn5lUHb8hfBbzGhsBKOpQW1uOhiK+y
575I9oKQQIDAQABo4IDXDCCA1gwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAw
576gZYGCCsGAQUFBwEBBIGJMIGGMEcGCCsGAQUFBzAChjtodHRwOi8vc2VjdXJlLmds
577b2JhbHNpZ24uY29tL2NhY2VydC9nc2V4dGVuZHZhbHNoYTJnM3IzLmNydDA7Bggr
578BgEFBQcwAYYvaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzZXh0ZW5kdmFs
579c2hhMmczcjMwVQYDVR0gBE4wTDBBBgkrBgEEAaAyAQEwNDAyBggrBgEFBQcCARYm
580aHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wBwYFZ4EMAQEw
581RQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybC5nbG9iYWxzaWduLmNvbS9ncy9n
582c2V4dGVuZHZhbHNoYTJnM3IzLmNybDAfBgNVHREEGDAWghRtZHMuZmlkb2FsbGlh
583bmNlLm9yZzAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgw
584FoAU3bPnbagu6MVObs905nU8lBXO6B0wHQYDVR0OBBYEFMaN4X1b9AHuWDPJK1AY
585dg2MQGhxMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgAS8U40vVNyTIQGGcOP
586P3oT+Oe1YoeInG0wBYTr5YYmOgAAAZBWMd/sAAAEAwBHMEUCIQDLehoLcAsQrMOG
587NzpCOEewntO7/FGYjM1BJwLaooEZeAIgKXVD02S4x8C+5zfxgVFbin3VHlP4l+FU
588925i66QhsVoAdgAN4fIwK9MNwUBiEgnqVS78R3R8sdfpMO8OQh60fk6qNAAAAZBW
589Md0DAAAEAwBHMEUCIFQEuBdgAXVF0joEul6oLwpIrz818XXZWbtg3LWJvInhAiEA
590iibo7o9oSc8UUnUUf6/4QhxBZ1DGGN34Qv1t8Cp+a5UAdwDm0jFjQHeMwRBBBtdx
591uc7B0kD2loSG+7qHMh39HjeOUAAAAZBWMd8eAAAEAwBIMEYCIQDQZGnntKA3LnHj
592V76+Fq55Nypv1BsHZLfhG736TcspLwIhANHF8kMePNAIooXltURI5i+sNF96x2zR
593PA6Ly2D/DezDMA0GCSqGSIb3DQEBCwUAA4IBAQBxWM7olfKF6bhJ8SzVKIKgfeV+
594YDqQS1Z9r453X5ZFv3jfD74uhsGjg2fI5vMulZzlFwXNTta0bf0TzaC0rkhuAcnc
595Rfi0rk9MmI6HMuG4qaEO+6JJxst/OH/1k/GC8gh2MgwX6Aq9b33kaMTEnGeByFEH
596Qf/4ZcuhoOkVeQ7MX+p0BNdaNdp6v6au4WDf0JJgTPPV//VJykqOCV6zgTt3hra0
597HR9+f1CMFvtSC1OpP197c7XGNdK2Rnn/6Z2y7Ak9G3iYhGhS/Ssz9zsOUTi7b+SY
598ywLlY2y0vY1svPUSJEWjhMtVDL9b2/DvIhNqp0kGCiXCGmtzW5DxgXE1ckkh
599-----END CERTIFICATE-----"#;
600}