diff options
-rw-r--r-- | CHANGELOG.rst | 2 | ||||
-rw-r--r-- | docs/hazmat/primitives/interfaces.rst | 52 | ||||
-rw-r--r-- | docs/index.rst | 1 | ||||
-rw-r--r-- | docs/x509.rst | 198 | ||||
-rw-r--r-- | setup.py | 1 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 48 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 112 | ||||
-rw-r--r-- | src/cryptography/hazmat/bindings/openssl/asn1.py | 1 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/interfaces.py | 4 | ||||
-rw-r--r-- | src/cryptography/x509.py | 68 | ||||
-rw-r--r-- | tests/test_x509.py | 250 |
11 files changed, 682 insertions, 55 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cf6d2252..a5e4684f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,6 +22,8 @@ Changelog :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` to support the loading of OpenSSH public keys (:rfc:`4253`). Currently, only RSA keys are supported. +* Added initial support for X.509 certificate parsing. See the + :doc:`X.509 documentation</x509>` for more information. 0.6.1 - 2014-10-15 ~~~~~~~~~~~~~~~~~~ diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index d87e8d66..2dea46d2 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -696,58 +696,6 @@ Key derivation functions the provided signature does not match the expected signature. -X509 ----- - -.. class:: X509Certificate - - .. versionadded:: 0.7 - - .. attribute:: version - - :type: X509Version - - The certificate version as an enumeration. - - .. method:: fingerprint(algorithm) - - :param algorithm: A - :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` - that will be used by this context. - - :return bytes: The fingerprint using the supplied hash algorithm as - bytes. - - .. attribute:: serial - - :type: int - - The serial as a Python integer. - - .. method:: public_key() - - :type: - :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey` or - :class:`~cryptography.hazmat.primitives.interfaces.DSAPublicKey` or - :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicKey` - - The public key associated with the certificate. - - .. attribute:: not_before - - :type: :class:`datetime.datetime` - - A naïve datetime representing the beginning of the validity period for the - certificate in UTC. This value is inclusive. - - .. attribute:: not_after - - :type: :class:`datetime.datetime` - - A naïve datetime representing the end of the validity period for the - certificate in UTC. This value is inclusive. - - .. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem) .. _`Chinese remainder theorem`: https://en.wikipedia.org/wiki/Chinese_remainder_theorem .. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm diff --git a/docs/index.rst b/docs/index.rst index 083533c9..35f80a2d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -63,6 +63,7 @@ The recipes layer :maxdepth: 2 fernet + x509 random-numbers exceptions faq diff --git a/docs/x509.rst b/docs/x509.rst new file mode 100644 index 00000000..18894ce5 --- /dev/null +++ b/docs/x509.rst @@ -0,0 +1,198 @@ +.. hazmat:: + +X.509 +===== + +.. currentmodule:: cryptography.x509 + +X.509 is an ITU-T standard for a `public key infrastructure`_. X.509v3 is +defined in :rfc:`5280` (which obsoletes :rfc:`2459` and :rfc:`3280`). X.509 +certificates are commonly used in protocols like `TLS`_. + + +Loading Certificates +~~~~~~~~~~~~~~~~~~~~ + +.. function:: load_pem_x509_certificate(data, backend) + + .. versionadded:: 0.7 + + Deserialize a certificate from PEM encoded data. PEM certificates are + base64 decoded and have delimiters that look like + ``-----BEGIN CERTIFICATE-----``. + + :param bytes data: The PEM encoded certificate data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of :class:`~cryptography.x509.Certificate`. + +.. function:: load_der_x509_certificate(data, backend) + + .. versionadded:: 0.7 + + Deserialize a certificate from DER encoded data. DER is a binary format + and is commonly found in files with the ``.cer`` extension (although file + extensions are not a guarantee of encoding type). + + :param bytes data: The DER encoded certificate data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of :class:`~cryptography.x509.Certificate`. + +.. testsetup:: + + pem_data = b""" + -----BEGIN CERTIFICATE----- + MIIDfDCCAmSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJVUzEf + MB0GA1UEChMWVGVzdCBDZXJ0aWZpY2F0ZXMgMjAxMTEVMBMGA1UEAxMMVHJ1c3Qg + QW5jaG9yMB4XDTEwMDEwMTA4MzAwMFoXDTMwMTIzMTA4MzAwMFowQDELMAkGA1UE + BhMCVVMxHzAdBgNVBAoTFlRlc3QgQ2VydGlmaWNhdGVzIDIwMTExEDAOBgNVBAMT + B0dvb2QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQWJpHYo37 + Xfb7oJSPe+WvfTlzIG21WQ7MyMbGtK/m8mejCzR6c+f/pJhEH/OcDSMsXq8h5kXa + BGqWK+vSwD/Pzp5OYGptXmGPcthDtAwlrafkGOS4GqIJ8+k9XGKs+vQUXJKsOk47 + RuzD6PZupq4s16xaLVqYbUC26UcY08GpnoLNHJZS/EmXw1ZZ3d4YZjNlpIpWFNHn + UGmdiGKXUPX/9H0fVjIAaQwjnGAbpgyCumWgzIwPpX+ElFOUr3z7BoVnFKhIXze+ + VmQGSWxZxvWDUN90Ul0tLEpLgk3OVxUB4VUGuf15OJOpgo1xibINPmWt14Vda2N9 + yrNKloJGZNqLAgMBAAGjfDB6MB8GA1UdIwQYMBaAFOR9X9FclYYILAWuvnW2ZafZ + XahmMB0GA1UdDgQWBBRYAYQkG7wrUpRKPaUQchRR9a86yTAOBgNVHQ8BAf8EBAMC + AQYwFwYDVR0gBBAwDjAMBgpghkgBZQMCATABMA8GA1UdEwEB/wQFMAMBAf8wDQYJ + KoZIhvcNAQELBQADggEBADWHlxbmdTXNwBL/llwhQqwnazK7CC2WsXBBqgNPWj7m + tvQ+aLG8/50Qc2Sun7o2VnwF9D18UUe8Gj3uPUYH+oSI1vDdyKcjmMbKRU4rk0eo + 3UHNDXwqIVc9CQS9smyV+x1HCwL4TTrq+LXLKx/qVij0Yqk+UJfAtrg2jnYKXsCu + FMBQQnWCGrwa1g1TphRp/RmYHnMynYFmZrXtzFz+U9XEA7C+gPq4kqDI/iVfIT1s + 6lBtdB50lrDVwl2oYfAvW/6sC2se2QleZidUmrziVNP4oEeXINokU6T6p//HM1FG + QYw2jOvpKcKtWCSAnegEbgsGYzATKjmPJPJ0npHFqzM= + -----END CERTIFICATE----- + """.strip() + +.. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> cert = x509.load_pem_x509_certificate(pem_data, default_backend()) + >>> cert.serial + 2 + +X.509 Certificate Object +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: Certificate + + .. versionadded:: 0.7 + + .. attribute:: version + + :type: :class:`~cryptography.x509.Version` + + The certificate version as an enumeration. Version 3 certificates are + the latest version and also the only type you should see in practice. + + :raises cryptography.x509.InvalidVersion: If the version in the + certificate is not a known + :class:`X.509 version <cryptography.x509.Version>`. + + .. doctest:: + + >>> cert.version + <Version.v3: 2> + + .. method:: fingerprint(algorithm) + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + that will be used to generate the fingerprint. + + :return bytes: The fingerprint using the supplied hash algorithm as + bytes. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> cert.fingerprint(hashes.SHA256()) + '\x86\xd2\x187Gc\xfc\xe7}[+E9\x8d\xb4\x8f\x10\xe5S\xda\x18u\xbe}a\x03\x08[\xac\xa04?' + + .. attribute:: serial + + :type: int + + The serial as a Python integer. + + .. doctest:: + + >>> cert.serial + 2 + + .. method:: public_key() + + :type: + :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey` or + :class:`~cryptography.hazmat.primitives.interfaces.DSAPublicKey` or + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicKey` + + The public key associated with the certificate. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import interfaces + >>> public_key = cert.public_key() + >>> isinstance(public_key, interfaces.RSAPublicKey) + True + + .. attribute:: not_valid_before + + :type: :class:`datetime.datetime` + + A naïve datetime representing the beginning of the validity period for + the certificate in UTC. This value is inclusive. + + .. doctest:: + + >>> cert.not_valid_before + datetime.datetime(2010, 1, 1, 8, 30) + + .. attribute:: not_valid_after + + :type: :class:`datetime.datetime` + + A naïve datetime representing the end of the validity period for the + certificate in UTC. This value is inclusive. + + .. doctest:: + + >>> cert.not_valid_after + datetime.datetime(2030, 12, 31, 8, 30) + + +.. class:: Version + + .. versionadded:: 0.7 + + An enumeration for X.509 versions. + + .. attribute:: v1 + + For version 1 X.509 certificates. + + .. attribute:: v3 + + For version 3 X.509 certificates. + +.. class:: InvalidVersion + + This is raised when an X.509 certificate has an invalid version number. + + .. attribute:: parsed_version + + :type: int + + Returns the raw version that was parsed from the certificate. + + +.. _`public key infrastructure`: https://en.wikipedia.org/wiki/Public_key_infrastructure +.. _`TLS`: https://en.wikipedia.org/wiki/Transport_Layer_Security @@ -36,6 +36,7 @@ VECTORS_DEPENDENCY = "cryptography_vectors=={0}".format(about['__version__']) requirements = [ CFFI_DEPENDENCY, + "enum34", "pyasn1", SIX_DEPENDENCY, SETUPTOOLS_DEPENDENCY diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index ebe38e20..daccf5ca 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -19,7 +19,7 @@ from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, PEMSerializationBackend, PKCS8SerializationBackend, RSABackend, - TraditionalOpenSSLSerializationBackend + TraditionalOpenSSLSerializationBackend, X509Backend ) from cryptography.hazmat.backends.openssl.ciphers import ( _AESCTRCipherContext, _CipherContext @@ -36,6 +36,7 @@ from cryptography.hazmat.backends.openssl.hmac import _HMACContext from cryptography.hazmat.backends.openssl.rsa import ( _RSAPrivateKey, _RSAPublicKey ) +from cryptography.hazmat.backends.openssl.x509 import _Certificate from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa @@ -66,6 +67,7 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError", @utils.register_interface(RSABackend) @utils.register_interface(TraditionalOpenSSLSerializationBackend) @utils.register_interface(PEMSerializationBackend) +@utils.register_interface(X509Backend) class Backend(object): """ OpenSSL API binding interfaces. @@ -445,6 +447,28 @@ class Backend(object): return _MemoryBIO(self._ffi.gc(bio, self._lib.BIO_free), data_char_p) + def _create_mem_bio(self): + """ + Creates an empty memory BIO. + """ + bio_method = self._lib.BIO_s_mem() + assert bio_method != self._ffi.NULL + bio = self._lib.BIO_new(bio_method) + assert bio != self._ffi.NULL + bio = self._ffi.gc(bio, self._lib.BIO_free) + return bio + + def _read_mem_bio(self, bio): + """ + Reads a memory BIO. This only works on memory BIOs. + """ + buf = self._ffi.new("char **") + buf_len = self._lib.BIO_get_mem_data(bio, buf) + assert buf_len > 0 + assert buf[0] != self._ffi.NULL + bio_data = self._ffi.buffer(buf[0], buf_len)[:] + return bio_data + def _evp_pkey_to_private_key(self, evp_pkey): """ Return the appropriate type of PrivateKey given an evp_pkey cdata @@ -675,6 +699,28 @@ class Backend(object): None, ) + def load_pem_x509_certificate(self, data): + mem_bio = self._bytes_to_bio(data) + x509 = self._lib.PEM_read_bio_X509( + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + if x509 == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to load certificate") + + x509 = self._ffi.gc(x509, self._lib.X509_free) + return _Certificate(self, x509) + + def load_der_x509_certificate(self, data): + mem_bio = self._bytes_to_bio(data) + x509 = self._lib.d2i_X509_bio(mem_bio.bio, self._ffi.NULL) + if x509 == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to load certificate") + + x509 = self._ffi.gc(x509, self._lib.X509_free) + return _Certificate(self, x509) + def load_traditional_openssl_pem_private_key(self, data, password): warnings.warn( "load_traditional_openssl_pem_private_key is deprecated and will " diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py new file mode 100644 index 00000000..0828f3cc --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -0,0 +1,112 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import datetime + +from cryptography import utils, x509 +from cryptography.hazmat.primitives import hashes + + +@utils.register_interface(x509.Certificate) +class _Certificate(object): + def __init__(self, backend, x509): + self._backend = backend + self._x509 = x509 + + def fingerprint(self, algorithm): + h = hashes.Hash(algorithm, self._backend) + bio = self._backend._create_mem_bio() + res = self._backend._lib.i2d_X509_bio( + bio, self._x509 + ) + assert res == 1 + der = self._backend._read_mem_bio(bio) + h.update(der) + return h.finalize() + + @property + def version(self): + version = self._backend._lib.X509_get_version(self._x509) + if version == 0: + return x509.Version.v1 + elif version == 2: + return x509.Version.v3 + else: + raise x509.InvalidVersion( + "{0} is not a valid X509 version".format(version), version + ) + + @property + def serial(self): + asn1_int = self._backend._lib.X509_get_serialNumber(self._x509) + assert asn1_int != self._backend._ffi.NULL + bn = self._backend._lib.ASN1_INTEGER_to_BN( + asn1_int, self._backend._ffi.NULL + ) + assert bn != self._backend._ffi.NULL + bn = self._backend._ffi.gc(bn, self._backend._lib.BN_free) + return self._backend._bn_to_int(bn) + + def public_key(self): + pkey = self._backend._lib.X509_get_pubkey(self._x509) + assert pkey != self._backend._ffi.NULL + pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) + # The following check is to find ECDSA certificates with unnamed + # curves and raise an error for now. + if ( + self._backend._lib.Cryptography_HAS_EC == 1 and + pkey.type == self._backend._lib.EVP_PKEY_EC + ): + ec_cdata = self._backend._lib.EVP_PKEY_get1_EC_KEY(pkey) + assert ec_cdata != self._backend._ffi.NULL + ec_cdata = self._backend._ffi.gc( + ec_cdata, self._backend._lib.EC_KEY_free + ) + group = self._backend._lib.EC_KEY_get0_group(ec_cdata) + assert group != self._backend._ffi.NULL + nid = self._backend._lib.EC_GROUP_get_curve_name(group) + if nid == self._backend._lib.NID_undef: + raise NotImplementedError( + "ECDSA certificates with unnamed curves are unsupported " + "at this time" + ) + + return self._backend._evp_pkey_to_public_key(pkey) + + @property + def not_valid_before(self): + asn1_time = self._backend._lib.X509_get_notBefore(self._x509) + return self._parse_asn1_time(asn1_time) + + @property + def not_valid_after(self): + asn1_time = self._backend._lib.X509_get_notAfter(self._x509) + return self._parse_asn1_time(asn1_time) + + def _parse_asn1_time(self, asn1_time): + assert asn1_time != self._backend._ffi.NULL + generalized_time = self._backend._lib.ASN1_TIME_to_generalizedtime( + asn1_time, self._backend._ffi.NULL + ) + assert generalized_time != self._backend._ffi.NULL + generalized_time = self._backend._ffi.gc( + generalized_time, self._backend._lib.ASN1_GENERALIZEDTIME_free + ) + time = self._backend._ffi.string( + self._backend._lib.ASN1_STRING_data( + self._backend._ffi.cast("ASN1_STRING *", generalized_time) + ) + ).decode("ascii") + return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") diff --git a/src/cryptography/hazmat/bindings/openssl/asn1.py b/src/cryptography/hazmat/bindings/openssl/asn1.py index e3631237..d8b8331e 100644 --- a/src/cryptography/hazmat/bindings/openssl/asn1.py +++ b/src/cryptography/hazmat/bindings/openssl/asn1.py @@ -123,6 +123,7 @@ const ASN1_ITEM *ASN1_ITEM_ptr(ASN1_ITEM_EXP *); /* These aren't macros these arguments are all const X on openssl > 1.0.x */ +int ASN1_TIME_print(BIO *, ASN1_TIME *); int ASN1_STRING_length(ASN1_STRING *); ASN1_STRING *ASN1_STRING_dup(ASN1_STRING *); int ASN1_STRING_cmp(ASN1_STRING *, ASN1_STRING *); diff --git a/src/cryptography/hazmat/primitives/interfaces.py b/src/cryptography/hazmat/primitives/interfaces.py index 18a62601..76616e1f 100644 --- a/src/cryptography/hazmat/primitives/interfaces.py +++ b/src/cryptography/hazmat/primitives/interfaces.py @@ -517,13 +517,13 @@ class X509Certificate(object): """ @abc.abstractproperty - def not_before(self): + def not_valid_before(self): """ Not before time (represented as UTC datetime) """ @abc.abstractproperty - def not_after(self): + def not_valid_after(self): """ Not after time (represented as UTC datetime) """ diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py new file mode 100644 index 00000000..be1298b6 --- /dev/null +++ b/src/cryptography/x509.py @@ -0,0 +1,68 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc +from enum import Enum + +import six + + +class Version(Enum): + v1 = 0 + v3 = 2 + + +def load_pem_x509_certificate(data, backend): + return backend.load_pem_x509_certificate(data) + + +def load_der_x509_certificate(data, backend): + return backend.load_der_x509_certificate(data) + + +class InvalidVersion(Exception): + def __init__(self, msg, parsed_version): + super(InvalidVersion, self).__init__(msg) + self.parsed_version = parsed_version + + +@six.add_metaclass(abc.ABCMeta) +class Certificate(object): + @abc.abstractmethod + def fingerprint(self, algorithm): + """ + Returns bytes using digest passed. + """ + + @abc.abstractproperty + def serial(self): + """ + Returns certificate serial number + """ + + @abc.abstractproperty + def version(self): + """ + Returns the certificate version + """ + + @abc.abstractmethod + def public_key(self): + """ + Returns the public key + """ + + @abc.abstractproperty + def not_valid_before(self): + """ + Not before time (represented as UTC datetime) + """ + + @abc.abstractproperty + def not_valid_after(self): + """ + Not after time (represented as UTC datetime) + """ diff --git a/tests/test_x509.py b/tests/test_x509.py new file mode 100644 index 00000000..5383871a --- /dev/null +++ b/tests/test_x509.py @@ -0,0 +1,250 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import datetime +import os + +import pytest + +from cryptography import x509 +from cryptography.hazmat.backends.interfaces import ( + DSABackend, EllipticCurveBackend, RSABackend, X509Backend +) +from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.primitives.asymmetric import ec + +from .hazmat.primitives.test_ec import _skip_curve_unsupported +from .utils import load_vectors_from_file + + +def _load_cert(filename, loader, backend): + cert = load_vectors_from_file( + filename=filename, + loader=lambda pemfile: loader(pemfile.read(), backend), + mode="rb" + ) + return cert + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSACertificate(object): + def test_load_pem_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert, x509.Certificate) + assert cert.serial == 11559813051657483483 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"2b619ed04bfc9c3b08eb677d272192286a0947a8" + + def test_load_der_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + assert isinstance(cert, x509.Certificate) + assert cert.serial == 2 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + + def test_load_good_ca_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.serial == 2 + public_key = cert.public_key() + assert isinstance(public_key, interfaces.RSAPublicKey) + assert cert.version is x509.Version.v3 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + + def test_utc_pre_2000_not_before_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Validpre2000UTCnotBeforeDateTest3EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_before == datetime.datetime(1950, 1, 1, 12, 1) + + def test_pre_2000_utc_not_after_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Invalidpre2000UTCEEnotAfterDateTest7EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_after == datetime.datetime(1999, 1, 1, 12, 1) + + def test_post_2000_utc_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime( + 2014, 11, 26, 21, 41, 20 + ) + assert cert.not_valid_after == datetime.datetime( + 2014, 12, 26, 21, 41, 20 + ) + + def test_generalized_time_not_before_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotBeforeDateTest4EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime(2002, 1, 1, 12, 1) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.version is x509.Version.v3 + + def test_generalized_time_not_after_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotAfterDateTest8EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2050, 1, 1, 12, 1) + assert cert.version is x509.Version.v3 + + def test_invalid_version_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "invalid_version.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(x509.InvalidVersion) as exc: + cert.version + + assert exc.value.parsed_version == 7 + + def test_version_1_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "v1_cert.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.version is x509.Version.v1 + + def test_invalid_pem(self, backend): + with pytest.raises(ValueError): + x509.load_pem_x509_certificate(b"notacert", backend) + + def test_invalid_der(self, backend): + with pytest.raises(ValueError): + x509.load_der_x509_certificate(b"notacert", backend) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestDSACertificate(object): + def test_load_dsa_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + public_key = cert.public_key() + assert isinstance(public_key, interfaces.DSAPublicKey) + if isinstance(public_key, interfaces.DSAPublicKeyWithNumbers): + num = public_key.public_numbers() + assert num.y == int( + "4c08bfe5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa94" + "53b6656f13e543684cd5f6431a314377d2abfa068b7080cb8ddc065afc2" + "dea559f0b584c97a2b235b9b69b46bc6de1aed422a6f341832618bcaae2" + "198aba388099dafb05ff0b5efecb3b0ae169a62e1c72022af50ae68af3b" + "033c18e6eec1f7df4692c456ccafb79cc7e08da0a5786e9816ceda651d6" + "1b4bb7b81c2783da97cea62df67af5e85991fdc13aff10fc60e06586386" + "b96bb78d65750f542f86951e05a6d81baadbcd35a2e5cad4119923ae6a2" + "002091a3d17017f93c52970113cdc119970b9074ca506eac91c3dd37632" + "5df4af6b3911ef267d26623a5a1c5df4a6d13f1c", 16 + ) + assert num.parameter_numbers.g == int( + "4b7ced71dc353965ecc10d441a9a06fc24943a32d66429dd5ef44d43e67" + "d789d99770aec32c0415dc92970880872da45fef8dd1e115a3e4801387b" + "a6d755861f062fd3b6e9ea8e2641152339b828315b1528ee6c7b79458d2" + "1f3db973f6fc303f9397174c2799dd2351282aa2d8842c357a73495bbaa" + "c4932786414c55e60d73169f5761036fba29e9eebfb049f8a3b1b7cee6f" + "3fbfa136205f130bee2cf5b9c38dc1095d4006f2e73335c07352c64130a" + "1ab2b89f13b48f628d3cc3868beece9bb7beade9f830eacc6fa241425c0" + "b3fcc0df416a0c89f7bf35668d765ec95cdcfbe9caff49cfc156c668c76" + "fa6247676a6d3ac945844a083509c6a1b436baca", 16 + ) + assert num.parameter_numbers.p == int( + "bfade6048e373cd4e48b677e878c8e5b08c02102ae04eb2cb5c46a523a3" + "af1c73d16b24f34a4964781ae7e50500e21777754a670bd19a7420d6330" + "84e5556e33ca2c0e7d547ea5f46a07a01bf8669ae3bdec042d9b2ae5e6e" + "cf49f00ba9dac99ab6eff140d2cedf722ee62c2f9736857971444c25d0a" + "33d2017dc36d682a1054fe2a9428dda355a851ce6e6d61e03e419fd4ca4" + "e703313743d86caa885930f62ed5bf342d8165627681e9cc3244ba72aa2" + "2148400a6bbe80154e855d042c9dc2a3405f1e517be9dea50562f56da93" + "f6085f844a7e705c1f043e65751c583b80d29103e590ccb26efdaa0893d" + "833e36468f3907cfca788a3cb790f0341c8a31bf", 16 + ) + assert num.parameter_numbers.q == int( + "822ff5d234e073b901cf5941f58e1f538e71d40d", 16 + ) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestECDSACertificate(object): + def test_load_ecdsa_cert(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + public_key = cert.public_key() + assert isinstance(public_key, interfaces.EllipticCurvePublicKey) + if isinstance( + public_key, interfaces.EllipticCurvePublicKeyWithNumbers + ): + num = public_key.public_numbers() + assert num.x == int( + "dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34eadec69bbcd095f" + "6f0ccd00bba615b51467e9e2d9fee8e630c17", 16 + ) + assert num.y == int( + "ec0770f5cf842e40839ce83f416d3badd3a4145936789d0343ee10136c7" + "2deae88a7a16bb543ce67dc23ff031ca3e23e", 16 + ) + assert isinstance(num.curve, ec.SECP384R1) + + def test_load_ecdsa_no_named_curve(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + cert = _load_cert( + os.path.join("x509", "custom", "ec_no_named_curve.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(NotImplementedError): + cert.public_key() |