diff options
-rw-r--r-- | docs/conf.py | 100 | ||||
-rw-r--r-- | docs/development/test-vectors.rst | 5 | ||||
-rw-r--r-- | docs/x509/reference.rst | 44 | ||||
-rw-r--r-- | src/_cffi_src/openssl/x509v3.py | 8 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/decode_asn1.py | 39 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/ciphers/base.py | 2 | ||||
-rw-r--r-- | src/cryptography/x509/__init__.py | 8 | ||||
-rw-r--r-- | src/cryptography/x509/extensions.py | 56 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 3 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 68 | ||||
-rw-r--r-- | vectors/cryptography_vectors/x509/custom/policy_constraints_explicit.pem | 17 | ||||
-rw-r--r-- | vectors/cryptography_vectors/x509/department-of-state-root.pem | 40 |
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 |