aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2015-12-25 10:59:22 -0600
committerPaul Kehrer <paul.l.kehrer@gmail.com>2015-12-25 13:01:47 -0600
commitc33ffd7527a4ce77010425fedfbeed27856c8aa8 (patch)
tree701630292bc8f0dd698487d7b95ef262fca834b4
parentef5f9fc339a7137f5a9761f8c27a08c554c27b1c (diff)
downloadcryptography-c33ffd7527a4ce77010425fedfbeed27856c8aa8.tar.gz
cryptography-c33ffd7527a4ce77010425fedfbeed27856c8aa8.tar.bz2
cryptography-c33ffd7527a4ce77010425fedfbeed27856c8aa8.zip
RevokedCertificateBuilder
-rw-r--r--docs/x509/reference.rst48
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py21
-rw-r--r--src/cryptography/x509/__init__.py3
-rw-r--r--src/cryptography/x509/base.py44
-rw-r--r--tests/hazmat/backends/test_openssl.py4
-rw-r--r--tests/test_x509_revokedcertbuilder.py80
6 files changed, 195 insertions, 5 deletions
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index e4711be3..8d8bda4b 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -895,6 +895,54 @@ X.509 Revoked Certificate Object
<Extension(oid=<ObjectIdentifier(oid=2.5.29.24, name=invalidityDate)>, critical=False, value=2015-01-01 00:00:00)>
<Extension(oid=<ObjectIdentifier(oid=2.5.29.21, name=cRLReason)>, critical=False, value=ReasonFlags.key_compromise)>
+X.509 Revoked Certificate Builder
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. class:: RevokedCertificateBuilder
+
+ This class is used to create :class:`~cryptography.x509.RevokedCertificate`
+ objects that can be used with the
+ :class:`~cryptography.x509.CertificateRevocationListBuilder`.
+
+ .. versionadded:: 1.2
+
+ .. doctest::
+
+ >>> from cryptography import x509
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> import datetime
+ >>> builder = x509.RevokedCertificateBuilder()
+ >>> builder = builder.revocation_date(datetime.datetime.today())
+ >>> builder = builder.serial_number(3333)
+ >>> revoked_certificate = builder.build(default_backend())
+ >>> isinstance(revoked_certificate, x509.RevokedCertificate)
+ True
+
+ .. method:: serial_number(serial_number)
+
+ Sets the revoked certificate's serial number.
+
+ :param serial_number: Integer number that is used to identify the
+ revoked certificate.
+
+ .. method:: revocation_date(time)
+
+ Sets the certificate's revocation date.
+
+ :param time: The :class:`datetime.datetime` object (in UTC) that marks the
+ revocation time for the certificate.
+
+ .. method:: build(backend)
+
+ Create a revoked certificate object using the provided backend.
+
+ :param backend: Backend that will be used to build the revoked
+ certificate. Must support the
+ :class:`~cryptography.hazmat.backends.interfaces.X509Backend`
+ interface.
+
+ :returns: :class:`~cryptography.x509.RevokedCertificate`
+
X.509 CSR (Certificate Signing Request) Builder Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index a60bf82b..81316da5 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -38,7 +38,7 @@ from cryptography.hazmat.backends.openssl.rsa import (
)
from cryptography.hazmat.backends.openssl.x509 import (
_Certificate, _CertificateRevocationList, _CertificateSigningRequest,
- _DISTPOINT_TYPE_FULLNAME, _DISTPOINT_TYPE_RELATIVENAME
+ _DISTPOINT_TYPE_FULLNAME, _DISTPOINT_TYPE_RELATIVENAME, _RevokedCertificate
)
from cryptography.hazmat.bindings._openssl import ffi as _ffi
from cryptography.hazmat.bindings.openssl import binding
@@ -1559,7 +1559,24 @@ class Backend(object):
self.openssl_assert(res >= 1)
def create_x509_revoked_certificate(self, builder):
- raise NotImplementedError("Not yet implemented")
+ if not isinstance(builder, x509.RevokedCertificateBuilder):
+ raise TypeError('Builder type mismatch.')
+
+ x509_revoked = self._lib.X509_REVOKED_new()
+ self.openssl_assert(x509_revoked != self._ffi.NULL)
+ x509_revoked = self._ffi.gc(x509_revoked, self._lib.X509_REVOKED_free)
+ serial_number = _encode_asn1_int_gc(self, builder._serial_number)
+ res = self._lib.X509_REVOKED_set_serialNumber(
+ x509_revoked, serial_number
+ )
+ self.openssl_assert(res == 1)
+ res = self._lib.ASN1_TIME_set(
+ x509_revoked.revocationDate,
+ calendar.timegm(builder._revocation_date.timetuple())
+ )
+ self.openssl_assert(res != self._ffi.NULL)
+ # TODO: add crl entry extensions
+ return _RevokedCertificate(self, None, x509_revoked)
def load_pem_private_key(self, data, password):
return self._load_key(
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
index 4978b199..5653144c 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -8,7 +8,7 @@ from cryptography.x509.base import (
Certificate, CertificateBuilder, CertificateRevocationList,
CertificateRevocationListBuilder,
CertificateSigningRequest, CertificateSigningRequestBuilder,
- InvalidVersion, RevokedCertificate,
+ InvalidVersion, RevokedCertificate, RevokedCertificateBuilder,
Version, load_der_x509_certificate, load_der_x509_crl, load_der_x509_csr,
load_pem_x509_certificate, load_pem_x509_crl, load_pem_x509_csr,
)
@@ -156,6 +156,7 @@ __all__ = [
"CertificateRevocationListBuilder",
"CertificateSigningRequest",
"RevokedCertificate",
+ "RevokedCertificateBuilder",
"CertificateSigningRequestBuilder",
"CertificateBuilder",
"Version",
diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py
index 49cbcf75..e29a3105 100644
--- a/src/cryptography/x509/base.py
+++ b/src/cryptography/x509/base.py
@@ -602,3 +602,47 @@ class CertificateRevocationListBuilder(object):
raise ValueError("A CRL must have a next update time")
return backend.create_x509_crl(self, private_key, algorithm)
+
+
+class RevokedCertificateBuilder(object):
+ def __init__(self, serial_number=None, revocation_date=None,
+ extensions=[]):
+ self._serial_number = serial_number
+ self._revocation_date = revocation_date
+ self._extensions = extensions
+
+ def serial_number(self, 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 RevokedCertificateBuilder(
+ number, self._revocation_date, self._extensions
+ )
+
+ def revocation_date(self, time):
+ if not isinstance(time, datetime.datetime):
+ raise TypeError('Expecting datetime object.')
+ if self._revocation_date is not None:
+ raise ValueError('The revocation date may only be set once.')
+ if time <= _UNIX_EPOCH:
+ raise ValueError('The revocation date must be after the unix'
+ ' epoch (1970 January 1).')
+ return RevokedCertificateBuilder(
+ self._serial_number, time, self._extensions
+ )
+
+ def build(self, backend):
+ if self._serial_number is None:
+ raise ValueError("A revoked certificate must have a serial number")
+ if self._revocation_date is None:
+ raise ValueError(
+ "A revoked certificate must have a revocation date"
+ )
+
+ return backend.create_x509_revoked_certificate(self)
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index af064d18..c8d35893 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -510,8 +510,8 @@ class TestOpenSSLSignX509CertificateRevocationList(object):
class TestOpenSSLCreateRevokedCertificate(object):
- def test_not_yet_implemented(self):
- with pytest.raises(NotImplementedError):
+ def test_invalid_builder(self):
+ with pytest.raises(TypeError):
backend.create_x509_revoked_certificate(object())
diff --git a/tests/test_x509_revokedcertbuilder.py b/tests/test_x509_revokedcertbuilder.py
new file mode 100644
index 00000000..9f79387b
--- /dev/null
+++ b/tests/test_x509_revokedcertbuilder.py
@@ -0,0 +1,80 @@
+# 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 datetime
+
+import pytest
+
+from cryptography import x509
+from cryptography.hazmat.backends.interfaces import X509Backend
+
+
+class TestRevokedCertificateBuilder(object):
+ def test_serial_number_must_be_integer(self):
+ with pytest.raises(TypeError):
+ x509.RevokedCertificateBuilder().serial_number("notanx509name")
+
+ def test_serial_number_must_be_non_negative(self):
+ with pytest.raises(ValueError):
+ x509.RevokedCertificateBuilder().serial_number(-1)
+
+ 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.RevokedCertificateBuilder().serial_number(2 ** 160)
+
+ def test_set_serial_number_twice(self):
+ builder = x509.RevokedCertificateBuilder().serial_number(3)
+ with pytest.raises(ValueError):
+ builder.serial_number(4)
+
+ def test_revocation_date_invalid(self):
+ with pytest.raises(TypeError):
+ x509.RevokedCertificateBuilder().revocation_date("notadatetime")
+
+ def test_revocation_date_before_unix_epoch(self):
+ with pytest.raises(ValueError):
+ x509.RevokedCertificateBuilder().revocation_date(
+ datetime.datetime(1960, 8, 10)
+ )
+
+ def test_set_revocation_date_twice(self):
+ builder = x509.RevokedCertificateBuilder().revocation_date(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ )
+ with pytest.raises(ValueError):
+ builder.revocation_date(datetime.datetime(2002, 1, 1, 12, 1))
+
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_no_serial_number(self, backend):
+ builder = x509.RevokedCertificateBuilder().revocation_date(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(ValueError):
+ builder.build(backend)
+
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_no_revocation_date(self, backend):
+ builder = x509.RevokedCertificateBuilder().serial_number(3)
+
+ with pytest.raises(ValueError):
+ builder.build(backend)
+
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_create_revoked(self, backend):
+ serial_number = 333
+ revocation_date = datetime.datetime(2002, 1, 1, 12, 1)
+ builder = x509.RevokedCertificateBuilder().serial_number(
+ serial_number
+ ).revocation_date(
+ revocation_date
+ )
+
+ revoked_certificate = builder.build(backend)
+ assert revoked_certificate.serial_number == serial_number
+ assert revoked_certificate.revocation_date == revocation_date
+ assert len(revoked_certificate.extensions) == 0