add SubjectInformationAccess extension support (#5295)

* add SubjectInformationAccess extension support

* fixes
This commit is contained in:
Paul Kehrer 2020-07-02 00:13:33 -05:00 committed by GitHub
parent 63d337e5cc
commit 13fae162da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 406 additions and 21 deletions

View file

@ -35,6 +35,8 @@ Changelog
* Added :meth:`~cryptography.fernet.Fernet.encrypt_at_time` and
:meth:`~cryptography.fernet.Fernet.decrypt_at_time` to
:class:`~cryptography.fernet.Fernet`.
* Added support for the :class:`~cryptography.x509.SubjectInformationAccess`
X.509 extension.
.. _v2-9-2:

View file

@ -401,6 +401,9 @@ Custom X.509 Vectors
a ``policyConstraints`` extension with a ``requireExplicitPolicy`` value.
* ``freshestcrl.pem`` - A self-signed certificate containing a ``freshestCRL``
extension.
* ``sia.pem`` - An RSA 2048 bit self-signed certificate containing a subject
information access extension with both a CA repository entry and a custom
OID entry.
* ``ca/ca.pem`` - A self-signed certificate with ``basicConstraints`` set to
true. Its private key is ``ca/ca_key.pem``. This certificate is encoded in
several of the PKCS12 custom vectors.

View file

@ -2146,6 +2146,29 @@ X.509 Extensions
:attr:`~cryptography.x509.oid.ExtensionOID.AUTHORITY_INFORMATION_ACCESS`.
.. class:: SubjectInformationAccess(descriptions)
.. versionadded:: 3.0
The subject information access extension indicates how to access
information and services for the subject of the certificate in which
the extension appears. When the subject is a CA, information and
services may include certificate validation services and CA policy
data. When the subject is an end entity, the information describes
the type of services offered and how to access them. It is an iterable,
containing one or more :class:`~cryptography.x509.AccessDescription`
instances.
:param list descriptions: A list of :class:`AccessDescription` objects.
.. attribute:: oid
:type: :class:`ObjectIdentifier`
Returns
:attr:`~cryptography.x509.oid.ExtensionOID.SUBJECT_INFORMATION_ACCESS`.
.. class:: AccessDescription(access_method, access_location)
.. versionadded:: 0.9
@ -2155,16 +2178,23 @@ X.509 Extensions
:type: :class:`ObjectIdentifier`
The access method defines what the ``access_location`` means. It must
be either
be
:attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` or
:attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS`.
:attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS`
when used with :class:`~cryptography.x509.AuthorityInformationAccess`
or
:attr:`~cryptography.x509.oid.SubjectInformationAccessOID.CA_REPOSITORY`
when used with :class:`~cryptography.x509.SubjectInformationAccess`.
If it is
:attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP`
the access location will be where to obtain OCSP
information for the certificate. If it is
:attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS`
the access location will provide additional information about the
issuing certificate.
issuing certificate. Finally, if it is
:attr:`~cryptography.x509.oid.SubjectInformationAccessOID.CA_REPOSITORY`
the access location will be the location of the CA's repository.
.. attribute:: access_location
@ -2973,6 +3003,17 @@ instances. The following common OIDs are available as constants.
:class:`~cryptography.x509.AccessDescription` objects.
.. class:: SubjectInformationAccessOID
.. versionadded:: 3.0
.. attribute:: CA_REPOSITORY
Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.5"``. Used as the
identifier for CA repository data in
:class:`~cryptography.x509.AccessDescription` objects.
.. class:: CertificatePoliciesOID
.. versionadded:: 1.0
@ -3050,6 +3091,14 @@ instances. The following common OIDs are available as constants.
for the :class:`~cryptography.x509.AuthorityInformationAccess` extension
type.
.. attribute:: SUBJECT_INFORMATION_ACCESS
.. versionadded:: 3.0
Corresponds to the dotted string ``"1.3.6.1.5.5.7.1.11"``. The
identifier for the :class:`~cryptography.x509.SubjectInformationAccess`
extension type.
.. attribute:: INHIBIT_ANY_POLICY
Corresponds to the dotted string ``"2.5.29.54"``. The identifier

View file

@ -377,29 +377,39 @@ def _decode_authority_key_identifier(backend, akid):
)
def _decode_authority_information_access(backend, aia):
aia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", aia)
aia = backend._ffi.gc(
aia,
def _decode_information_access(backend, ia):
ia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", ia)
ia = backend._ffi.gc(
ia,
lambda x: backend._lib.sk_ACCESS_DESCRIPTION_pop_free(
x, backend._ffi.addressof(
backend._lib._original_lib, "ACCESS_DESCRIPTION_free"
)
)
)
num = backend._lib.sk_ACCESS_DESCRIPTION_num(aia)
num = backend._lib.sk_ACCESS_DESCRIPTION_num(ia)
access_descriptions = []
for i in range(num):
ad = backend._lib.sk_ACCESS_DESCRIPTION_value(aia, i)
ad = backend._lib.sk_ACCESS_DESCRIPTION_value(ia, i)
backend.openssl_assert(ad.method != backend._ffi.NULL)
oid = x509.ObjectIdentifier(_obj2txt(backend, ad.method))
backend.openssl_assert(ad.location != backend._ffi.NULL)
gn = _decode_general_name(backend, ad.location)
access_descriptions.append(x509.AccessDescription(oid, gn))
return access_descriptions
def _decode_authority_information_access(backend, aia):
access_descriptions = _decode_information_access(backend, aia)
return x509.AuthorityInformationAccess(access_descriptions)
def _decode_subject_information_access(backend, aia):
access_descriptions = _decode_information_access(backend, aia)
return x509.SubjectInformationAccess(access_descriptions)
def _decode_key_usage(backend, bit_string):
bit_string = backend._ffi.cast("ASN1_BIT_STRING *", bit_string)
bit_string = backend._ffi.gc(bit_string, backend._lib.ASN1_BIT_STRING_free)
@ -816,6 +826,9 @@ _EXTENSION_HANDLERS_NO_SCT = {
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: (
_decode_authority_information_access
),
ExtensionOID.SUBJECT_INFORMATION_ACCESS: (
_decode_subject_information_access
),
ExtensionOID.CERTIFICATE_POLICIES: _decode_certificate_policies,
ExtensionOID.CRL_DISTRIBUTION_POINTS: _decode_crl_distribution_points,
ExtensionOID.FRESHEST_CRL: _decode_freshest_crl,

View file

@ -343,7 +343,7 @@ def _encode_basic_constraints(backend, basic_constraints):
return constraints
def _encode_authority_information_access(backend, authority_info_access):
def _encode_information_access(backend, info_access):
aia = backend._lib.sk_ACCESS_DESCRIPTION_new_null()
backend.openssl_assert(aia != backend._ffi.NULL)
aia = backend._ffi.gc(
@ -354,7 +354,7 @@ def _encode_authority_information_access(backend, authority_info_access):
)
)
)
for access_description in authority_info_access:
for access_description in info_access:
ad = backend._lib.ACCESS_DESCRIPTION_new()
method = _txt2obj(
backend, access_description.access_method.dotted_string
@ -622,9 +622,8 @@ _EXTENSION_ENCODE_HANDLERS = {
ExtensionOID.EXTENDED_KEY_USAGE: _encode_extended_key_usage,
ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier,
ExtensionOID.CERTIFICATE_POLICIES: _encode_certificate_policies,
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: (
_encode_authority_information_access
),
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _encode_information_access,
ExtensionOID.SUBJECT_INFORMATION_ACCESS: _encode_information_access,
ExtensionOID.CRL_DISTRIBUTION_POINTS: _encode_cdps_freshest_crl,
ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl,
ExtensionOID.INHIBIT_ANY_POLICY: _encode_inhibit_any_policy,
@ -636,9 +635,7 @@ _EXTENSION_ENCODE_HANDLERS = {
_CRL_EXTENSION_ENCODE_HANDLERS = {
ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name,
ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier,
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: (
_encode_authority_information_access
),
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _encode_information_access,
ExtensionOID.CRL_NUMBER: _encode_crl_number_delta_crl_indicator,
ExtensionOID.DELTA_CRL_INDICATOR: _encode_crl_number_delta_crl_indicator,
ExtensionOID.ISSUING_DISTRIBUTION_POINT: _encode_issuing_dist_point,

View file

@ -24,8 +24,8 @@ from cryptography.x509.extensions import (
IssuingDistributionPoint, KeyUsage, NameConstraints, NoticeReference,
OCSPNoCheck, OCSPNonce, PolicyConstraints, PolicyInformation,
PrecertPoison, PrecertificateSignedCertificateTimestamps, ReasonFlags,
SubjectAlternativeName, SubjectKeyIdentifier, TLSFeature, TLSFeatureType,
UnrecognizedExtension, UserNotice
SubjectAlternativeName, SubjectInformationAccess, SubjectKeyIdentifier,
TLSFeature, TLSFeatureType, UnrecognizedExtension, UserNotice
)
from cryptography.x509.general_name import (
DNSName, DirectoryName, GeneralName, IPAddress, OtherName, RFC822Name,
@ -142,6 +142,7 @@ __all__ = [
"CRLNumber",
"KeyUsage",
"AuthorityInformationAccess",
"SubjectInformationAccess",
"AccessDescription",
"CertificatePolicies",
"PolicyInformation",

View file

@ -317,6 +317,38 @@ class AuthorityInformationAccess(object):
return hash(tuple(self._descriptions))
@utils.register_interface(ExtensionType)
class SubjectInformationAccess(object):
oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS
def __init__(self, descriptions):
descriptions = list(descriptions)
if not all(isinstance(x, AccessDescription) for x in descriptions):
raise TypeError(
"Every item in the descriptions list must be an "
"AccessDescription"
)
self._descriptions = descriptions
__len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions")
def __repr__(self):
return "<SubjectInformationAccess({})>".format(self._descriptions)
def __eq__(self, other):
if not isinstance(other, SubjectInformationAccess):
return NotImplemented
return self._descriptions == other._descriptions
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash(tuple(self._descriptions))
class AccessDescription(object):
def __init__(self, access_method, access_location):
if not isinstance(access_method, ObjectIdentifier):

View file

@ -149,6 +149,10 @@ class AuthorityInformationAccessOID(object):
OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1")
class SubjectInformationAccessOID(object):
CA_REPOSITORY = ObjectIdentifier("1.3.6.1.5.5.7.48.5")
class CertificatePoliciesOID(object):
CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1")
CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2")
@ -251,6 +255,7 @@ _OID_NAMES = {
ExtensionOID.TLS_FEATURE: "TLSFeature",
AuthorityInformationAccessOID.OCSP: "OCSP",
AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers",
SubjectInformationAccessOID.CA_REPOSITORY: "caRepository",
CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps",
CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice",
OCSPExtensionOID.NONCE: "OCSPNonce",

View file

@ -36,7 +36,7 @@ from cryptography.hazmat.primitives.asymmetric.utils import (
from cryptography.x509.name import _ASN1Type
from cryptography.x509.oid import (
AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID,
NameOID, SignatureAlgorithmOID
NameOID, SignatureAlgorithmOID, SubjectInformationAccessOID,
)
from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048
@ -1718,6 +1718,40 @@ class TestCertificateBuilder(object):
builder.sign(private_key, hashes.SHA256(), backend)
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
def test_encode_nonstandard_sia(self, backend):
private_key = RSA_KEY_2048.private_key(backend)
sia = x509.SubjectInformationAccess([
x509.AccessDescription(
x509.ObjectIdentifier("2.999.7"),
x509.UniformResourceIdentifier(u"http://example.com")
),
])
builder = x509.CertificateBuilder().subject_name(x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
])).issuer_name(x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
])).public_key(
private_key.public_key()
).serial_number(
777
).not_valid_before(
datetime.datetime(2015, 1, 1)
).not_valid_after(
datetime.datetime(2040, 1, 1)
).add_extension(
sia, False
)
cert = builder.sign(private_key, hashes.SHA256(), backend)
ext = cert.extensions.get_extension_for_oid(
ExtensionOID.SUBJECT_INFORMATION_ACCESS
)
assert ext.value == sia
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
def test_subject_dn_asn1_types(self, backend):
@ -3708,6 +3742,45 @@ class TestCertificateSigningRequestBuilder(object):
)
assert ext.value == aia
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
def test_build_cert_with_sia(self, backend):
issuer_private_key = RSA_KEY_2048.private_key(backend)
subject_private_key = RSA_KEY_2048.private_key(backend)
not_valid_before = datetime.datetime(2002, 1, 1, 12, 1)
not_valid_after = datetime.datetime(2030, 12, 31, 8, 30)
sia = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
])
builder = x509.CertificateBuilder().serial_number(
777
).issuer_name(x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
])).subject_name(x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
])).public_key(
subject_private_key.public_key()
).add_extension(
sia, critical=False
).not_valid_before(
not_valid_before
).not_valid_after(
not_valid_after
)
cert = builder.sign(issuer_private_key, hashes.SHA256(), backend)
ext = cert.extensions.get_extension_for_oid(
ExtensionOID.SUBJECT_INFORMATION_ACCESS
)
assert ext.value == sia
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
def test_build_cert_with_ski(self, backend):

View file

@ -26,7 +26,7 @@ from cryptography.x509.extensions import _key_identifier_from_public_key
from cryptography.x509.general_name import _lazy_import_idna
from cryptography.x509.oid import (
AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID,
NameOID, ObjectIdentifier, _OID_NAMES
NameOID, ObjectIdentifier, SubjectInformationAccessOID, _OID_NAMES
)
from .test_x509 import _load_cert
@ -3052,6 +3052,198 @@ class TestAuthorityInformationAccess(object):
assert hash(aia) != hash(aia3)
class TestSubjectInformationAccess(object):
def test_invalid_descriptions(self):
with pytest.raises(TypeError):
x509.SubjectInformationAccess(["notanAccessDescription"])
def test_iter_len(self):
sia = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca2.domain.com")
)
])
assert len(sia) == 2
assert list(sia) == [
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca2.domain.com")
)
]
def test_iter_input(self):
desc = [
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
)
]
sia = x509.SubjectInformationAccess(iter(desc))
assert list(sia) == desc
def test_repr(self):
sia = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
)
])
if not six.PY2:
assert repr(sia) == (
"<SubjectInformationAccess([<AccessDescription(access_method"
"=<ObjectIdentifier(oid=1.3.6.1.5.5.7.48.5, name=caRepositor"
"y)>, access_location=<UniformResourceIdentifier(value='http"
"://ca.domain.com')>)>])>"
)
else:
assert repr(sia) == (
"<SubjectInformationAccess([<AccessDescription(access_method"
"=<ObjectIdentifier(oid=1.3.6.1.5.5.7.48.5, name=caRepositor"
"y)>, access_location=<UniformResourceIdentifier(value=u'htt"
"p://ca.domain.com')>)>])>"
)
def test_eq(self):
sia = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca2.domain.com")
)
])
sia2 = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca2.domain.com")
)
])
assert sia == sia2
def test_ne(self):
sia = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca2.domain.com")
)
])
sia2 = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
])
assert sia != sia2
assert sia != object()
def test_indexing(self):
sia = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca2.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca3.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca4.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca5.domain.com")
),
])
assert sia[-1] == sia[4]
assert sia[2:6:2] == [sia[2], sia[4]]
def test_hash(self):
sia = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca2.domain.com")
),
])
sia2 = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca2.domain.com")
),
])
sia3 = x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca.domain.com")
),
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"http://ca3.domain.com")
),
])
assert hash(sia) == hash(sia2)
assert hash(sia) != hash(sia3)
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
class TestSubjectInformationAccessExtension(object):
def test_sia(self, backend):
cert = _load_cert(
os.path.join("x509", "custom", "sia.pem"),
x509.load_pem_x509_certificate,
backend
)
ext = cert.extensions.get_extension_for_oid(
ExtensionOID.SUBJECT_INFORMATION_ACCESS
)
assert ext is not None
assert ext.critical is False
assert ext.value == x509.SubjectInformationAccess([
x509.AccessDescription(
SubjectInformationAccessOID.CA_REPOSITORY,
x509.UniformResourceIdentifier(u"https://my.ca.issuer/")
),
x509.AccessDescription(
x509.ObjectIdentifier("2.999.7"),
x509.UniformResourceIdentifier(u"gopher://info-mac-archive")
),
])
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
class TestAuthorityInformationAccessExtension(object):

View file

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC7TCCAdWgAwIBAgICAwkwDQYJKoZIhvcNAQELBQAwDTELMAkGA1UEBhMCVVMw
HhcNMTUwMTAxMDAwMDAwWhcNNDAwMTAxMDAwMDAwWjANMQswCQYDVQQGEwJVUzCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMF6/H53R0yqWqgwNhWKP/v3
tSFoUboiMOXWq/zBxs/vWekj6hMwvFk7c4Aqtgim5KMwZSOjEWulqjlmFFF04Tts
Sem3gGLkSdcu+xD9SekfoIuW0FHngun1q8W1pveYSCetuOc9oA8isu/c23bqtG7a
2Y7WVmJ0P9xsDjNqXQzbqn3CnlNjXiTIelssQhWWgGPN62ipcrq7wePP8A+5qA43
Kk0MLJINHozuMzzkcNwugUWtsFvymu4dJPFB6Mx4SYnFh/xvus2Xnz8hY8HXKZs2
W8cv/ihI6Weu0eSNzFFbOlDtTeBP0FOEbKEKIjsQzIQcyA/evuRPMRTBPohq9YMC
AwEAAaNXMFUwUwYIKwYBBQUHAQsERzBFMCEGCCsGAQUFBzAFhhVodHRwczovL215
LmNhLmlzc3Vlci8wIAYDiDcHhhlnb3BoZXI6Ly9pbmZvLW1hYy1hcmNoaXZlMA0G
CSqGSIb3DQEBCwUAA4IBAQB4AdYx02aXDJURPbZNi3j7FnK3LRVvJcq8vRHaG9b4
soD/7qA8RJX11WTFNDY7g5OQhYT+WBc8OUinJaqJOPvEzgp5Prgq5AlAtcImvNX7
dI3lr9esZ5gBWbsMK9saNEERhEZDUCSYW/GRMN4yxdUgTDPsfNr8N6bwfnGRR0xM
EBr+p+fT1xth4uren7J/edYrY9a171y6bMdZQ1iVnFH2dFO25D+3k9sM6FRWWsWu
mmrcg79QAl6jqC/6SkqVzpBPzi7dgGYluaKJjREC8e/cMcpphW1TP+8rZ161BmDk
hk5/PrWguFuguWUyEkPH5oqFqoZuqeM0fULxHh2JiqOx
-----END CERTIFICATE-----