Skip to content

Commit 11df11c

Browse files
Added RSASSA-PSS support
Since `oscrypto` doesn't do PSS with arbitrary parameters (which is an issue in our validation use case) all cryptographic operations have been replaced with `pyca/cryptography` instead. The role of `oscrypto` is now limited to accessing the system trust. Since oscrypto is a fairly lightweight dependency it'll probably stay; extracting & testing its trust list API would probably be more trouble than it's worth. I realise that `pyca/cryptography` can be harder to install (especially on resource-limited systems running on somewhat obscure architectures), but since this certvalidator fork is mainly intended as a tool to serve pyHanko's needs, the additional dependency burden is not a significant one.
1 parent 5802c50 commit 11df11c

File tree

11 files changed

+299
-85
lines changed

11 files changed

+299
-85
lines changed

.github/workflows/ci.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
name: CI
22
on: [push, pull_request]
33

4+
5+
# TODO: work out how many of these tests configs still make sense now that we use
6+
# pyca/cryptography for all crypto operations (and oscrypto just serves as a means
7+
# to access the system trust)
48
jobs:
59
build:
610
name: Python ${{ matrix.python }} on ${{ matrix.os }} ${{ matrix.arch }}
@@ -32,7 +36,7 @@ jobs:
3236
- name: Install dependencies
3337
run: |
3438
python -m pip install --upgrade pip
35-
pip install requests~=2.24.0 uritools~=3.0.1
39+
pip install requests~=2.24.0 uritools~=3.0.1 cryptography>=3.3.1
3640
python run.py deps
3741
- name: Run test suite
3842
run: python run.py ci

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ revocation checks.
2323
- X.509 path building
2424
- X.509 basic path validation
2525
- Signatures
26-
- RSA, DSA and EC algorithms
26+
- RSA (including PSS padding), DSA and EC algorithms
2727
- Name chaining
2828
- Validity dates
2929
- Basic constraints extension

pyhanko_certvalidator/validate.py

+111-83
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# coding: utf-8
22
from __future__ import unicode_literals, division, absolute_import, print_function
33

4-
from asn1crypto import x509, crl
5-
from oscrypto import asymmetric
6-
import oscrypto.errors
4+
from asn1crypto import x509, crl, algos
5+
from asn1crypto.keys import PublicKeyInfo
6+
from cryptography.exceptions import InvalidSignature
7+
from cryptography.hazmat.primitives import hashes, serialization
8+
from cryptography.hazmat.primitives.asymmetric import padding, rsa, ec, dsa
79

810
from ._errors import pretty_message
911
from ._types import str_cls, type_name
@@ -324,27 +326,23 @@ def _validate_path(validation_context, path, end_entity_name_override=None):
324326
hash_algo
325327
))
326328

327-
if signature_algo == 'rsassa_pkcs1v15':
328-
verify_func = asymmetric.rsa_pkcs1v15_verify
329-
elif signature_algo == 'dsa':
330-
verify_func = asymmetric.dsa_verify
331-
elif signature_algo == 'ecdsa':
332-
verify_func = asymmetric.ecdsa_verify
333-
else:
329+
try:
330+
_validate_sig(
331+
signature=cert['signature_value'].native,
332+
signed_data=cert['tbs_certificate'].dump(),
333+
public_key_info=working_public_key,
334+
sig_algo=signature_algo, hash_algo=hash_algo,
335+
parameters=cert['signature_algorithm']['parameters']
336+
)
337+
except PSSParameterMismatch:
334338
raise PathValidationError(pretty_message(
335339
'''
336-
The path could not be validated because the signature of %s
337-
uses the unsupported algorithm %s
340+
The signature parameters for %s do not match the constraints
341+
on the public key.
338342
''',
339-
_cert_type(index, last_index, end_entity_name_override, definite=True),
340-
signature_algo
343+
_cert_type(index, last_index, end_entity_name_override, definite=True)
341344
))
342-
343-
try:
344-
key_object = asymmetric.load_public_key(working_public_key)
345-
verify_func(key_object, cert['signature_value'].native, cert['tbs_certificate'].dump(), hash_algo)
346-
347-
except (oscrypto.errors.SignatureError):
345+
except InvalidSignature:
348346
raise PathValidationError(pretty_message(
349347
'''
350348
The path could not be validated because the signature of %s
@@ -638,6 +636,9 @@ def _validate_path(validation_context, path, end_entity_name_override=None):
638636

639637
# Handle inheritance of DSA parameters from a signing CA to the
640638
# next in the chain
639+
# NOTE: we don't perform this step for RSASSA-PSS since there the
640+
# parameters are drawn form the signature parameters, where they
641+
# must always be present.
641642
copy_params = None
642643
if cert.public_key.algorithm == 'dsa' and cert.public_key.hash_algo is None:
643644
if working_public_key.algorithm == 'dsa':
@@ -831,7 +832,7 @@ def _cert_type(index, last_index, end_entity_name_override, definite=False):
831832
return prefix + 'end-entity certificate'
832833

833834

834-
def _self_signed(cert):
835+
def _self_signed(cert: x509.Certificate):
835836
"""
836837
Determines if a certificate is self-signed
837838
@@ -853,27 +854,16 @@ def _self_signed(cert):
853854
signature_algo = cert['signature_algorithm'].signature_algo
854855
hash_algo = cert['signature_algorithm'].hash_algo
855856

856-
if signature_algo == 'rsassa_pkcs1v15':
857-
verify_func = asymmetric.rsa_pkcs1v15_verify
858-
elif signature_algo == 'dsa':
859-
verify_func = asymmetric.dsa_verify
860-
elif signature_algo == 'ecdsa':
861-
verify_func = asymmetric.ecdsa_verify
862-
else:
863-
raise PathValidationError(pretty_message(
864-
'''
865-
Unable to verify the signature of the certificate since it uses
866-
the unsupported algorithm %s
867-
''',
868-
signature_algo
869-
))
870-
871857
try:
872-
key_object = asymmetric.load_certificate(cert)
873-
verify_func(key_object, cert['signature_value'].native, cert['tbs_certificate'].dump(), hash_algo)
858+
_validate_sig(
859+
signature=cert['signature_value'].native,
860+
signed_data=cert['tbs_certificate'].dump(),
861+
public_key_info=cert.public_key,
862+
sig_algo=signature_algo, hash_algo=hash_algo,
863+
parameters=cert['signature_algorithm']['parameters']
864+
)
874865
return True
875-
876-
except (oscrypto.errors.SignatureError):
866+
except InvalidSignature:
877867
return False
878868

879869

@@ -1128,30 +1118,22 @@ def verify_ocsp_response(cert, path, validation_context, cert_description=None,
11281118
signature_algo = response['signature_algorithm'].signature_algo
11291119
hash_algo = response['signature_algorithm'].hash_algo
11301120

1131-
if signature_algo == 'rsassa_pkcs1v15':
1132-
verify_func = asymmetric.rsa_pkcs1v15_verify
1133-
elif signature_algo == 'dsa':
1134-
verify_func = asymmetric.dsa_verify
1135-
elif signature_algo == 'ecdsa':
1136-
verify_func = asymmetric.ecdsa_verify
1137-
else:
1121+
# Verify that the response was properly signed by the validated certificate
1122+
try:
1123+
_validate_sig(
1124+
signature=response['signature'].native,
1125+
signed_data=tbs_response.dump(),
1126+
public_key_info=signing_cert.public_key,
1127+
sig_algo=signature_algo, hash_algo=hash_algo,
1128+
parameters=response['signature_algorithm']['parameters']
1129+
)
1130+
except PSSParameterMismatch:
11381131
failures.append((
1139-
pretty_message(
1140-
'''
1141-
Unable to verify OCSP response since response signature
1142-
uses the unsupported algorithm %s
1143-
''',
1144-
signature_algo
1145-
),
1132+
'The signature parameters on the OCSP response do not match '
1133+
'the constraints on the public key',
11461134
ocsp_response
11471135
))
1148-
continue
1149-
1150-
# Verify that the response was properly signed by the validated certificate
1151-
try:
1152-
key_object = asymmetric.load_certificate(signing_cert)
1153-
verify_func(key_object, response['signature'].native, tbs_response.dump(), hash_algo)
1154-
except (oscrypto.errors.SignatureError):
1136+
except InvalidSignature:
11551137
failures.append((
11561138
'Unable to verify OCSP response signature',
11571139
ocsp_response
@@ -1828,30 +1810,19 @@ def _verify_signature(certificate_list, crl_issuer):
18281810
signature_algo = certificate_list['signature_algorithm'].signature_algo
18291811
hash_algo = certificate_list['signature_algorithm'].hash_algo
18301812

1831-
if signature_algo == 'rsassa_pkcs1v15':
1832-
verify_func = asymmetric.rsa_pkcs1v15_verify
1833-
elif signature_algo == 'dsa':
1834-
verify_func = asymmetric.dsa_verify
1835-
elif signature_algo == 'ecdsa':
1836-
verify_func = asymmetric.ecdsa_verify
1837-
else:
1838-
raise CRLValidationError(pretty_message(
1839-
'''
1840-
Unable to verify the CertificateList since the signature uses the
1841-
unsupported algorithm %s
1842-
''',
1843-
signature_algo
1844-
))
1845-
18461813
try:
1847-
key_object = asymmetric.load_certificate(crl_issuer)
1848-
verify_func(
1849-
key_object,
1850-
certificate_list['signature'].native,
1851-
certificate_list['tbs_cert_list'].dump(),
1852-
hash_algo
1814+
_validate_sig(
1815+
signature=certificate_list['signature'].native,
1816+
signed_data=certificate_list['tbs_cert_list'].dump(),
1817+
public_key_info=crl_issuer.public_key,
1818+
sig_algo=signature_algo, hash_algo=hash_algo,
1819+
parameters=certificate_list['signature_algorithm']['parameters']
18531820
)
1854-
except (oscrypto.errors.SignatureError):
1821+
except PSSParameterMismatch as e:
1822+
raise CRLValidationError(
1823+
'Invalid signature parameters on CertificateList'
1824+
) from e
1825+
except InvalidSignature:
18551826
raise CRLValidationError('Unable to verify the signature of the CertificateList')
18561827

18571828

@@ -2038,3 +2009,60 @@ def __init__(self, valid_policy, qualifier_set, expected_policy_set):
20382009
self.qualifier_set = qualifier_set
20392010
self.expected_policy_set = expected_policy_set
20402011
self.children = []
2012+
2013+
2014+
class PSSParameterMismatch(InvalidSignature):
2015+
pass
2016+
2017+
2018+
def _validate_sig(signature: bytes, signed_data: bytes,
2019+
public_key_info: PublicKeyInfo,
2020+
sig_algo: str, hash_algo: str, parameters=None):
2021+
2022+
hash_algo = getattr(hashes, hash_algo.upper())()
2023+
2024+
# pyca/cryptography can't load PSS-exclusive keys without some help:
2025+
if public_key_info.algorithm == 'rsassa_pss':
2026+
public_key_info = public_key_info.copy()
2027+
assert isinstance(parameters, algos.RSASSAPSSParams)
2028+
pss_key_params = public_key_info['algorithm']['parameters'].native
2029+
if pss_key_params is not None and pss_key_params != parameters.native:
2030+
raise PSSParameterMismatch(
2031+
"Public key info includes PSS parameters that do not match "
2032+
"those on the signature"
2033+
)
2034+
# set key type to generic RSA, discard parameters
2035+
public_key_info['algorithm'] = {'algorithm': 'rsa'}
2036+
2037+
pub_key = serialization.load_der_public_key(public_key_info.dump())
2038+
2039+
if sig_algo == 'rsassa_pkcs1v15':
2040+
assert isinstance(pub_key, rsa.RSAPublicKey)
2041+
pub_key.verify(signature, signed_data, padding.PKCS1v15(), hash_algo)
2042+
elif sig_algo == 'rsassa_pss':
2043+
assert isinstance(pub_key, rsa.RSAPublicKey)
2044+
assert isinstance(parameters, algos.RSASSAPSSParams)
2045+
mga: algos.MaskGenAlgorithm = parameters['mask_gen_algorithm']
2046+
if not mga['algorithm'].native == 'mgf1':
2047+
raise NotImplementedError("Only MFG1 is supported")
2048+
2049+
mgf_md_name = mga['parameters']['algorithm'].native
2050+
2051+
salt_len: int = parameters['salt_length'].native
2052+
2053+
mgf_md = getattr(hashes, mgf_md_name.upper())()
2054+
pss_padding = padding.PSS(
2055+
mgf=padding.MGF1(algorithm=mgf_md),
2056+
salt_length=salt_len
2057+
)
2058+
pub_key.verify(signature, signed_data, pss_padding, hash_algo)
2059+
elif sig_algo == 'dsa':
2060+
assert isinstance(pub_key, dsa.DSAPublicKey)
2061+
pub_key.verify(signature, signed_data, hash_algo)
2062+
elif sig_algo == 'ecdsa':
2063+
assert isinstance(pub_key, ec.EllipticCurvePublicKey)
2064+
pub_key.verify(signature, signed_data, ec.ECDSA(hash_algo))
2065+
else: # pragma: nocover
2066+
raise NotImplementedError(
2067+
f"Signature mechanism {sig_algo} is not supported."
2068+
)

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ def run(self):
125125
'requests>=2.24.0',
126126
'asn1crypto>=1.2.0',
127127
'oscrypto>=1.1.0',
128+
'cryptography>=3.3.1',
128129
'uritools>=3.0.1'
129130
],
130131
packages=[PYTHON_PACKAGE_NAME],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDyjCCAn6gAwIBAgICEAEwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEF
3+
AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgMDsxCzAJBgNVBAYT
4+
AkJFMRowGAYDVQQKDBFUZXN0aW5nIEF1dGhvcml0eTEQMA4GA1UEAwwHUm9vdCBD
5+
QTAiGA8yMDAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjBDMQswCQYDVQQG
6+
EwJCRTEaMBgGA1UECgwRVGVzdGluZyBBdXRob3JpdHkxGDAWBgNVBAMMD0ludGVy
7+
bWVkaWF0ZSBDQTCCASAwCwYJKoZIhvcNAQEKA4IBDwAwggEKAoIBAQDPlhBn2wSd
8+
um440KB4vmb4RbFeo7C6YfAulLEEF4D8V0fgd6Rj2XKRk8WowNCmf+QPFA0xH6Ew
9+
ndIGT+i4haS04TsOt++Wkz/Dj8lyFTC50WW1ZVyDZl89mpJ5myyTbToke9fF/YLN
10+
K0QyH19BXoxNGqAGf/cDvraWpUWVwgcA+rGWwGZU4CRnOSs2WqBO2ESzaJ1xAqMJ
11+
bBZyQeCrS8p5+Wz6e1G0layjinNtAQdwe2UVzsOUtZ/errqb0t3If3wRWh2rf93c
12+
t/MGYBC4R0LeR7vn3ZwAdOgZ39+LyHz04mmUBsokT8P55p2wuxecw8YiFDoy0xFe
13+
bAq4NnzEzG6vAgMBAAGjZjBkMB0GA1UdDgQWBBRnXK/DvDkcd7dpfXcgnEV7tQAn
14+
ITAfBgNVHSMEGDAWgBRx8eBpesXpOEuTGzsVR4vzVq9i+zASBgNVHRMBAf8ECDAG
15+
AQH/AgEAMA4GA1UdDwEB/wQEAwIBhjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFl
16+
AwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASADggEBAFZH
17+
FG2TegyZGn5x/v4qOgDVGNxIDVbMPZ7vFAwfmDRu/JsNH3MynzhUjEIWq3OAbk4e
18+
acE5oJ2FsUCHkqVcLizqma89pltGDD/Aug1JWY3gKKuEMEFDUBggAy0V7i6VY6pw
19+
SoJRRxAi7YINwqHt4iytM98aLQJc6ceyMcRXA1cWzjjIDPMv90SdRha1kBnGTtjR
20+
nkJ2bteVSX6NAhj81GKbA+7PDGKbOCvzmM7k3punDgFL8vbJrkvpg/2F8f5vZaiP
21+
29vHI9MwOS4L2MkjFV8AzssAc771P42ND9ns38iw04d5w1uM07voDe8pBKBRdMCF
22+
Z+KJicfjpfcXzNiUOQU=
23+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDvzCCAnOgAwIBAgICEAAwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEF
3+
AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgMDsxCzAJBgNVBAYT
4+
AkJFMRowGAYDVQQKDBFUZXN0aW5nIEF1dGhvcml0eTEQMA4GA1UEAwwHUm9vdCBD
5+
QTAiGA8yMDAwMDEwMTAwMDAwMFoYDzI1MDAwMTAxMDAwMDAwWjA7MQswCQYDVQQG
6+
EwJCRTEaMBgGA1UECgwRVGVzdGluZyBBdXRob3JpdHkxEDAOBgNVBAMMB1Jvb3Qg
7+
Q0EwggEgMAsGCSqGSIb3DQEBCgOCAQ8AMIIBCgKCAQEAr6rUQReGwbWaufayDOPl
8+
Js/Gm1bDOPt8+3kC3Ejd1RXvilsKpdOfuP8s+4D7MmNfjrCEMnvlGYKrVuZHySHz
9+
48qQlFUxb7jsuD5YS9KWClkV7xJ2FoFC2q57jgBrUhfBHhU/i+EWu5zuMlHWALjU
10+
rQIjAqheQAfaj5RG6W7fsufx7zgN9hWUDUClCXUauVdS6rIwMH3poyvixojQGgQF
11+
WUyWDOd1ZzCG33XaY4yPglppvRorDVVfajW7qnStbjW36s/ekCCit+w2JU699HuH
12+
93B89nmCoylh1WhogT9WBqjjWcN7B6Q52jGx2N+A0lUvsk6DApEmCHUM48Sa3hO5
13+
RwIDAQABo2MwYTAdBgNVHQ4EFgQUcfHgaXrF6ThLkxs7FUeL81avYvswHwYDVR0j
14+
BBgwFoAUcfHgaXrF6ThLkxs7FUeL81avYvswDwYDVR0TAQH/BAUwAwEB/zAOBgNV
15+
HQ8BAf8EBAMCAYYwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEFAKEcMBoG
16+
CSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgA4IBAQAdouZlWC/t3mBBGdhG
17+
/UMzmA1+5OlOOAFszt2+LAqMj3lxLbAreDNNuSfxqmFaN+4CanZVELVacV5BUU4y
18+
tfLFLpf3zu7iWyGUXJTHlwknSvVS5ZXZIY3jw0JsdC0OCDtRVmnMxYcMlqjzQZ+V
19+
Gp8j6ce8qbvw5Vkky4k2hpnx79E/9+88uHGKZhhasU1yuEZgLwIfq04RwCc13tTg
20+
wfYo4ysEJ0TzsNuXUyC/V7xtpbKmH9ZgnyubspmIgMM0HGKnk6id7eHPe/SNyM5J
21+
f0qVOkh86mIMwaL2puQqE9wogp/vtsxNN1jI9eA5Q3h8YkTVUUm0q0SfkzUE+Yp1
22+
6Lv2
23+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDxjCCAnqgAwIBAgICEAAwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEF
3+
AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgMEMxCzAJBgNVBAYT
4+
AkJFMRowGAYDVQQKDBFUZXN0aW5nIEF1dGhvcml0eTEYMBYGA1UEAwwPSW50ZXJt
5+
ZWRpYXRlIENBMCIYDzIwMjAwMTAxMDAwMDAwWhgPMjAyMjAxMDEwMDAwMDBaMEsx
6+
CzAJBgNVBAYTAkJFMRowGAYDVQQKDBFUZXN0aW5nIEF1dGhvcml0eTEQMA4GA1UE
7+
CwwHU2lnbmVyczEOMAwGA1UEAwwFQWxpY2UwggEgMAsGCSqGSIb3DQEBCgOCAQ8A
8+
MIIBCgKCAQEA54KAlKDeHceR3gNmgsWtBX5MjYg6PFd8rUI6mac+O+/LuoZvwLoR
9+
Wa59ivHJpJ/yLpUrKP9r3byNyEojAlJB5Em7YLhNR9uJcRc4nXPStyZhBGWO438s
10+
U6cCDJDF7NckD7E2vCBw6+sB4UhkpRCgFn8O8WEWtdt3QuJqchkdL1KB1bGH5dve
11+
NeJ+oW+rzuxqQ6SuDT5O2uztA2LM27poMxWdb250NvcuRELLFkvwWxgb6bZUiDIa
12+
HUPt1naZPGhH8VKt5ZhCuOhTnv2vJViQzbFPHX2sG7d2u1YhUYxqmOVtDIG84hzp
13+
JQRZ9xX+1oBFDIF6a14PSc/YyLw91X2C6wIDAQABo1IwUDAdBgNVHQ4EFgQUADv2
14+
3DgWFu93XfHVtOrrhMGByfEwHwYDVR0jBBgwFoAUZ1yvw7w5HHe3aX13IJxFe7UA
15+
JyEwDgYDVR0PAQH/BAQDAgbAMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIB
16+
BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAoo6uWgOd
17+
Dyo1FtsFWMylhjOUJ8TGN82ZEV2ZkXS7OxkXNES8yyY84jS72MjJEbcmSrV/Elb2
18+
4+UOiYoZepwaClPtAypWI7YQCM3V7ZA6sW2F2yac7TNpAsngNR1ERAQA8BFg4WSH
19+
pvrm3I+w77jSzRM/hKxKlYcrdoE0+e3Oeb7o0DYqAlnN2axiozy74xUR8PkqUdfX
20+
CZwtvCqBHsP7U50dAKBxKRuZ150/BzgBAgAKjYtQXH6GhAiySjVnuMrAfrhbqMje
21+
0H9fP5hrSjdtdsKWZxYckZ4yxOoWnswPfQTQZ7VDRmXnqaxXUe2LMG/FktiolEqE
22+
XJqkP0FPBIOtXg==
23+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDzDCCAoCgAwIBAgICEAEwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEF
3+
AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgMDsxCzAJBgNVBAYT
4+
AkJFMRowGAYDVQQKDBFUZXN0aW5nIEF1dGhvcml0eTEQMA4GA1UEAwwHUm9vdCBD
5+
QTAiGA8yMDAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjBDMQswCQYDVQQG
6+
EwJCRTEaMBgGA1UECgwRVGVzdGluZyBBdXRob3JpdHkxGDAWBgNVBAMMD0ludGVy
7+
bWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHp2s/e
8+
iBXxSdjZgzQZEQ9QIIR7sVf3Tn85yXYyPI/KHf+9p/njYsZBZ0LWZ/d81Cwy7Z2p
9+
plPBRCsonAizTtBXuvWejDespQ0bxjIa11l8rK3qOA6x9pnkoHnixS1YbLgspPhw
10+
+rS8RaebyUWj3WS8mpU6MVdalRIrBM9glX6uIGYqDbkL7wPYvmgp0zNDF3yj0rSQ
11+
AjR8MXUI9uq0IH8YjPA3YKrbRtf12nNNtk6W8i6d0DiEtgE1x9eeKA8vKVzfHdjH
12+
KmgGi1Dkv0zEpx4GCu/VJ+C0QCrF5iBRl79RB0QbuNHLQkhgUpL8Ra8dwnqMBf6/
13+
riEp4gCyU7/rtNsCAwEAAaNmMGQwHQYDVR0OBBYEFO+/elGLLqQuSl3SzudheM5q
14+
NklPMB8GA1UdIwQYMBaAFPG0eZUCsrRfOXL9sI/CX69bmlC1MBIGA1UdEwEB/wQI
15+
MAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZI
16+
AWUDBAIBBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEA
17+
cBP88oJDFzHF5n63taaY1AKVZKrphwTWdqn2UQH13F065qfxIZcxZJgQArTd0nc5
18+
aVPsh/cXs5hv+bKsdsnkZeoEWSDmCdKh7rk5PcwFaX75ebWASrQ6JVSwJKlVI+3Y
19+
99wsqQ56PPBFYQmr3iNlC4RW079UHek+e1BnTizyUzutdVRZPqTEUqAYWB2ksqDG
20+
ICGi6zuzsZMOJc13uV27SL4hrLueNQnOPu2HMBZ/DWBRbvtkQeeYItPYqjmSVain
21+
ggAylz2v84tCxAVZC1OKpIOt5vRJxrpA9vpMvT1SlReC3ZXtHM0HpB4+SXH2PKuF
22+
lPWqtS+yvu2uSiUderYZ/g==
23+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)