diff options
-rw-r--r-- | CHANGELOG.rst | 27 | ||||
-rw-r--r-- | docs/development/test-vectors.rst | 2 | ||||
-rw-r--r-- | docs/installation.rst | 2 | ||||
-rw-r--r-- | docs/x509/reference.rst | 153 | ||||
-rw-r--r-- | src/_cffi_src/openssl/x509v3.py | 6 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/interfaces.py | 6 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/multibackend.py | 9 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 161 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/rsa.py | 5 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 154 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/serialization.py | 4 | ||||
-rw-r--r-- | src/cryptography/x509.py | 155 | ||||
-rw-r--r-- | tests/hazmat/backends/test_multibackend.py | 8 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 56 | ||||
-rw-r--r-- | tests/hazmat/bindings/test_openssl.py | 30 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_serialization.py | 10 | ||||
-rw-r--r-- | tests/test_utils.py | 5 | ||||
-rw-r--r-- | tests/test_x509.py | 478 | ||||
-rw-r--r-- | tests/test_x509_ext.py | 15 | ||||
-rw-r--r-- | tests/utils.py | 11 | ||||
-rw-r--r-- | vectors/cryptography_vectors/x509/custom/cp_invalid.pem | 16 |
21 files changed, 1159 insertions, 154 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 85f84477..f07a9087 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,8 +24,33 @@ Changelog and :class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHMAC`. * Raise a ``TypeError`` when passing objects that are not text as the value to :class:`~cryptography.x509.NameAttribute`. +* Add support for :class:`~cryptography.x509.OtherName` as a general name + type. +* Added new X.509 extension support in :class:`~cryptography.x509.Certificate` + The following new extensions are now supported: + + * :class:`~cryptography.x509.OCSPNoCheck` + * :class:`~cryptography.x509.InhibitAnyPolicy` + * :class:`~cryptography.x509.IssuerAlternativeName` + * :class:`~cryptography.x509.NameConstraints` + +* Extension support was added to + :class:`~cryptography.x509.CertificateSigningRequest`. * Add support for creating certificate signing requests with - :class:`~cryptography.x509.CertificateSigningRequestBuilder`. + :class:`~cryptography.x509.CertificateSigningRequestBuilder`. This includes + support for the following extensions: + + * :class:`~cryptography.x509.BasicConstraints` + * :class:`~cryptography.x509.ExtendedKeyUsage` + * :class:`~cryptography.x509.KeyUsage` + * :class:`~cryptography.x509.SubjectAlternativeName` + +* Add support for creating signed certificates with + :class:`~cryptography.x509.CertificateBuilder`. This includes support for + the following extensions + + * :class:`~cryptography.x509.BasicConstraints` + * :class:`~cryptography.x509.SubjectAlternativeName` 0.9.3 - 2015-07-09 ~~~~~~~~~~~~~~~~~~ diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index ea44a46c..d400e662 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -233,6 +233,8 @@ Custom X.509 Vectors * ``cp_user_notice_no_explicit_text.pem`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with a user notice with no explicit text. +* ``cp_invalid.pem`` - An RSA 2048 bit self-signed certificate containing a + certificate policies extension with invalid data. * ``ian_uri.pem`` - An RSA 2048 bit certificate containing an issuer alternative name extension with a ``URI`` general name. * ``ocsp_nocheck.pem`` - An RSA 2048 bit self-signed certificate containing diff --git a/docs/installation.rst b/docs/installation.rst index becab6b0..f7a88b98 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -142,7 +142,7 @@ To link cryptography against a custom version of OpenSSL you'll need to set .. code-block:: console $ brew install openssl - $ env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography + $ env ARCHFLAGS="-arch x86_64" LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography or `MacPorts`_: diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 9179468f..61971fed 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -388,6 +388,140 @@ X.509 CRL (Certificate Revocation List) Object The extensions encoded in the CRL. +X.509 Certificate Builder +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: CertificateBuilder + + .. versionadded:: 1.0 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> import datetime + >>> import uuid + >>> one_day = datetime.timedelta(1, 0, 0) + >>> private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> public_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ).public_key() + >>> builder = x509.CertificateBuilder() + >>> builder = builder.subject_name(x509.Name([ + ... x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ... ])) + >>> builder = builder.issuer_name(x509.Name([ + ... x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ... ])) + >>> builder = builder.not_valid_before(datetime.datetime.today() - one_day) + >>> builder = builder.not_valid_after(datetime.datetime(2018, 8, 2)) + >>> builder = builder.serial_number(int(uuid.uuid4())) + >>> builder = builder.public_key(public_key) + >>> builder = builder.add_extension( + ... x509.BasicConstraints(ca=False, path_length=None), critical=True, + ... ) + >>> certificate = builder.sign( + ... private_key=private_key, algorithm=hashes.SHA256(), + ... backend=default_backend() + ... ) + >>> isinstance(certificate, x509.Certificate) + True + + .. method:: issuer_name(name) + + Sets the issuer's distinguished name. + + :param name: The :class:`~cryptography.x509.Name` that describes the + issuer (CA). + + .. method:: subject_name(name) + + Sets the subject's distinguished name. + + :param name: The :class:`~cryptography.x509.Name` that describes the + subject. + + .. method:: public_key(public_key) + + Sets the subject's public key. + + :param public_key: The subject's public key. This can be one of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + + .. method:: serial_number(serial_number) + + Sets the certificate's serial number (an integer). The CA's policy + determines how it attributes serial numbers to certificates. The only + requirement is that this number uniquely identify the certificate given + the issuer. + + :param serial_number: Integer number that will be used by the CA to + identify this certificate (most notably during certificate + revocation checking). Users are encouraged to use a method of + generating 20 bytes of entropy, e.g., UUID4. For more information + on secure random number generation, see :doc:`/random-numbers`. + + .. method:: not_valid_before(time) + + Sets the certificate's activation time. This is the time from which + clients can start trusting the certificate. It may be different from + the time at which the certificate was created. + + :param time: The :class:`datetime.datetime` object (in UTC) that marks the + activation time for the certificate. The certificate may not be + trusted clients if it is used before this time. + + .. method:: not_valid_after(time) + + Sets the certificate's expiration time. This is the time from which + clients should no longer trust the certificate. The CA's policy will + determine how long the certificate should remain in use. + + :param time: The :class:`datetime.datetime` object (in UTC) that marks the + expiration time for the certificate. The certificate may not be + trusted clients if it is used after this time. + + .. method:: add_extension(extension, critical) + + Adds an X.509 extension to the certificate. + + :param extension: The extension to add to the certificate. Can be one + of :class:`~cryptography.x509.BasicConstraints` or + :class:`~cryptography.x509.SubjectAlternativeName`. + + :param critical: Set to ``True`` if the extension must be understood and + handled by whoever reads the certificate. + + .. method:: sign(private_key, algorithm, backend) + + Sign the certificate using the CA's private key. + + :param private_key: The + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + that will be used to sign the certificate. + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that + will be used to generate the signature. + + :param backend: Backend that will be used to build the certificate. + Must support the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + X.509 CSR (Certificate Signing Request) Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -431,6 +565,25 @@ X.509 CSR (Certificate Signing Request) Object >>> isinstance(csr.signature_hash_algorithm, hashes.SHA1) True + .. attribute:: extensions + + :type: :class:`Extensions` + + The extensions encoded in the certificate signing request. + + :raises cryptography.x509.DuplicateExtension: If more than one + extension of the same type is found within the certificate signing request. + + :raises cryptography.x509.UnsupportedExtension: If the certificate signing request + contains an extension that is not supported. + + :raises cryptography.x509.UnsupportedGeneralNameType: If an extension + contains a general name that is not supported. + + :raises UnicodeError: If an extension contains IDNA encoding that is + invalid or not compliant with IDNA 2008. + + .. method:: public_bytes(encoding) .. versionadded:: 1.0 diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index 89822b85..f6a18903 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -203,6 +203,9 @@ int i2d_GENERAL_NAMES(GENERAL_NAMES *, unsigned char **); int i2d_EXTENDED_KEY_USAGE(EXTENDED_KEY_USAGE *, unsigned char **); +int i2d_AUTHORITY_INFO_ACCESS(Cryptography_STACK_OF_ACCESS_DESCRIPTION *, + unsigned char **); + int sk_GENERAL_NAME_num(struct stack_st_GENERAL_NAME *); int sk_GENERAL_NAME_push(struct stack_st_GENERAL_NAME *, GENERAL_NAME *); GENERAL_NAME *sk_GENERAL_NAME_value(struct stack_st_GENERAL_NAME *, int); @@ -216,6 +219,9 @@ void sk_ACCESS_DESCRIPTION_free(Cryptography_STACK_OF_ACCESS_DESCRIPTION *); int sk_ACCESS_DESCRIPTION_push(Cryptography_STACK_OF_ACCESS_DESCRIPTION *, ACCESS_DESCRIPTION *); +ACCESS_DESCRIPTION *ACCESS_DESCRIPTION_new(void); +void ACCESS_DESCRIPTION_free(ACCESS_DESCRIPTION *); + X509_EXTENSION *X509V3_EXT_conf_nid(Cryptography_LHASH_OF_CONF_VALUE *, X509V3_CTX *, int, char *); diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 4d378e6b..49ccda18 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -280,6 +280,12 @@ class X509Backend(object): Create and sign an X.509 CSR from a CSR builder object. """ + @abc.abstractmethod + def sign_x509_certificate(self, builder, private_key, algorithm): + """ + Sign an X.509 Certificate from a CertificateBuilder object. + """ + @six.add_metaclass(abc.ABCMeta) class DHBackend(object): diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 6e911fd5..8008989e 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -351,3 +351,12 @@ class MultiBackend(object): "This backend does not support X.509.", _Reasons.UNSUPPORTED_X509 ) + + def sign_x509_certificate(self, builder, private_key, algorithm): + for b in self._filtered_backends(X509Backend): + return b.sign_x509_certificate(builder, private_key, algorithm) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index db4f963a..f9da9ea7 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function +import calendar import collections import itertools from contextlib import contextmanager @@ -78,6 +79,12 @@ def _encode_asn1_int(backend, x): return i +def _encode_asn1_int_gc(backend, x): + i = _encode_asn1_int(backend, x) + i = backend._ffi.gc(i, backend._lib.ASN1_INTEGER_free) + return i + + def _encode_asn1_str(backend, data, length): """ Create an ASN1_OCTET_STRING from a Python byte string. @@ -195,6 +202,32 @@ def _encode_basic_constraints(backend, basic_constraints): return pp, r +def _encode_authority_information_access(backend, authority_info_access): + aia = backend._lib.sk_ACCESS_DESCRIPTION_new_null() + assert aia != backend._ffi.NULL + aia = backend._ffi.gc( + aia, backend._lib.sk_ACCESS_DESCRIPTION_free + ) + for access_description in authority_info_access: + ad = backend._lib.ACCESS_DESCRIPTION_new() + method = _txt2obj( + backend, access_description.access_method.dotted_string + ) + gn = _encode_general_name(backend, access_description.access_location) + ad.method = method + ad.location = gn + res = backend._lib.sk_ACCESS_DESCRIPTION_push(aia, ad) + assert res >= 1 + + pp = backend._ffi.new('unsigned char **') + r = backend._lib.i2d_AUTHORITY_INFO_ACCESS(aia, pp) + assert r > 0 + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + def _encode_subject_alt_name(backend, san): general_names = backend._lib.GENERAL_NAMES_new() assert general_names != backend._ffi.NULL @@ -1063,6 +1096,114 @@ class Backend(object): return _CertificateSigningRequest(self, x509_req) + def sign_x509_certificate(self, builder, private_key, algorithm): + if not isinstance(builder, x509.CertificateBuilder): + raise TypeError('Builder type mismatch.') + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError('Algorithm must be a registered hash algorithm.') + + if self._lib.OPENSSL_VERSION_NUMBER <= 0x10001000: + if isinstance(private_key, _DSAPrivateKey): + raise NotImplementedError( + "Certificate signatures aren't implemented for DSA" + " keys on OpenSSL versions less than 1.0.1." + ) + if isinstance(private_key, _EllipticCurvePrivateKey): + raise NotImplementedError( + "Certificate signatures aren't implemented for EC" + " keys on OpenSSL versions less than 1.0.1." + ) + + # Resolve the signature algorithm. + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode('ascii') + ) + assert evp_md != self._ffi.NULL + + # Create an empty certificate. + x509_cert = self._lib.X509_new() + x509_cert = self._ffi.gc(x509_cert, backend._lib.X509_free) + + # Set the x509 version. + res = self._lib.X509_set_version(x509_cert, builder._version.value) + assert res == 1 + + # Set the subject's name. + res = self._lib.X509_set_subject_name( + x509_cert, _encode_name(self, list(builder._subject_name)) + ) + assert res == 1 + + # Set the subject's public key. + res = self._lib.X509_set_pubkey( + x509_cert, builder._public_key._evp_pkey + ) + assert res == 1 + + # Set the certificate serial number. + serial_number = _encode_asn1_int_gc(self, builder._serial_number) + res = self._lib.X509_set_serialNumber(x509_cert, serial_number) + assert res == 1 + + # Set the "not before" time. + res = self._lib.ASN1_TIME_set( + self._lib.X509_get_notBefore(x509_cert), + calendar.timegm(builder._not_valid_before.timetuple()) + ) + assert res != self._ffi.NULL + + # Set the "not after" time. + res = self._lib.ASN1_TIME_set( + self._lib.X509_get_notAfter(x509_cert), + calendar.timegm(builder._not_valid_after.timetuple()) + ) + assert res != self._ffi.NULL + + # Add extensions. + for i, extension in enumerate(builder._extensions): + if isinstance(extension.value, x509.BasicConstraints): + pp, r = _encode_basic_constraints(self, extension.value) + elif isinstance(extension.value, x509.KeyUsage): + pp, r = _encode_key_usage(self, extension.value) + elif isinstance(extension.value, x509.ExtendedKeyUsage): + pp, r = _encode_extended_key_usage(self, extension.value) + elif isinstance(extension.value, x509.SubjectAlternativeName): + pp, r = _encode_subject_alt_name(self, extension.value) + elif isinstance(extension.value, x509.AuthorityInformationAccess): + pp, r = _encode_authority_information_access( + self, extension.value + ) + else: + raise NotImplementedError('Extension not yet supported.') + + obj = _txt2obj(self, extension.oid.dotted_string) + extension = self._lib.X509_EXTENSION_create_by_OBJ( + self._ffi.NULL, + obj, + 1 if extension.critical else 0, + _encode_asn1_str_gc(self, pp[0], r) + ) + res = self._lib.X509_add_ext(x509_cert, extension, i) + assert res == 1 + + # Set the issuer name. + res = self._lib.X509_set_issuer_name( + x509_cert, _encode_name(self, list(builder._issuer_name)) + ) + assert res == 1 + + # Sign the certificate with the issuer's private key. + res = self._lib.X509_sign( + x509_cert, private_key._evp_pkey, evp_md + ) + if res == 0: + errors = self._consume_errors() + assert errors[0][1] == self._lib.ERR_LIB_RSA + assert errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY + raise ValueError("Digest too big for RSA key") + + return _Certificate(self, x509_cert) + def load_pem_private_key(self, data, password): return self._load_key( self._lib.PEM_read_bio_PrivateKey, @@ -1577,13 +1718,15 @@ class Backend(object): if format is serialization.PrivateFormat.PKCS8: write_bio = self._lib.PEM_write_bio_PKCS8PrivateKey key = evp_pkey - elif format is serialization.PrivateFormat.TraditionalOpenSSL: + else: + assert format is serialization.PrivateFormat.TraditionalOpenSSL if evp_pkey.type == self._lib.EVP_PKEY_RSA: write_bio = self._lib.PEM_write_bio_RSAPrivateKey elif evp_pkey.type == self._lib.EVP_PKEY_DSA: write_bio = self._lib.PEM_write_bio_DSAPrivateKey - elif (self._lib.Cryptography_HAS_EC == 1 and - evp_pkey.type == self._lib.EVP_PKEY_EC): + else: + assert self._lib.Cryptography_HAS_EC == 1 + assert evp_pkey.type == self._lib.EVP_PKEY_EC write_bio = self._lib.PEM_write_bio_ECPrivateKey key = cdata @@ -1600,7 +1743,8 @@ class Backend(object): return self._private_key_bytes_traditional_der( evp_pkey.type, cdata ) - elif format is serialization.PrivateFormat.PKCS8: + else: + assert format is serialization.PrivateFormat.PKCS8 write_bio = self._lib.i2d_PKCS8PrivateKey_bio key = evp_pkey else: @@ -1625,7 +1769,8 @@ class Backend(object): elif (self._lib.Cryptography_HAS_EC == 1 and key_type == self._lib.EVP_PKEY_EC): write_bio = self._lib.i2d_ECPrivateKey_bio - elif key_type == self._lib.EVP_PKEY_DSA: + else: + assert key_type == self._lib.EVP_PKEY_DSA write_bio = self._lib.i2d_DSAPrivateKey_bio bio = self._create_mem_bio() @@ -1640,7 +1785,8 @@ class Backend(object): if format is serialization.PublicFormat.SubjectPublicKeyInfo: if encoding is serialization.Encoding.PEM: write_bio = self._lib.PEM_write_bio_PUBKEY - elif encoding is serialization.Encoding.DER: + else: + assert encoding is serialization.Encoding.DER write_bio = self._lib.i2d_PUBKEY_bio key = evp_pkey @@ -1649,7 +1795,8 @@ class Backend(object): assert evp_pkey.type == self._lib.EVP_PKEY_RSA if encoding is serialization.Encoding.PEM: write_bio = self._lib.PEM_write_bio_RSAPublicKey - elif encoding is serialization.Encoding.DER: + else: + assert encoding is serialization.Encoding.DER write_bio = self._lib.i2d_RSAPublicKey_bio key = cdata diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 21414c05..822c7304 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -268,8 +268,9 @@ class _RSASignatureContext(object): self._backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE): reason = ("Salt length too long for key size. Try using " "MAX_LENGTH instead.") - elif (errors[0].reason == - self._backend._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY): + else: + assert (errors[0].reason == + self._backend._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY) reason = "Digest too large for key size. Use a larger key." assert reason is not None raise ValueError(reason) diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 493abc83..ee9a3bbf 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -234,7 +234,15 @@ class _X509ExtensionParser(object): "{0} is not currently supported".format(oid), oid ) else: - value = handler(backend, ext) + d2i = backend._lib.X509V3_EXT_d2i(ext) + if d2i == backend._ffi.NULL: + backend._consume_errors() + raise ValueError( + "The {0} extension is invalid and can't be " + "parsed".format(oid) + ) + + value = handler(backend, d2i) extensions.append(x509.Extension(oid, critical, value)) seen_oids.add(oid) @@ -358,12 +366,8 @@ class _Certificate(object): return self._backend._read_mem_bio(bio) -def _decode_certificate_policies(backend, ext): - cp = backend._ffi.cast( - "Cryptography_STACK_OF_POLICYINFO *", - backend._lib.X509V3_EXT_d2i(ext) - ) - assert cp != backend._ffi.NULL +def _decode_certificate_policies(backend, cp): + cp = backend._ffi.cast("Cryptography_STACK_OF_POLICYINFO *", cp) cp = backend._ffi.gc(cp, backend._lib.sk_POLICYINFO_free) num = backend._lib.sk_POLICYINFO_num(cp) certificate_policies = [] @@ -386,7 +390,8 @@ def _decode_certificate_policies(backend, ext): pqi.d.cpsuri.data, pqi.d.cpsuri.length )[:].decode('ascii') qualifiers.append(cpsuri) - elif pqualid == x509.OID_CPS_USER_NOTICE: + else: + assert pqualid == x509.OID_CPS_USER_NOTICE user_notice = _decode_user_notice( backend, pqi.d.usernotice ) @@ -431,12 +436,8 @@ def _decode_user_notice(backend, un): return x509.UserNotice(notice_reference, explicit_text) -def _decode_basic_constraints(backend, ext): - bc_st = backend._lib.X509V3_EXT_d2i(ext) - assert bc_st != backend._ffi.NULL - basic_constraints = backend._ffi.cast( - "BASIC_CONSTRAINTS *", bc_st - ) +def _decode_basic_constraints(backend, bc_st): + basic_constraints = backend._ffi.cast("BASIC_CONSTRAINTS *", bc_st) basic_constraints = backend._ffi.gc( basic_constraints, backend._lib.BASIC_CONSTRAINTS_free ) @@ -447,19 +448,13 @@ def _decode_basic_constraints(backend, ext): 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(backend, basic_constraints.pathlen) return x509.BasicConstraints(ca, path_length) -def _decode_subject_key_identifier(backend, ext): - asn1_string = backend._lib.X509V3_EXT_d2i(ext) - assert asn1_string != backend._ffi.NULL - asn1_string = backend._ffi.cast( - "ASN1_OCTET_STRING *", asn1_string - ) +def _decode_subject_key_identifier(backend, asn1_string): + asn1_string = backend._ffi.cast("ASN1_OCTET_STRING *", asn1_string) asn1_string = backend._ffi.gc( asn1_string, backend._lib.ASN1_OCTET_STRING_free ) @@ -468,13 +463,9 @@ def _decode_subject_key_identifier(backend, ext): ) -def _decode_authority_key_identifier(backend, ext): - akid = backend._lib.X509V3_EXT_d2i(ext) - assert akid != backend._ffi.NULL +def _decode_authority_key_identifier(backend, akid): akid = backend._ffi.cast("AUTHORITY_KEYID *", akid) - akid = backend._ffi.gc( - akid, backend._lib.AUTHORITY_KEYID_free - ) + akid = backend._ffi.gc(akid, backend._lib.AUTHORITY_KEYID_free) key_identifier = None authority_cert_issuer = None authority_cert_serial_number = None @@ -499,15 +490,9 @@ def _decode_authority_key_identifier(backend, ext): ) -def _decode_authority_information_access(backend, ext): - aia = backend._lib.X509V3_EXT_d2i(ext) - assert aia != backend._ffi.NULL - aia = backend._ffi.cast( - "Cryptography_STACK_OF_ACCESS_DESCRIPTION *", aia - ) - aia = backend._ffi.gc( - aia, backend._lib.sk_ACCESS_DESCRIPTION_free - ) +def _decode_authority_information_access(backend, aia): + aia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", aia) + aia = backend._ffi.gc(aia, backend._lib.sk_ACCESS_DESCRIPTION_free) num = backend._lib.sk_ACCESS_DESCRIPTION_num(aia) access_descriptions = [] for i in range(num): @@ -521,13 +506,9 @@ def _decode_authority_information_access(backend, ext): return x509.AuthorityInformationAccess(access_descriptions) -def _decode_key_usage(backend, ext): - bit_string = backend._lib.X509V3_EXT_d2i(ext) - assert bit_string != backend._ffi.NULL +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 - ) + bit_string = backend._ffi.gc(bit_string, backend._lib.ASN1_BIT_STRING_free) get_bit = backend._lib.ASN1_BIT_STRING_get_bit digital_signature = get_bit(bit_string, 0) == 1 content_commitment = get_bit(bit_string, 1) == 1 @@ -551,11 +532,8 @@ def _decode_key_usage(backend, ext): ) -def _decode_general_names_extension(backend, ext): - gns = backend._ffi.cast( - "GENERAL_NAMES *", backend._lib.X509V3_EXT_d2i(ext) - ) - assert gns != backend._ffi.NULL +def _decode_general_names_extension(backend, gns): + gns = backend._ffi.cast("GENERAL_NAMES *", gns) gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free) general_names = _decode_general_names(backend, gns) return general_names @@ -573,11 +551,8 @@ def _decode_issuer_alt_name(backend, ext): ) -def _decode_name_constraints(backend, ext): - nc = backend._ffi.cast( - "NAME_CONSTRAINTS *", backend._lib.X509V3_EXT_d2i(ext) - ) - assert nc != backend._ffi.NULL +def _decode_name_constraints(backend, nc): + nc = backend._ffi.cast("NAME_CONSTRAINTS *", nc) nc = backend._ffi.gc(nc, backend._lib.NAME_CONSTRAINTS_free) permitted = _decode_general_subtrees(backend, nc.permittedSubtrees) excluded = _decode_general_subtrees(backend, nc.excludedSubtrees) @@ -602,12 +577,8 @@ def _decode_general_subtrees(backend, stack_subtrees): return subtrees -def _decode_extended_key_usage(backend, ext): - sk = backend._ffi.cast( - "Cryptography_STACK_OF_ASN1_OBJECT *", - backend._lib.X509V3_EXT_d2i(ext) - ) - assert sk != backend._ffi.NULL +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) num = backend._lib.sk_ASN1_OBJECT_num(sk) ekus = [] @@ -621,14 +592,9 @@ def _decode_extended_key_usage(backend, ext): return x509.ExtendedKeyUsage(ekus) -def _decode_crl_distribution_points(backend, ext): - cdps = backend._ffi.cast( - "Cryptography_STACK_OF_DIST_POINT *", - backend._lib.X509V3_EXT_d2i(ext) - ) - assert cdps != backend._ffi.NULL - cdps = backend._ffi.gc( - cdps, backend._lib.sk_DIST_POINT_free) +def _decode_crl_distribution_points(backend, cdps): + cdps = backend._ffi.cast("Cryptography_STACK_OF_DIST_POINT *", cdps) + cdps = backend._ffi.gc(cdps, backend._lib.sk_DIST_POINT_free) num = backend._lib.sk_DIST_POINT_num(cdps) dist_points = [] @@ -716,12 +682,8 @@ def _decode_crl_distribution_points(backend, ext): return x509.CRLDistributionPoints(dist_points) -def _decode_inhibit_any_policy(backend, ext): - asn1_int = backend._ffi.cast( - "ASN1_INTEGER *", - backend._lib.X509V3_EXT_d2i(ext) - ) - assert asn1_int != backend._ffi.NULL +def _decode_inhibit_any_policy(backend, asn1_int): + asn1_int = backend._ffi.cast("ASN1_INTEGER *", asn1_int) asn1_int = backend._ffi.gc(asn1_int, backend._lib.ASN1_INTEGER_free) skip_certs = _asn1_integer_to_int(backend, asn1_int) return x509.InhibitAnyPolicy(skip_certs) @@ -789,35 +751,33 @@ class _CertificateSigningRequest(object): return self._backend._read_mem_bio(bio) +_EXTENSION_HANDLERS = { + x509.OID_BASIC_CONSTRAINTS: _decode_basic_constraints, + x509.OID_SUBJECT_KEY_IDENTIFIER: _decode_subject_key_identifier, + x509.OID_KEY_USAGE: _decode_key_usage, + x509.OID_SUBJECT_ALTERNATIVE_NAME: _decode_subject_alt_name, + x509.OID_EXTENDED_KEY_USAGE: _decode_extended_key_usage, + x509.OID_AUTHORITY_KEY_IDENTIFIER: _decode_authority_key_identifier, + x509.OID_AUTHORITY_INFORMATION_ACCESS: ( + _decode_authority_information_access + ), + x509.OID_CERTIFICATE_POLICIES: _decode_certificate_policies, + x509.OID_CRL_DISTRIBUTION_POINTS: _decode_crl_distribution_points, + x509.OID_OCSP_NO_CHECK: _decode_ocsp_no_check, + x509.OID_INHIBIT_ANY_POLICY: _decode_inhibit_any_policy, + x509.OID_ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name, + x509.OID_NAME_CONSTRAINTS: _decode_name_constraints, +} + + _CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x), get_ext=lambda backend, x, i: backend._lib.X509_get_ext(x, i), - handlers={ - x509.OID_BASIC_CONSTRAINTS: _decode_basic_constraints, - x509.OID_SUBJECT_KEY_IDENTIFIER: _decode_subject_key_identifier, - x509.OID_KEY_USAGE: _decode_key_usage, - x509.OID_SUBJECT_ALTERNATIVE_NAME: _decode_subject_alt_name, - x509.OID_EXTENDED_KEY_USAGE: _decode_extended_key_usage, - x509.OID_AUTHORITY_KEY_IDENTIFIER: _decode_authority_key_identifier, - x509.OID_AUTHORITY_INFORMATION_ACCESS: ( - _decode_authority_information_access - ), - x509.OID_CERTIFICATE_POLICIES: _decode_certificate_policies, - x509.OID_CRL_DISTRIBUTION_POINTS: _decode_crl_distribution_points, - x509.OID_OCSP_NO_CHECK: _decode_ocsp_no_check, - x509.OID_INHIBIT_ANY_POLICY: _decode_inhibit_any_policy, - x509.OID_ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name, - x509.OID_NAME_CONSTRAINTS: _decode_name_constraints, - } + handlers=_EXTENSION_HANDLERS ) _CSR_EXTENSION_PARSER = _X509ExtensionParser( ext_count=lambda backend, x: backend._lib.sk_X509_EXTENSION_num(x), get_ext=lambda backend, x, i: backend._lib.sk_X509_EXTENSION_value(x, i), - handlers={ - x509.OID_BASIC_CONSTRAINTS: _decode_basic_constraints, - x509.OID_KEY_USAGE: _decode_key_usage, - x509.OID_SUBJECT_ALTERNATIVE_NAME: _decode_subject_alt_name, - x509.OID_EXTENDED_KEY_USAGE: _decode_extended_key_usage, - } + handlers=_EXTENSION_HANDLERS ) diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index 098b31dc..fc50456e 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -33,9 +33,9 @@ def load_der_public_key(data, backend): def load_ssh_public_key(data, backend): - key_parts = data.split(b' ') + key_parts = data.split(b' ', 2) - if len(key_parts) != 2 and len(key_parts) != 3: + if len(key_parts) < 2: raise ValueError( 'Key is not in the proper format or contains extra data.') diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 75552fc1..978eb560 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import abc +import datetime import ipaddress from email.utils import parseaddr from enum import Enum @@ -17,6 +18,7 @@ from six.moves import urllib_parse from cryptography import utils from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa _OID_NAMES = { @@ -95,6 +97,8 @@ _GENERAL_NAMES = { 8: "registeredID", } +_UNIX_EPOCH = datetime.datetime(1970, 1, 1) + class Version(Enum): v1 = 0 @@ -1598,3 +1602,154 @@ class CertificateSigningRequestBuilder(object): if self._subject_name is None: raise ValueError("A CertificateSigningRequest must have a subject") return backend.create_x509_csr(self, private_key, algorithm) + + +class CertificateBuilder(object): + def __init__(self, issuer_name=None, subject_name=None, + public_key=None, serial_number=None, not_valid_before=None, + not_valid_after=None, extensions=[]): + self._version = Version.v3 + self._issuer_name = issuer_name + self._subject_name = subject_name + self._public_key = public_key + self._serial_number = serial_number + self._not_valid_before = not_valid_before + self._not_valid_after = not_valid_after + self._extensions = extensions + + def issuer_name(self, name): + """ + Sets the CA's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + if self._issuer_name is not None: + raise ValueError('The issuer name may only be set once.') + return CertificateBuilder( + name, self._subject_name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def subject_name(self, name): + """ + Sets the requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + if self._subject_name is not None: + raise ValueError('The subject name may only be set once.') + return CertificateBuilder( + self._issuer_name, name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def public_key(self, key): + """ + Sets the requestor's public key (as found in the signing request). + """ + if not isinstance(key, (dsa.DSAPublicKey, rsa.RSAPublicKey, + ec.EllipticCurvePublicKey)): + raise TypeError('Expecting one of DSAPublicKey, RSAPublicKey,' + ' or EllipticCurvePublicKey.') + if self._public_key is not None: + raise ValueError('The public key may only be set once.') + return CertificateBuilder( + self._issuer_name, self._subject_name, key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def serial_number(self, number): + """ + Sets the certificate serial number. + """ + if not isinstance(number, six.integer_types): + raise TypeError('Serial number must be of integral type.') + if self._serial_number is not None: + raise ValueError('The serial number may only be set once.') + if number < 0: + raise ValueError('The serial number should be non-negative.') + if utils.bit_length(number) > 160: # As defined in RFC 5280 + raise ValueError('The serial number should not be more than 160 ' + 'bits.') + return CertificateBuilder( + self._issuer_name, self._subject_name, + self._public_key, number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def not_valid_before(self, time): + """ + Sets the certificate activation time. + """ + if not isinstance(time, datetime.datetime): + raise TypeError('Expecting datetime object.') + if self._not_valid_before is not None: + raise ValueError('The not valid before may only be set once.') + if time <= _UNIX_EPOCH: + raise ValueError('The not valid before date must be after the unix' + ' epoch (1970 January 1).') + return CertificateBuilder( + self._issuer_name, self._subject_name, + self._public_key, self._serial_number, time, + self._not_valid_after, self._extensions + ) + + def not_valid_after(self, time): + """ + Sets the certificate expiration time. + """ + if not isinstance(time, datetime.datetime): + raise TypeError('Expecting datetime object.') + if self._not_valid_after is not None: + raise ValueError('The not valid after may only be set once.') + if time <= _UNIX_EPOCH: + raise ValueError('The not valid after date must be after the unix' + ' epoch (1970 January 1).') + return CertificateBuilder( + self._issuer_name, self._subject_name, + self._public_key, self._serial_number, self._not_valid_before, + time, self._extensions + ) + + def add_extension(self, extension, critical): + """ + Adds an X.509 extension to the certificate. + """ + if isinstance(extension, BasicConstraints): + extension = Extension(OID_BASIC_CONSTRAINTS, critical, extension) + elif isinstance(extension, KeyUsage): + extension = Extension(OID_KEY_USAGE, critical, extension) + elif isinstance(extension, ExtendedKeyUsage): + extension = Extension(OID_EXTENDED_KEY_USAGE, critical, extension) + elif isinstance(extension, SubjectAlternativeName): + extension = Extension( + OID_SUBJECT_ALTERNATIVE_NAME, critical, extension + ) + elif isinstance(extension, AuthorityInformationAccess): + extension = Extension( + OID_AUTHORITY_INFORMATION_ACCESS, critical, extension + ) + elif isinstance(extension, InhibitAnyPolicy): + extension = Extension(OID_INHIBIT_ANY_POLICY, critical, extension) + else: + raise NotImplementedError('Unsupported X.509 extension.') + + # TODO: This is quadratic in the number of extensions + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + + return CertificateBuilder( + self._issuer_name, self._subject_name, + self._public_key, self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + [extension] + ) + + def sign(self, private_key, algorithm, backend): + """ + Signs the certificate using the CA's private key. + """ + return backend.sign_x509_certificate(self, private_key, algorithm) diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index 3c05cdfa..d516af16 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -206,6 +206,9 @@ class DummyX509Backend(object): def create_x509_csr(self, builder, private_key, algorithm): pass + def sign_x509_certificate(self, builder, private_key, algorithm): + pass + class TestMultiBackend(object): def test_ciphers(self): @@ -484,6 +487,7 @@ class TestMultiBackend(object): backend.load_pem_x509_csr(b"reqdata") backend.load_der_x509_csr(b"reqdata") backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) + backend.sign_x509_certificate(object(), b"privatekey", hashes.SHA1()) backend = MultiBackend([]) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): @@ -496,3 +500,7 @@ class TestMultiBackend(object): backend.load_der_x509_csr(b"reqdata") with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.sign_x509_certificate( + object(), b"privatekey", hashes.SHA1() + ) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 6a2e8a77..0f2c80a6 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function +import datetime import os import subprocess import sys @@ -14,6 +15,7 @@ import pretend import pytest from cryptography import utils +from cryptography import x509 from cryptography.exceptions import InternalError, _Reasons from cryptography.hazmat.backends.interfaces import RSABackend from cryptography.hazmat.backends.openssl.backend import ( @@ -34,6 +36,20 @@ from ..primitives.test_ec import _skip_curve_unsupported from ...utils import load_vectors_from_file, raises_unsupported_algorithm +def skip_if_libre_ssl(openssl_version): + if u'LibreSSL' in openssl_version: + pytest.skip("LibreSSL hard-codes RAND_bytes to use arc4random.") + + +class TestLibreSkip(object): + def test_skip_no(self): + assert skip_if_libre_ssl(u"OpenSSL 0.9.8zf 19 Mar 2015") is None + + def test_skip_yes(self): + with pytest.raises(pytest.skip.Exception): + skip_if_libre_ssl(u"LibreSSL 2.1.6") + + @utils.register_interface(Mode) class DummyMode(object): name = "dummy-mode" @@ -216,6 +232,19 @@ class TestOpenSSL(object): bn = backend._int_to_bn(0) assert backend._bn_to_int(bn) == 0 + def test_actual_osrandom_bytes(self, monkeypatch): + skip_if_libre_ssl(backend.openssl_version_text()) + sample_data = (b"\x01\x02\x03\x04" * 4) + length = len(sample_data) + + def notrandom(size): + assert size == length + return sample_data + monkeypatch.setattr(os, "urandom", notrandom) + buf = backend._ffi.new("char[]", length) + backend._lib.RAND_bytes(buf, length) + assert backend._ffi.buffer(buf)[0:length] == sample_data + class TestOpenSSLRandomEngine(object): def teardown_method(self, method): @@ -478,6 +507,33 @@ class TestOpenSSLCreateX509CSR(object): backend.create_x509_csr(object(), private_key, hashes.SHA1()) +class TestOpenSSLSignX509Certificate(object): + def test_requires_certificate_builder(self): + private_key = RSA_KEY_2048.private_key(backend) + + with pytest.raises(TypeError): + backend.sign_x509_certificate(object(), private_key, DummyHash()) + + def test_checks_for_unsupported_extensions(self): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + datetime.datetime(1999, 1, 1) + ).not_valid_after( + datetime.datetime(2020, 1, 1) + ).add_extension( + x509.InhibitAnyPolicy(0), False + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA1(), backend) + + class TestOpenSSLSerialisationWithOpenSSL(object): def test_pem_password_cb_buffer_too_small(self): ffi_cb, cb = backend._pem_password_cb(b"aa") diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index c5f0a7d7..20171fa7 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -4,27 +4,11 @@ from __future__ import absolute_import, division, print_function -import os - import pytest from cryptography.hazmat.bindings.openssl.binding import Binding -def skip_if_libre_ssl(openssl_version): - if b'LibreSSL' in openssl_version: - pytest.skip("LibreSSL hard-codes RAND_bytes to use arc4random.") - - -class TestLibreSkip(object): - def test_skip_no(self): - assert skip_if_libre_ssl(b"OpenSSL 0.9.8zf 19 Mar 2015") is None - - def test_skip_yes(self): - with pytest.raises(pytest.skip.Exception): - skip_if_libre_ssl(b"LibreSSL 2.1.6") - - class TestOpenSSL(object): def test_binding_loads(self): binding = Binding() @@ -108,20 +92,6 @@ class TestOpenSSL(object): with pytest.raises(RuntimeError): b._register_osrandom_engine() - def test_actual_osrandom_bytes(self, monkeypatch): - b = Binding() - skip_if_libre_ssl(b.ffi.string(b.lib.OPENSSL_VERSION_TEXT)) - sample_data = (b"\x01\x02\x03\x04" * 4) - length = len(sample_data) - - def notrandom(size): - assert size == length - return sample_data - monkeypatch.setattr(os, "urandom", notrandom) - buf = b.ffi.new("char[]", length) - b.lib.RAND_bytes(buf, length) - assert b.ffi.buffer(buf)[0:length] == sample_data - def test_ssl_ctx_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index af605830..f82e7354 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -854,7 +854,7 @@ class TestRSASSHSerialization(object): with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) - def test_load_ssh_public_key_rsa_extra_string_after_comment(self, backend): + def test_load_ssh_public_key_rsa_comment_with_spaces(self, backend): ssh_key = ( b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" @@ -866,8 +866,7 @@ class TestRSASSHSerialization(object): b"2MzHvnbv testkey@localhost extra" ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) + load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_rsa_extra_data_after_modulo(self, backend): ssh_key = ( @@ -943,7 +942,7 @@ class TestDSSSSHSerialization(object): with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) - def test_load_ssh_public_key_dss_extra_string_after_comment(self, backend): + def test_load_ssh_public_key_dss_comment_with_spaces(self, backend): ssh_key = ( b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" @@ -957,8 +956,7 @@ class TestDSSSSHSerialization(object): b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost extra" ) - with pytest.raises(ValueError): - load_ssh_public_key(ssh_key, backend) + load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_dss_extra_data_after_modulo(self, backend): ssh_key = ( diff --git a/tests/test_utils.py b/tests/test_utils.py index f71264ea..210e9292 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3045,8 +3045,13 @@ d518475576730ed528779366568e46b7dd4ed787cb72d0733c93 assert expected == load_kasvs_dh_vectors(vector_data) +def test_load_kasvs_ecdh_vectors_empty_vector_data(): + assert [] == load_kasvs_ecdh_vectors([]) + + def test_load_kasvs_ecdh_vectors(): vector_data = textwrap.dedent(""" + # CAVS 11.0 # Parameter set(s) supported: EA EB EC ED EE # CAVSid: CAVSid (in hex: 434156536964) # IUTid: In hex: a1b2c3d4e5 diff --git a/tests/test_x509.py b/tests/test_x509.py index 98cf49be..e31b57f4 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -22,7 +22,7 @@ from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048 -from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048 +from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 from .hazmat.primitives.test_ec import _skip_curve_unsupported from .utils import load_vectors_from_file @@ -775,6 +775,439 @@ class TestRSACertificateRequest(object): assert hash(request1) == hash(request2) assert hash(request1) != hash(request3) + def test_build_cert(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) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + +class TestCertificateBuilder(object): + def test_issuer_name_must_be_a_name_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.issuer_name("subject") + + with pytest.raises(TypeError): + builder.issuer_name(object) + + def test_issuer_name_may_only_be_set_once(self): + name = x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ]) + builder = x509.CertificateBuilder().issuer_name(name) + + with pytest.raises(ValueError): + builder.issuer_name(name) + + def test_subject_name_must_be_a_name_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.subject_name("subject") + + with pytest.raises(TypeError): + builder.subject_name(object) + + def test_subject_name_may_only_be_set_once(self): + name = x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ]) + builder = x509.CertificateBuilder().subject_name(name) + + with pytest.raises(ValueError): + builder.subject_name(name) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_public_key_must_be_public_key(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.public_key(private_key) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_public_key_may_only_be_set_once(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + public_key = private_key.public_key() + builder = x509.CertificateBuilder().public_key(public_key) + + with pytest.raises(ValueError): + builder.public_key(public_key) + + def test_serial_number_must_be_an_integer_type(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().serial_number(10.0) + + def test_serial_number_must_be_non_negative(self): + with pytest.raises(ValueError): + x509.CertificateBuilder().serial_number(-10) + + def test_serial_number_must_be_less_than_160_bits_long(self): + with pytest.raises(ValueError): + # 2 raised to the 160th power is actually 161 bits + x509.CertificateBuilder().serial_number(2 ** 160) + + def test_serial_number_may_only_be_set_once(self): + builder = x509.CertificateBuilder().serial_number(10) + + with pytest.raises(ValueError): + builder.serial_number(20) + + def test_invalid_not_valid_after(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_after(104204304504) + + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_after(datetime.time()) + + with pytest.raises(ValueError): + x509.CertificateBuilder().not_valid_after( + datetime.datetime(1960, 8, 10) + ) + + def test_not_valid_after_may_only_be_set_once(self): + builder = x509.CertificateBuilder().not_valid_after( + datetime.datetime.now() + ) + + with pytest.raises(ValueError): + builder.not_valid_after( + datetime.datetime.now() + ) + + def test_invalid_not_valid_before(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_before(104204304504) + + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_before(datetime.time()) + + with pytest.raises(ValueError): + x509.CertificateBuilder().not_valid_before( + datetime.datetime(1960, 8, 10) + ) + + def test_not_valid_before_may_only_be_set_once(self): + builder = x509.CertificateBuilder().not_valid_before( + datetime.datetime.now() + ) + + with pytest.raises(ValueError): + builder.not_valid_before( + datetime.datetime.now() + ) + + def test_add_extension_checks_for_duplicates(self): + builder = x509.CertificateBuilder().add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ) + + with pytest.raises(ValueError): + builder.add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ) + + def test_add_unsupported_extension(self): + builder = x509.CertificateBuilder() + + with pytest.raises(NotImplementedError): + builder.add_extension(object(), False) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_unsupported_hash(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.sign(private_key, object(), backend) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_dsa_private_key_is_unsupported(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000: + pytest.skip("Requires an older OpenSSL. Must be < 1.0.1") + + private_key = DSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA512(), backend) + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_ec_private_key_is_unsupported(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000: + pytest.skip("Requires an older OpenSSL. Must be < 1.0.1") + + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + builder = x509.CertificateBuilder() + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA512(), backend) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_dsa_private_key(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + + issuer_private_key = DSA_KEY_2048.private_key(backend) + subject_private_key = DSA_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) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_ec_private_key(self, backend): + if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + + _skip_curve_unsupported(backend, ec.SECP256R1()) + issuer_private_key = ec.generate_private_key(ec.SECP256R1(), backend) + subject_private_key = ec.generate_private_key(ec.SECP256R1(), backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + x509.OID_BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + x509.OID_SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_rsa_key_too_small(self, backend): + issuer_private_key = RSA_KEY_512.private_key(backend) + subject_private_key = RSA_KEY_512.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + with pytest.raises(ValueError): + builder.sign(issuer_private_key, hashes.SHA512(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_extended_key_usage(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) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + x509.ExtendedKeyUsage([ + x509.OID_CLIENT_AUTH, + x509.OID_SERVER_AUTH, + x509.OID_CODE_SIGNING, + ]), critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + eku = cert.extensions.get_extension_for_oid( + x509.OID_EXTENDED_KEY_USAGE + ) + assert eku.critical is False + assert eku.value == x509.ExtendedKeyUsage([ + x509.OID_CLIENT_AUTH, + x509.OID_SERVER_AUTH, + x509.OID_CODE_SIGNING, + ]) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_key_usage(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) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid(x509.OID_KEY_USAGE) + assert ext.critical is False + assert ext.value == x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestCertificateSigningRequestBuilder(object): @@ -1219,6 +1652,49 @@ class TestCertificateSigningRequestBuilder(object): assert str(exc.value) == "Digest too big for RSA key" + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_aia(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) + + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.OID_OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + x509.OID_CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + aia, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_AUTHORITY_INFORMATION_ACCESS + ) + assert ext.value == aia + @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 7b135828..890709ae 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -2853,3 +2853,18 @@ class TestInhibitAnyPolicyExtension(object): x509.OID_INHIBIT_ANY_POLICY ).value assert iap.skip_certs == 5 + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestInvalidExtension(object): + def test_invalid_certificate_policies_data(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_invalid.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(ValueError): + cert.extensions diff --git a/tests/utils.py b/tests/utils.py index 5083d48c..7e7abdf1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -539,8 +539,8 @@ def load_fips_ecdsa_key_pair_vectors(vector_data): elif line.startswith("Qy = "): key_data["y"] = int(line.split("=")[1], 16) - if key_data is not None: - vectors.append(key_data) + assert key_data is not None + vectors.append(key_data) return vectors @@ -559,9 +559,6 @@ def load_fips_ecdsa_signing_vectors(vector_data): for line in vector_data: line = line.strip() - if not line or line.startswith("#"): - continue - curve_match = curve_rx.match(line) if curve_match: curve_name = _ECDSA_CURVE_NAMES[curve_match.group("curve")] @@ -593,8 +590,8 @@ def load_fips_ecdsa_signing_vectors(vector_data): elif line.startswith("Result = "): data["fail"] = line.split("=")[1].strip()[0] == "F" - if data is not None: - vectors.append(data) + assert data is not None + vectors.append(data) return vectors diff --git a/vectors/cryptography_vectors/x509/custom/cp_invalid.pem b/vectors/cryptography_vectors/x509/custom/cp_invalid.pem new file mode 100644 index 00000000..b7bcc079 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/cp_invalid.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIC8TCCAdmgAwIBAgITBmsoYWX1PCELRmm8qB2WJ2QdDjANBgkqhkiG9w0BAQUFADASMRAwDgYD +VQQDDAdQeUNBIENBMB4XDTE1MDUxMTE4NTc0NVoXDTE2MDUxMDE4NTc0NVowEjEQMA4GA1UEAwwH +UHlDQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3FTRITEY4b/Y1Uv4CtH61Y +19TPxK2+H/XuqHwtYlPRyD35LLFES0wykf0V2m1DUmf9jQa9R63jBZxzCgJ/oIJzV28PgSg9P/Nn +417fNASDduY2GPvYuwwKXcLY2fBBFjBrz7z/5tyXCADjLDkzoUTzQlYPbhOrFU5QwaqlckXBgt/4 +8GRDujoHy4RSMEDNjLUDgwx7Z/JK2ujbGJDguLRuBsHirk2h6xXEmSWxquKDXw4NnakwBqp8kKhQ +2xTSWXxabNps8FCBM4sC78gKgONy3lbYdHFt/2BU4yAMyowJwtDEYHCqe1g4sVsB839Ol0SXb6vl +eXQ6dx+zbi8UzTsCAwEAAaNAMD4wPAYDVR0gBDUwMzAxBgtghkgB4DkBAgMEATAiMCAGCCsGAQUF +BwICFhRodHRwOi8vb3RoZXIuY29tL2NwczANBgkqhkiG9w0BAQUFAAOCAQEADpZIjHvu02euPNI8 +nzzDufRXEnjrF09xc9pudxTjWU2mSVApXPmTDyWzOD+2HmsNKHRE6sWjca5qPDeDbGq4JOw+TzYq +9eoqwK2Sh0QHUpg5ZaAmIJ1qe5/sNETH5RFlXrlzW9S0rwViLgUaJp6MreTdGZbxdpNsfdkuNd+S +Tz0MA/3ScbdUcj6uwQQ4JxQiTuPwD35pKwxfUzHjeTmqIEHDuCk17KqIRORdbeD3vFx0R5IQ3mQ6 +9zSGY2AGB0A9oS0qQ2/Mh59A6xyjbPH3Rr7g5MW58PPTWp2FSXkloy7Ze+doQ7wXE6PVmaeKz5qA +9OGaCHIiC2iG9UcqWxfeWw== +-----END CERTIFICATE----- |