aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/conf.py100
-rw-r--r--docs/development/test-vectors.rst5
-rw-r--r--docs/x509/reference.rst44
-rw-r--r--src/_cffi_src/openssl/x509v3.py8
-rw-r--r--src/cryptography/hazmat/backends/openssl/decode_asn1.py39
-rw-r--r--src/cryptography/hazmat/primitives/ciphers/base.py2
-rw-r--r--src/cryptography/x509/__init__.py8
-rw-r--r--src/cryptography/x509/extensions.py56
-rw-r--r--tests/hazmat/backends/test_openssl.py3
-rw-r--r--tests/test_x509_ext.py68
-rw-r--r--vectors/cryptography_vectors/x509/custom/policy_constraints_explicit.pem17
-rw-r--r--vectors/cryptography_vectors/x509/department-of-state-root.pem40
12 files changed, 276 insertions, 114 deletions
diff --git a/docs/conf.py b/docs/conf.py
index 643eddba..85a569a7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -116,10 +116,6 @@ exclude_patterns = ['_build']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
-# A list of ignored prefixes for module index sorting.
-# modindex_common_prefix = []
-
-
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
@@ -131,73 +127,11 @@ if sphinx_rtd_theme:
else:
html_theme = "default"
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-# html_theme_options = {}
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-# html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-# html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-# html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-# html_favicon = None
-
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-# html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-# html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-# html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-# html_additional_pages = {}
-
-# If false, no module index is generated.
-# html_domain_indices = True
-
-# If false, no index is generated.
-# html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-# html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-# html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-# html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-# html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-# html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-# html_file_suffix = None
-
# Output file base name for HTML help builder.
htmlhelp_basename = 'Cryptographydoc'
@@ -214,27 +148,6 @@ latex_documents = [
'Individual Contributors', 'manual'),
]
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-# latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-# latex_use_parts = False
-
-# If true, show page references after internal links.
-# latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-# latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-# latex_appendices = []
-
-# If false, no module index is generated.
-# latex_domain_indices = True
-
-
# -- Options for manual page output -------------------------------------------
# One entry per manual page. List of tuples
@@ -244,10 +157,6 @@ man_pages = [
['Individual Contributors'], 1)
]
-# If true, show URL addresses after external links.
-# man_show_urls = False
-
-
# -- Options for Texinfo output -----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
@@ -260,15 +169,6 @@ texinfo_documents = [
'Miscellaneous'),
]
-# Documents to append as an appendix to all manuals.
-# texinfo_appendices = []
-
-# If false, no module index is generated.
-# texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-# texinfo_show_urls = 'footnote'
-
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/3': None}
diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst
index ad945f2f..4d284197 100644
--- a/docs/development/test-vectors.rst
+++ b/docs/development/test-vectors.rst
@@ -110,6 +110,9 @@ X.509
containing a SAN extension with an ``ediPartyName`` general name.
* ``san_x400address.der`` - A DSA certificate from a `Mozilla bug`_ containing
a SAN extension with an ``x400Address`` general name.
+* ``department-of-state-root.pem`` - The intermediary CA for the Department of
+ State, issued by the United States Federal Government's Common Policy CA.
+ Notably has a ``critical`` policy constraints extensions.
Custom X.509 Vectors
~~~~~~~~~~~~~~~~~~~~
@@ -260,6 +263,8 @@ Custom X.509 Vectors
policy constraints extension with a require explicit policy element.
* ``unsupported_subject_public_key_info.pem`` - A certificate whose public key
is an unknown OID (``1.3.6.1.4.1.8432.1.1.2``).
+* ``policy_constraints_explicit.pem`` - A self-signed certificate containing
+ a ``policyConstraints`` extension with a ``requireExplicitPolicy`` value.
Custom X.509 Request Vectors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 8bb3f40d..529578ba 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -1860,6 +1860,44 @@ X.509 Extensions
:type: int
+.. class:: PolicyConstraints
+
+ .. versionadded:: 1.3
+
+ The policy constraints extension is used to inhibit policy mapping or
+ require that each certificate in a chain contain an acceptable policy
+ identifier. For more information about the use of this extension see
+ :rfc:`5280`.
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ Returns :attr:`~cryptography.x509.oid.ExtensionOID.POLICY_CONSTRAINTS`.
+
+ .. attribute:: require_explicit_policy
+
+ :type: int or None
+
+ If this field is not None, the value indicates the number of additional
+ certificates that may appear in the chain before an explicit policy is
+ required for the entire path. When an explicit policy is required, it
+ is necessary for all certificates in the chain to contain an acceptable
+ policy identifier in the certificate policies extension. An
+ acceptable policy identifier is the identifier of a policy required
+ by the user of the certification path or the identifier of a policy
+ that has been declared equivalent through policy mapping.
+
+ .. attribute:: inhibit_policy_mapping
+
+ :type: int or None
+
+ If this field is not None, the value indicates the number of additional
+ certificates that may appear in the chain before policy mapping is no
+ longer permitted. For example, a value of one indicates that policy
+ mapping may be processed in certificates issued by the subject of this
+ certificate, but not in additional certificates in the chain.
+
.. class:: CRLNumber(crl_number)
.. versionadded:: 1.2
@@ -2392,6 +2430,12 @@ instances. The following common OIDs are available as constants.
the ``CRLNumber`` extension type. This extension only has meaning
for certificate revocation lists.
+ .. attribute:: POLICY_CONSTRAINTS
+
+ Corresponds to the dotted string ``"2.5.29.36"``. The identifier for the
+ :class:`~cryptography.x509.PolicyConstraints` extension type.
+
+
.. class:: CRLEntryExtensionOID
.. versionadded:: 1.2
diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py
index 51c8410a..3612f1c2 100644
--- a/src/_cffi_src/openssl/x509v3.py
+++ b/src/_cffi_src/openssl/x509v3.py
@@ -78,6 +78,11 @@ typedef struct {
Cryptography_STACK_OF_GENERAL_SUBTREE *excludedSubtrees;
} NAME_CONSTRAINTS;
+typedef struct {
+ ASN1_INTEGER *requireExplicitPolicy;
+ ASN1_INTEGER *inhibitPolicyMapping;
+} POLICY_CONSTRAINTS;
+
typedef struct {
int type;
@@ -200,6 +205,9 @@ int Cryptography_i2d_NAME_CONSTRAINTS(NAME_CONSTRAINTS *, unsigned char **);
OTHERNAME *OTHERNAME_new(void);
void OTHERNAME_free(OTHERNAME *);
+POLICY_CONSTRAINTS *POLICY_CONSTRAINTS_new(void);
+void POLICY_CONSTRAINTS_free(POLICY_CONSTRAINTS *);
+
void *X509V3_set_ctx_nodb(X509V3_CTX *);
int i2d_GENERAL_NAMES(GENERAL_NAMES *, unsigned char **);
diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
index 42d6c858..5f828c6b 100644
--- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
@@ -320,10 +320,9 @@ def _decode_basic_constraints(backend, bc_st):
# chooses to just map this to its ordinal value, so true is 255 and
# false is 0.
ca = basic_constraints.ca == 255
- if basic_constraints.pathlen == backend._ffi.NULL:
- path_length = None
- else:
- path_length = _asn1_integer_to_int(backend, basic_constraints.pathlen)
+ path_length = _asn1_integer_to_int_or_none(
+ backend, basic_constraints.pathlen
+ )
return x509.BasicConstraints(ca, path_length)
@@ -343,7 +342,6 @@ def _decode_authority_key_identifier(backend, akid):
akid = backend._ffi.gc(akid, backend._lib.AUTHORITY_KEYID_free)
key_identifier = None
authority_cert_issuer = None
- authority_cert_serial_number = None
if akid.keyid != backend._ffi.NULL:
key_identifier = backend._ffi.buffer(
@@ -355,10 +353,9 @@ def _decode_authority_key_identifier(backend, akid):
backend, akid.issuer
)
- if akid.serial != backend._ffi.NULL:
- authority_cert_serial_number = _asn1_integer_to_int(
- backend, akid.serial
- )
+ authority_cert_serial_number = _asn1_integer_to_int_or_none(
+ backend, akid.serial
+ )
return x509.AuthorityKeyIdentifier(
key_identifier, authority_cert_issuer, authority_cert_serial_number
@@ -452,6 +449,22 @@ def _decode_general_subtrees(backend, stack_subtrees):
return subtrees
+def _decode_policy_constraints(backend, pc):
+ pc = backend._ffi.cast("POLICY_CONSTRAINTS *", pc)
+ pc = backend._ffi.gc(pc, backend._lib.POLICY_CONSTRAINTS_free)
+
+ require_explicit_policy = _asn1_integer_to_int_or_none(
+ backend, pc.requireExplicitPolicy
+ )
+ inhibit_policy_mapping = _asn1_integer_to_int_or_none(
+ backend, pc.inhibitPolicyMapping
+ )
+
+ return x509.PolicyConstraints(
+ require_explicit_policy, inhibit_policy_mapping
+ )
+
+
def _decode_extended_key_usage(backend, sk):
sk = backend._ffi.cast("Cryptography_STACK_OF_ASN1_OBJECT *", sk)
sk = backend._ffi.gc(sk, backend._lib.sk_ASN1_OBJECT_free)
@@ -675,6 +688,13 @@ def _asn1_integer_to_int(backend, asn1_int):
return backend._bn_to_int(bn)
+def _asn1_integer_to_int_or_none(backend, asn1_int):
+ if asn1_int == backend._ffi.NULL:
+ return None
+ else:
+ return _asn1_integer_to_int(backend, asn1_int)
+
+
def _asn1_string_to_bytes(backend, asn1_string):
return backend._ffi.buffer(asn1_string.data, asn1_string.length)[:]
@@ -729,6 +749,7 @@ _EXTENSION_HANDLERS = {
ExtensionOID.INHIBIT_ANY_POLICY: _decode_inhibit_any_policy,
ExtensionOID.ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name,
ExtensionOID.NAME_CONSTRAINTS: _decode_name_constraints,
+ ExtensionOID.POLICY_CONSTRAINTS: _decode_policy_constraints,
}
_REVOKED_EXTENSION_HANDLERS = {
diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py
index dae93655..496975ae 100644
--- a/src/cryptography/hazmat/primitives/ciphers/base.py
+++ b/src/cryptography/hazmat/primitives/ciphers/base.py
@@ -185,7 +185,7 @@ class _AEADCipherContext(object):
self._aad_bytes_processed += len(data)
if self._aad_bytes_processed > self._ctx._mode._MAX_AAD_BYTES:
raise ValueError(
- "{0} has a maximum AAD byte limit of {0}".format(
+ "{0} has a maximum AAD byte limit of {1}".format(
self._ctx._mode.name, self._ctx._mode._MAX_AAD_BYTES
)
)
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
index 787f1a60..8d7bad27 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -20,9 +20,10 @@ from cryptography.x509.extensions import (
DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension,
ExtensionNotFound, ExtensionType, Extensions, GeneralNames,
InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, KeyUsage,
- NameConstraints, NoticeReference, OCSPNoCheck, PolicyInformation,
- ReasonFlags, SubjectAlternativeName, SubjectKeyIdentifier,
- UnrecognizedExtension, UnsupportedExtension, UserNotice
+ NameConstraints, NoticeReference, OCSPNoCheck, PolicyConstraints,
+ PolicyInformation, ReasonFlags, SubjectAlternativeName,
+ SubjectKeyIdentifier, UnrecognizedExtension, UnsupportedExtension,
+ UserNotice
)
from cryptography.x509.general_name import (
DNSName, DirectoryName, GeneralName, IPAddress, OtherName, RFC822Name,
@@ -178,4 +179,5 @@ __all__ = [
"CRLReason",
"InvalidityDate",
"UnrecognizedExtension",
+ "PolicyConstraints",
]
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
index db55789e..0aa67212 100644
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -490,6 +490,62 @@ class ReasonFlags(Enum):
@utils.register_interface(ExtensionType)
+class PolicyConstraints(object):
+ oid = ExtensionOID.POLICY_CONSTRAINTS
+
+ def __init__(self, require_explicit_policy, inhibit_policy_mapping):
+ if require_explicit_policy is not None and not isinstance(
+ require_explicit_policy, six.integer_types
+ ):
+ raise TypeError(
+ "require_explicit_policy must be a non-negative integer or "
+ "None"
+ )
+
+ if inhibit_policy_mapping is not None and not isinstance(
+ inhibit_policy_mapping, six.integer_types
+ ):
+ raise TypeError(
+ "inhibit_policy_mapping must be a non-negative integer or None"
+ )
+
+ if inhibit_policy_mapping is None and require_explicit_policy is None:
+ raise ValueError(
+ "At least one of require_explicit_policy and "
+ "inhibit_policy_mapping must not be None"
+ )
+
+ self._require_explicit_policy = require_explicit_policy
+ self._inhibit_policy_mapping = inhibit_policy_mapping
+
+ def __repr__(self):
+ return (
+ u"<PolicyConstraints(require_explicit_policy={0.require_explicit"
+ u"_policy}, inhibit_policy_mapping={0.inhibit_policy_"
+ u"mapping})>".format(self)
+ )
+
+ def __eq__(self, other):
+ if not isinstance(other, PolicyConstraints):
+ return NotImplemented
+
+ return (
+ self.require_explicit_policy == other.require_explicit_policy and
+ self.inhibit_policy_mapping == other.inhibit_policy_mapping
+ )
+
+ def __ne__(self, other):
+ return not self == other
+
+ require_explicit_policy = utils.read_only_property(
+ "_require_explicit_policy"
+ )
+ inhibit_policy_mapping = utils.read_only_property(
+ "_inhibit_policy_mapping"
+ )
+
+
+@utils.register_interface(ExtensionType)
class CertificatePolicies(object):
oid = ExtensionOID.CERTIFICATE_POLICIES
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 0b55a485..f94b94ab 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -268,7 +268,8 @@ class TestOpenSSLRandomEngine(object):
subprocess.check_call(
[sys.executable, "-c", engine_printer],
env=env,
- stdout=out
+ stdout=out,
+ stderr=subprocess.PIPE,
)
osrandom_engine_name = backend._ffi.string(
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index d8a5f9de..d85b4bbc 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -2245,6 +2245,74 @@ class TestAccessDescription(object):
assert hash(ad) != hash(ad3)
+class TestPolicyConstraints(object):
+ def test_invalid_explicit_policy(self):
+ with pytest.raises(TypeError):
+ x509.PolicyConstraints("invalid", None)
+
+ def test_invalid_inhibit_policy(self):
+ with pytest.raises(TypeError):
+ x509.PolicyConstraints(None, "invalid")
+
+ def test_both_none(self):
+ with pytest.raises(ValueError):
+ x509.PolicyConstraints(None, None)
+
+ def test_repr(self):
+ pc = x509.PolicyConstraints(0, None)
+
+ assert repr(pc) == (
+ u"<PolicyConstraints(require_explicit_policy=0, inhibit_policy_ma"
+ u"pping=None)>"
+ )
+
+ def test_eq(self):
+ pc = x509.PolicyConstraints(2, 1)
+ pc2 = x509.PolicyConstraints(2, 1)
+ assert pc == pc2
+
+ def test_ne(self):
+ pc = x509.PolicyConstraints(2, 1)
+ pc2 = x509.PolicyConstraints(2, 2)
+ pc3 = x509.PolicyConstraints(3, 1)
+ assert pc != pc2
+ assert pc != pc3
+ assert pc != object()
+
+
+@pytest.mark.requires_backend_interface(interface=RSABackend)
+@pytest.mark.requires_backend_interface(interface=X509Backend)
+class TestPolicyConstraintsExtension(object):
+ def test_inhibit_policy_mapping(self, backend):
+ cert = _load_cert(
+ os.path.join("x509", "department-of-state-root.pem"),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ ext = cert.extensions.get_extension_for_oid(
+ ExtensionOID.POLICY_CONSTRAINTS,
+ )
+ assert ext.critical is True
+
+ assert ext.value == x509.PolicyConstraints(
+ require_explicit_policy=None, inhibit_policy_mapping=0,
+ )
+
+ def test_require_explicit_policy(self, backend):
+ cert = _load_cert(
+ os.path.join("x509", "custom", "policy_constraints_explicit.pem"),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ ext = cert.extensions.get_extension_for_oid(
+ ExtensionOID.POLICY_CONSTRAINTS
+ )
+ assert ext.critical is True
+ assert ext.value == x509.PolicyConstraints(
+ require_explicit_policy=1, inhibit_policy_mapping=None,
+ )
+
+
class TestAuthorityInformationAccess(object):
def test_invalid_descriptions(self):
with pytest.raises(TypeError):
diff --git a/vectors/cryptography_vectors/x509/custom/policy_constraints_explicit.pem b/vectors/cryptography_vectors/x509/custom/policy_constraints_explicit.pem
new file mode 100644
index 00000000..d6b5b29a
--- /dev/null
+++ b/vectors/cryptography_vectors/x509/custom/policy_constraints_explicit.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICzDCCAbSgAwIBAgITBogVd7/i/RGrRglqXbb7pbvn4TANBgkqhkiG9w0BAQUF
+ADAWMRQwEgYDVQQDDAtwb2xpY3kgdGVzdDAeFw0xNjAyMjcxMDI3NTFaFw0xNzAy
+MjYxMDI3NTFaMBYxFDASBgNVBAMMC3BvbGljeSB0ZXN0MIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAy07NK9FL62wNQo+eYrRUEUK7V4cvxV3h1jwmMS0V
+7Po3SYNlJYwbb/G4cMzMzNntC6NqxCY7Vi2Lz2r1TeOTfIp6nCzb7m+vi1kL5KMw
+5pMaXJTw2oRDjjtt+GokB6cXx5YUdKpRP5g583t2pRNzXsrLW8UVhPeY6y6SO2BN
+qdSq4RbHF7rlsJUNMg/H0FStoewE0G95gQHRs02vhxqZcDmJGCfXHg/9Lbpsw3hj
+wahNbYjabx6AFJItXsB/bOtb5uHVOlYyTyiyy7oaXxw6yop7GF1ocXFqPW0dhV+n
+V0HfjRvl4HHCq6URJtkX19jq8CN40eb4qX8XUCWK+ImWqwIDAQABoxMwETAPBgNV
+HSQBAf8EBTADgAEBMA0GCSqGSIb3DQEBBQUAA4IBAQAl+zzfU/KkwuixfoRBWE/2
+QGcFJkq2cOCI2q4b/PknuuEqRMn403/2tq2AU11WqowYjtsn64eD1w1dVeDGI/gz
+Na9U48+rahXIxt3V2Ou2QwvakHYyEmWPfGCivR+iAyE6JBrF4rngs9pKYkpRhgKe
+UICWEnnjXSk87oVqez5w/9StknxjD/7APE22zYeZ470v3Hs29GHX4UhxrvOmlW3u
+qVtkTYi9TFfGvVQYy4bkLakqAqIR3BqeWgogeKCMqO+shylpL94zIZ+2jO7vrHs5
+VGCS+L+tqBmIfQIYXQCE62ms/2sZT96804/nSYbxGXG5wH1Bi2wiBK3N9LAJLntW
+-----END CERTIFICATE-----
diff --git a/vectors/cryptography_vectors/x509/department-of-state-root.pem b/vectors/cryptography_vectors/x509/department-of-state-root.pem
new file mode 100644
index 00000000..6b6885c1
--- /dev/null
+++ b/vectors/cryptography_vectors/x509/department-of-state-root.pem
@@ -0,0 +1,40 @@
+-----BEGIN CERTIFICATE-----
+MIIG+DCCBeCgAwIBAgICITgwDQYJKoZIhvcNAQELBQAwWTELMAkGA1UEBhMCVVMx
+GDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDENMAsGA1UECxMERlBLSTEhMB8GA1UE
+AxMYRmVkZXJhbCBDb21tb24gUG9saWN5IENBMB4XDTE0MDgxMTE0MjUwMloXDTE3
+MDgxMTE0MjM1OVowgbExEzARBgoJkiaJk/IsZAEZFgNzYnUxFTATBgoJkiaJk/Is
+ZAEZFgVzdGF0ZTEWMBQGA1UEAxMNQ29uZmlndXJhdGlvbjERMA8GA1UEAxMIU2Vy
+dmljZXMxHDAaBgNVBAMTE1B1YmxpYyBLZXkgU2VydmljZXMxDDAKBgNVBAMTA0FJ
+QTEsMCoGA1UEAxMjVS5TLiBEZXBhcnRtZW50IG9mIFN0YXRlIEFEIFJvb3QgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5Nl+zsQXaSuJrw5d/SlwQ
+2Qopr9lvmlWvpRlutPSl7X5Dg5WSyU2V0u++JE0uprOhs+ZI9WS27MK+a32OmMzT
+g6HVzpO3curRiM/h5fJrAEBYFeXour0oUHYFwWcChi1k0mZkMrb4WO9+WppTFlIv
+9b7MSgaOoH5UTUNE5HAMMDgPVQGnIsxylftF7ikCyld45N764HDLdlfzw2j0RHn1
+ntEw2q0pp6vGbBN9/RPh97rOqJrKIr3ieE4Bzw3b+gAF4ymjcKOigl2lih8PJG5Z
+puGmPwOmWXh+rmwnzAM6pQmHp/xXDIPqJx5hvTfoOKsJhBiePgwQOc0blYgsXM7n
+AgMBAAGjggNvMIIDazAPBgNVHRMBAf8EBTADAQH/MGsGA1UdIARkMGIwDAYKYIZI
+AWUDAgEDAjAMBgpghkgBZQMCAQMNMAwGCmCGSAFlAwIBAwEwDAYKYIZIAWUDAgED
+BjAMBgpghkgBZQMCAQMHMAwGCmCGSAFlAwIBAwgwDAYKYIZIAWUDAgEDEDBPBggr
+BgEFBQcBAQRDMEEwPwYIKwYBBQUHMAKGM2h0dHA6Ly9odHRwLmZwa2kuZ292L2Zj
+cGNhL2NhQ2VydHNJc3N1ZWRUb2ZjcGNhLnA3YzCBjQYDVR0hBIGFMIGCMBgGCmCG
+SAFlAwIBAwEGCmCGSAFlAwIBBgEwGAYKYIZIAWUDAgEDAgYKYIZIAWUDAgEGAjAY
+BgpghkgBZQMCAQMGBgpghkgBZQMCAQYDMBgGCmCGSAFlAwIBAwcGCmCGSAFlAwIB
+BgwwGAYKYIZIAWUDAgEDEAYKYIZIAWUDAgEGBDCCATYGCCsGAQUFBwELBIIBKDCC
+ASQwQwYIKwYBBQUHMAWGN2h0dHA6Ly9jcmxzLnBraS5zdGF0ZS5nb3YvU0lBL0Nl
+cnRzSXNzdWVkQnlBRFJvb3RDQS5wN2MwgdwGCCsGAQUFBzAFhoHPbGRhcDovL2Nl
+cnRyZXAucGtpLnN0YXRlLmdvdi9jbj1VLlMuJTIwRGVwYXJ0bWVudCUyMG9mJTIw
+U3RhdGUlMjBBRCUyMFJvb3QlMjBDQSxjbj1BSUEsY249UHVibGljJTIwS2V5JTIw
+U2VydmljZXMsY249U2VydmljZXMsY249Q29uZmlndXJhdGlvbixkYz1zdGF0ZSxk
+Yz1zYnU/Y0FDZXJ0aWZpY2F0ZTtiaW5hcnksY3Jvc3NDZXJ0aWZpY2F0ZVBhaXI7
+YmluYXJ5MCkGA1UdHgEB/wQfMB2hGzAZpBcwFTETMBEGCgmSJomT8ixkARkWA21p
+bDAPBgNVHSQBAf8EBTADgQEAMA0GA1UdNgEB/wQDAgEAMA4GA1UdDwEB/wQEAwIB
+BjAfBgNVHSMEGDAWgBStDHp1XOXzmMR5mA6sKP2X9OcC/DA1BgNVHR8ELjAsMCqg
+KKAmhiRodHRwOi8vaHR0cC5mcGtpLmdvdi9mY3BjYS9mY3BjYS5jcmwwHQYDVR0O
+BBYEFG+D/oJQZGV3Pv3fA5rOKdEvMMzsMA0GCSqGSIb3DQEBCwUAA4IBAQBYnF0+
+cCv5Kbqafkn8hdljuUnhCDHKVVL7gysmZUCsIerzzklPWaCrWgTO+yRzIA7oHX4n
+o9sLVpru8evQL5IQVYUCnHIoCWPW9LIvt7eKJHi0KdTlbK5JTN6SNo1MVn1z1L+D
+15nUhuqs3b5FWxCl9AbO0V5tsiAIq6dNrhdfhUJIOLzDffM24HOC3wIDERAfZiPC
+LsK0nOHixW8dNQ8XsT6ZVJ4xhxJ9zW2O2wp/sWJhnzGUYd43IYH0AriHlByYJ3Ef
+vWtu74ypiI9C5xgnzW+rqze4DxG38+QC0V4kIB5adGghRD/qdPElw4hMCzbbZGGs
++OJGofrihzt0GyNM
+-----END CERTIFICATE----- \ No newline at end of file