Skip to content

Commit

Permalink
x509: Group BasicConstraints in namedtuple
Browse files Browse the repository at this point in the history
  • Loading branch information
Synss committed Oct 25, 2018
1 parent 337d69a commit e17d50b
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 42 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ not implemented.

API Changes

* x509: `ca` and `max_path_length` basic constraints are now
grouped into a `BasicConstraints` namedtuple. It is possible
to leave the basic constraints unset by passing None
to `CRT.new(..., basic_constraints=None)`.
* random: `random` module renamed `_random`.

Bugfixes
Expand Down
5 changes: 3 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ Here, the trusted root is a self-signed CA certificate
>>> ca0_crt = x509.CRT.selfsign(
... ca0_csr, ca0_key,
... not_before=now, not_after=now + dt.timedelta(days=90),
... serial_number=0x123456, ca=True, max_path_length=-1)
... serial_number=0x123456,
... basic_constraints=x509.BasicConstraints(True, 1))
...

An intermediate then issues a Certificate Singing Request (CSR) that the
Expand All @@ -318,7 +319,7 @@ root CA signs::
>>>
>>> ca1_crt = ca0_crt.sign(
... ca1_csr, ca0_key, now, now + dt.timedelta(days=90), 0x123456,
... ca=True, max_path_length=3)
... basic_constraints=x509.BasicConstraints(ca=True, max_path_length=3))
...

And finally, the intermediate CA signs a certificate for the
Expand Down
55 changes: 30 additions & 25 deletions mbedtls/x509.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ cdef class Certificate:
raise NotImplementedError


class BasicConstraints(
namedtuple("BasicConstraints", ["ca", "max_path_length"])):
"""The basic constraints for the certificate."""

def __new__(cls, ca=False, max_path_length=0):
return super(BasicConstraints, cls).__new__(cls, ca, max_path_length)


cdef class CRT(Certificate):
"""X.509 certificate."""

Expand Down Expand Up @@ -352,21 +360,20 @@ cdef class CRT(Certificate):
# RFC 5280, Section 4.2.1.8 Subject Directory Attributes

@property
def ca(self):
"""True if the certified public key may be used to verify
certificate signatures.
def basic_constraints(self):
"""ca is true if the certified public key may be used
to verify certificate signatures.
See Also:
RFC 5280, Section 4.2.1.9 Basic Constraints
- RFC 5280, Section 4.2.1.9 Basic Constraints
- RFC 5280, `max_path_length`
"""
return bool(self._ctx.ca_istrue)

@property
def max_path_length(self):
"""RFC 5280 `max_path_length`."""
max_path_length = int(self._ctx.max_pathlen)
return max_path_length - 1 if max_path_length > 0 else 0
if max_path_length > 0:
max_path_length -= 1
ca = bool(self._ctx.ca_istrue)
return BasicConstraints(ca, max_path_length)

# RFC 5280, Section 4.2.1.10 Name Constraints
# RFC 5280, Section 4.2.1.11 Policy Constraints
Expand Down Expand Up @@ -413,7 +420,7 @@ cdef class CRT(Certificate):
return DER_to_PEM(self.to_DER(), "Certificate")

def sign(self, csr, issuer_key, not_before, not_after, serial_number,
ca=False, max_path_length=0):
basic_constraints=BasicConstraints()):
"""Return a new, signed certificate for the CSR."""
if not _pk.check_pair(
self.subject_public_key,
Expand All @@ -429,13 +436,12 @@ cdef class CRT(Certificate):
subject_key=csr.subject_public_key,
serial_number=serial_number,
digestmod=csr.digestmod,
ca=ca,
max_path_length=max_path_length)
basic_constraints=basic_constraints)
return crt

@classmethod
def selfsign(cls, csr, issuer_key, not_before, not_after, serial_number,
ca=False, max_path_length=0):
basic_constraints=BasicConstraints()):
"""Return a new, self-signed certificate for the CSR."""
return cls.new(
not_before=not_before,
Expand All @@ -446,8 +452,7 @@ cdef class CRT(Certificate):
subject_key=csr.subject_public_key,
serial_number=serial_number,
digestmod=csr.digestmod,
ca=ca,
max_path_length=max_path_length)
basic_constraints=basic_constraints)

def verify(self, crt):
"""Verify the certificate `crt`."""
Expand All @@ -456,12 +461,12 @@ cdef class CRT(Certificate):

@staticmethod
def new(not_before, not_after, issuer, issuer_key, subject, subject_key,
serial_number, digestmod, ca=False, max_path_length=0):
serial_number, digestmod, basic_constraints=BasicConstraints()):
"""Return a new certificate."""
return _CRTWriter(
not_before, not_after, issuer, issuer_key,
subject, subject_key, serial_number, digestmod,
ca, max_path_length).to_certificate()
basic_constraints=basic_constraints).to_certificate()


cdef class _CRTWriter:
Expand All @@ -473,7 +478,7 @@ cdef class _CRTWriter:
"""
def __init__(self, not_before, not_after, issuer, issuer_key,
subject, subject_key, serial_number, digestmod,
ca=False, max_path_length=0):
basic_constraints=BasicConstraints()):
super(_CRTWriter, self).__init__()
self.set_validity(not_before, not_after)
self.set_issuer(issuer)
Expand All @@ -482,7 +487,7 @@ cdef class _CRTWriter:
self.set_subject_key(subject_key)
self.set_serial_number(serial_number)
self.set_digestmod(digestmod)
self.set_basic_constraints(ca, max_path_length)
self.set_basic_constraints(basic_constraints)

def __cinit__(self):
"""Initialize a CRT write context."""
Expand Down Expand Up @@ -621,17 +626,17 @@ cdef class _CRTWriter:
check_error(
x509.mbedtls_x509write_crt_set_authority_key_identifier(&self._ctx))

def set_basic_constraints(self, ca, max_path_length):
def set_basic_constraints(self, basic_constraints):
"""Set the basic constraints extension for a CRT.
Args:
ca (bool): True if this is a CA certificate.
max_path_length (int): Maximum length of a certificate below
this certificate, -1 is unlimited.
basic_constraints (BasicConstraints): The basic constraints.
"""
if not basic_constraints:
return
check_error(x509.mbedtls_x509write_crt_set_basic_constraints(
&self._ctx, int(ca), max_path_length))
&self._ctx, int(basic_constraints[0]), basic_constraints[1]))


cdef class CSR(Certificate):
Expand Down
7 changes: 4 additions & 3 deletions tests/test_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import mbedtls.hash as hashlib
from mbedtls.exceptions import TLSError
from mbedtls.pk import RSA, ECC
from mbedtls.x509 import CRT, CSR
from mbedtls.x509 import BasicConstraints, CRT, CSR
from mbedtls.tls import *


Expand Down Expand Up @@ -96,14 +96,15 @@ def ca0_crt(self, ca0_key, digestmod, now):
return CRT.selfsign(
ca0_csr, ca0_key,
not_before=now, not_after=now + dt.timedelta(days=90),
serial_number=0x123456, ca=True, max_path_length=-1)
serial_number=0x123456,
basic_constraints=BasicConstraints(True, -1))

@pytest.fixture(scope="class")
def ca1_crt(self, ca1_key, ca0_crt, ca0_key, digestmod, now):
ca1_csr = CSR.new(ca1_key, "CN=Intermediate CA", digestmod())
return ca0_crt.sign(
ca1_csr, ca0_key, now, now + dt.timedelta(days=90), 0x234567,
ca=True, max_path_length=1)
basic_constraints=BasicConstraints(True, -1))

@pytest.fixture(scope="class")
def ee0_crt(self, ee0_key, ca1_crt, ca1_key, digestmod, now):
Expand Down
25 changes: 13 additions & 12 deletions tests/test_x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ def digestmod(self):
return hash.sha256()

@pytest.fixture
def is_ca(self):
return False, 0
def basic_constraints(self):
return BasicConstraints(False, 0)

@pytest.fixture
def x509(self,
Expand All @@ -132,9 +132,8 @@ def x509(self,
subject, subject_key,
serial_number,
digestmod,
is_ca
basic_constraints
):
is_ca, max_path_length = is_ca
return CRT.new(
not_before=now,
not_after=now + dt.timedelta(days=90),
Expand All @@ -144,8 +143,7 @@ def x509(self,
subject_key=subject_key,
serial_number=serial_number,
digestmod=digestmod,
ca=is_ca,
max_path_length=max_path_length)
basic_constraints=basic_constraints)

@pytest.fixture
def crt(self, x509):
Expand Down Expand Up @@ -205,12 +203,14 @@ def test_digestmod(self, crt, digestmod):

class TestCRTCAPath(_CRTBase):

@pytest.fixture(params=[(True, 0), (True, 2), (False, 0)])
def is_ca(self, request):
@pytest.fixture(params=[(True, 0), (True, 2), (False, 0), None])
def basic_constraints(self, request):
return request.param

def test_ca(self, crt, is_ca):
assert (crt.ca, crt.max_path_length) == is_ca
def test_ca(self, crt, basic_constraints):
if basic_constraints is None:
basic_constraints = BasicConstraints()
assert crt.basic_constraints == basic_constraints


class _CSRBase(_X509Base):
Expand Down Expand Up @@ -331,14 +331,15 @@ def ca0_crt(self, ca0_key, now):
return CRT.selfsign(
ca0_csr, ca0_key,
not_before=now, not_after=now + dt.timedelta(days=90),
serial_number=0x123456, ca=True, max_path_length=-1)
serial_number=0x123456,
basic_constraints=BasicConstraints(True, -1))

@pytest.fixture
def ca1_crt(self, ca1_key, ca0_crt, ca0_key, now):
ca1_csr = CSR.new(ca1_key, "CN=Intermediate CA", hash.sha256())
return ca0_crt.sign(
ca1_csr, ca0_key, now, now + dt.timedelta(days=90), 0x234567,
ca=True, max_path_length=1)
basic_constraints=BasicConstraints(True, 1))

@pytest.fixture
def ee0_crt(self, ee0_key, ca1_crt, ca1_key, now):
Expand Down

0 comments on commit e17d50b

Please sign in to comment.