summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2014-12-17 13:55:49 -0800
committerAlex Gaynor <alex.gaynor@gmail.com>2014-12-17 13:55:49 -0800
commit4d8de138910628db04a1c861303e744e7f10729a (patch)
tree3d7fec1de040cb7bb168150d8778262253f3e3fb
parentd1746da04faa07ebc597f721afbe4980593564eb (diff)
parentbbffc4067c1278a5d96f73bb89255becd43da29e (diff)
downloadcryptography-4d8de138910628db04a1c861303e744e7f10729a.tar.gz
Merge pull request #1499 from reaperhulk/x509-ossl-impl
X509Backend support in OpenSSL backend
-rw-r--r--CHANGELOG.rst2
-rw-r--r--docs/hazmat/primitives/interfaces.rst52
-rw-r--r--docs/index.rst1
-rw-r--r--docs/x509.rst198
-rw-r--r--setup.py1
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py48
-rw-r--r--src/cryptography/hazmat/backends/openssl/x509.py112
-rw-r--r--src/cryptography/hazmat/bindings/openssl/asn1.py1
-rw-r--r--src/cryptography/hazmat/primitives/interfaces.py4
-rw-r--r--src/cryptography/x509.py68
-rw-r--r--tests/test_x509.py250
11 files changed, 682 insertions, 55 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index cf6d22524..a5e4684f9 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 d87e8d660..2dea46d21 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 083533c94..35f80a2d0 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 000000000..18894ce55
--- /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
diff --git a/setup.py b/setup.py
index 3e2ab3ebc..ead5ec4f9 100644
--- a/setup.py
+++ b/setup.py
@@ -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 ebe38e20e..daccf5ca5 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 000000000..0828f3cc0
--- /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 e3631237a..d8b8331e0 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 18a626017..76616e1f9 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 000000000..be1298b67
--- /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 000000000..5383871ac
--- /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()