From df8131a3019563df6d2fb79a9cc9a8f7974eb0d1 Mon Sep 17 00:00:00 2001 From: Mathias Laurin Date: Sat, 10 Feb 2018 10:41:37 +0100 Subject: [PATCH 1/7] Conf: update sphinx config --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index cdd168b4..d5288b27 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,7 +34,7 @@ 'sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', - 'sphinxcontrib.napoleon', + 'sphinx.ext.napoleon', ] autodoc_default_flags = ['members', @@ -67,7 +67,7 @@ # built documents. # # The short X.Y version. -version = '0.5' +version = '0.7' # The full version, including alpha/beta/rc tags. release = version From 13b344f2539efb220899cfe2182d39edbebfdfed Mon Sep 17 00:00:00 2001 From: Mathias Laurin Date: Mon, 19 Feb 2018 22:16:18 +0100 Subject: [PATCH 2/7] Minimal wrapper to MPI/bignum.h --- mbedtls/_mpi.pxd | 75 +++++++++++++++++++++++++++++++++ mbedtls/_mpi.pyx | 105 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_mpi.py | 33 +++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 mbedtls/_mpi.pxd create mode 100644 mbedtls/_mpi.pyx create mode 100644 tests/test_mpi.py diff --git a/mbedtls/_mpi.pxd b/mbedtls/_mpi.pxd new file mode 100644 index 00000000..0541d721 --- /dev/null +++ b/mbedtls/_mpi.pxd @@ -0,0 +1,75 @@ +"""Declaration from `mbedtls/bignum.h`.""" + +__author__ = "Mathias Laurin" +__copyright__ = "Copyright 2018, Mathias Laurin" +__license__ = "MIT License" + + +cdef extern from "mbedtls/bignum.h": + # Multi-precision integer library + # ------------------------------- + ctypedef enum mbedtls_mpi: pass + ctypedef enum mbedtls_mpi_sint: pass + + # mbedtls_mpi + # ----------- + void mbedtls_mpi_init( mbedtls_mpi *X ) + void mbedtls_mpi_free( mbedtls_mpi *X ); + + # mbedtls_mpi_grow + # mbedtls_mpi_shrink + # mbedtls_mpi_copy + # mbedtls_mpi_swap + # mbedtls_mpi_safe_cond_assign + # mbedtls_mpi_safe_cond_swap + # mbedtls_mpi_lset // limited to 64-bits + # mbedtls_mpi_get_bit + # mbedtls_mpi_set_bit + # mbedtls_mpi_lsb + + size_t mbedtls_mpi_bitlen(const mbedtls_mpi *X) + size_t mbedtls_mpi_size(const mbedtls_mpi *X) + + # mbedtls_mpi_read_string + # mbedtls_mpi_write_string + # mbedtls_mpi_read_file + # mbedtls_mpi_write_file + + int mbedtls_mpi_read_binary( + mbedtls_mpi *X, + const unsigned char *buf, + size_t buflen) + int mbedtls_mpi_write_binary( + mbedtls_mpi *X, + unsigned char *buf, + size_t buflen) + + # mbedtls_mpi_shift_l + # mbedtls_mpi_shift_r + # mbedtls_mpi_cmp_abs + # mbedtls_mpi_cmp_mpi + # mbedtls_mpi_cmp_int + # mbedtls_mpi_add_abs + # mbedtls_mpi_sub_abs + # mbedtls_mpi_add_mpi + # mbedtls_mpi_sub_mpi + # mbedtls_mpi_add_int + # mbedtls_mpi_sub_int + # mbedtls_mpi_mul_mpi + # mbedtls_mpi_mul_int + # mbedtls_mpi_div_mpi + # mbedtls_mpi_div_int + # mbedtls_mpi_mod_mpi + # mbedtls_mpi_mod_int + # mbedtls_mpi_exp_mod + # mbedtls_mpi_fill_random + # mbedtls_mpi_gcd + # mbedtls_mpi_inv_mod + # mbedtls_mpi_is_prime + # mbedtls_mpi_gen_prime + + +cdef class MPI: + cdef mbedtls_mpi _ctx + cdef _len(self) + cpdef _from_bytes(self, const unsigned char[:] bytes) diff --git a/mbedtls/_mpi.pyx b/mbedtls/_mpi.pyx new file mode 100644 index 00000000..fcee867e --- /dev/null +++ b/mbedtls/_mpi.pyx @@ -0,0 +1,105 @@ +"""Multi-precision integer library (MPI).""" + +__author__ = "Mathias Laurin" +__copyright__ = "Copyright 2018, Mathias Laurin" +__license__ = "MIT License" + + +cimport _mpi +from libc.stdlib cimport malloc, free + +import numbers +from binascii import hexlify, unhexlify + +from mbedtls.exceptions import * + +try: + long +except NameError: + long = int + + +cdef to_bytes(value): + return unhexlify("{0:02x}".format(value).encode("ascii")) + + +cdef from_bytes(value): + return long(hexlify(value), 16) + + +cdef class MPI: + """Multi-precision integer. + + Only minimal bindings here because Python already has + arbitrary-precision integers. + + """ + def __init__(self, value): + if value is None: + return # Implementation detail. + try: + value = to_bytes(value) + except TypeError: + pass + self._from_bytes(bytearray(value)) + + def __cinit__(self): + """Initialize one MPI.""" + _mpi.mbedtls_mpi_init(&self._ctx) + + def __dealloc__(self): + """Unallocate one MPI.""" + _mpi.mbedtls_mpi_free(&self._ctx) + + cdef _len(self): + """Return the total size in bytes.""" + return _mpi.mbedtls_mpi_size(&self._ctx) + + cpdef _from_bytes(self, const unsigned char[:] bytes): + check_error( + _mpi.mbedtls_mpi_read_binary(&self._ctx, &bytes[0], bytes.shape[0])) + return self + + def __str__(self): + return "%i" % long(self) + + def bit_length(self): + """Return the number of bits necessary to represent MPI in binary.""" + return _mpi.mbedtls_mpi_bitlen(&self._ctx) + + def __eq__(self, other): + if not isinstance(other, numbers.Integral): + raise NotImplemented + return long(self) == other + + @classmethod + def from_int(cls, value): + # mbedtls_mpi_lset is 'limited' to 64 bits. + return cls.from_bytes(to_bytes(value), byteorder="big") + + def __int__(self): + return from_bytes(self.to_bytes(self._len(), byteorder="big")) + + @classmethod + def from_bytes(cls, bytes, byteorder): + assert byteorder in {"big", "little"} + order = slice(None, None, -1 if byteorder is "little" else None) + return cls(None)._from_bytes(bytearray(bytes[order])) + + def to_bytes(self, length, byteorder): + assert byteorder in {"big", "little"} + order = slice(None, None, -1 if byteorder is "little" else None) + cdef unsigned char* output = malloc( + length * sizeof(unsigned char)) + if not output: + raise MemoryError() + try: + check_error(_mpi.mbedtls_mpi_write_binary( + &self._ctx, output, length)) + return bytes(output[:length])[order] + except Exception as exc: + raise OverflowError from exc + finally: + free(output) + + __bytes__ = to_bytes diff --git a/tests/test_mpi.py b/tests/test_mpi.py new file mode 100644 index 00000000..6316c02c --- /dev/null +++ b/tests/test_mpi.py @@ -0,0 +1,33 @@ +from binascii import hexlify, unhexlify + +import pytest + +from mbedtls._mpi import MPI + + +@pytest.mark.parametrize("value", (12, 2**32 - 1, 10**100)) +def test_from_int(value): + mpi = MPI.from_int(value) + assert mpi == value + + +@pytest.mark.parametrize("value", (12, 2**32 - 1, 10**100)) +def test_bit_length(value): + mpi = MPI(value) + assert mpi == value + assert mpi.bit_length() == value.bit_length() + + +def test_from_bytes(): + value = unhexlify(b"DEEADBEEFF") + mpi = MPI.from_bytes(value, byteorder="big") + assert mpi.to_bytes(5, byteorder="big") == value + assert mpi.to_bytes(5, byteorder="little") == value[::-1] + assert mpi == int(hexlify(value), 16) + + +def test_to_bytes_overflow(): + value = unhexlify(b"DEEADBEEFF") + mpi = MPI.from_bytes(value, byteorder="big") + with pytest.raises(OverflowError): + mpi.to_bytes(2, byteorder="big") From 0f1d68447cf1519ed3b89d8c23ed3c51cee75f45 Mon Sep 17 00:00:00 2001 From: Mathias Laurin Date: Fri, 23 Feb 2018 15:42:42 +0100 Subject: [PATCH 3/7] Add pathlib2 to py27 env --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 90e0e7a0..56ace39c 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,9 @@ envlist = py27, py34, py35, py36 [testenv] -deps = -rrequirements.txt +deps = + -rrequirements.txt + py27: pathlib2 usedevelop = true passenv=DYLD_LIBRARY_PATH LD_LIBRARY_PATH commands=pytest mbedtls tests From bdd86eae3b40e082ecaca3fdb4445a17a4fffd43 Mon Sep 17 00:00:00 2001 From: Mathias Laurin Date: Fri, 16 Feb 2018 22:58:34 +0100 Subject: [PATCH 4/7] X509 module --- Makefile | 2 +- mbedtls/exceptions.pyx | 25 ++ mbedtls/x509.pxd | 229 ++++++++++ mbedtls/x509.pyx | 473 +++++++++++++++++++++ tests/ca/wikipedia.pem | 48 +++ tests/ca/wp_crl.pem | 787 +++++++++++++++++++++++++++++++++++ tests/ca/wp_intermediate.pem | 28 ++ tests/test_x509.py | 224 ++++++++++ 8 files changed, 1815 insertions(+), 1 deletion(-) create mode 100644 mbedtls/x509.pxd create mode 100644 mbedtls/x509.pyx create mode 100644 tests/ca/wikipedia.pem create mode 100644 tests/ca/wp_crl.pem create mode 100644 tests/ca/wp_intermediate.pem create mode 100644 tests/test_x509.py diff --git a/Makefile b/Makefile index bc10c035..f83616ee 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ PYX = $(wildcard mbedtls/*.pyx) PYX += $(wildcard mbedtls/cipher/*.pyx) PYX += $(wildcard mbedtls/pk/*.pyx) -LIBMBEDTLS = $(HOME)/lib/mbedtls-2.5.2 +LIBMBEDTLS = $(HOME)/lib/mbedtls-2.4.2 release: cython $(PYX) diff --git a/mbedtls/exceptions.pyx b/mbedtls/exceptions.pyx index e3d56146..83c35eb3 100644 --- a/mbedtls/exceptions.pyx +++ b/mbedtls/exceptions.pyx @@ -69,6 +69,10 @@ class EcError(PkError): """Errors defined in the ecp module.""" +class X509Error(_ErrorBase): + """Errors defined in the x509 module.""" + + __lookup = { # Blowfish-specific 0x0016: (InvalidKeyLengthError, "invalid key length"), @@ -107,6 +111,26 @@ __lookup = { 0x1400: (PemError, "unavailable feature, e.g. hashing/decryption combination"), 0x1480: (PemError, "bad input parameters to function"), + # X509 errors + 0x2080: (X509Error, "feature unavailable"), + 0x2100: (X509Error, "unknown OID"), + 0x2180: (X509Error, "invalid format"), + 0x2200: (X509Error, "invalid version"), + 0x2280: (X509Error, "invalid, serial"), + 0x2300: (X509Error, "invalid alg"), + 0x2380: (X509Error, "invalid name"), + 0x2400: (X509Error, "invalid date"), + 0x2480: (X509Error, "invalid signature"), + 0x2500: (X509Error, "invalid extensions"), + 0x2580: (X509Error, "unknown version"), + 0x2600: (X509Error, "unknown sig alg"), + 0x2680: (X509Error, "sig mismatch"), + 0x2700: (X509Error, "cert verify failed"), + 0x2780: (X509Error, "cert unknown format"), + 0x2800: (X509Error, "bad input data"), + 0x2880: (X509Error, "alloc failed"), + 0x2900: (X509Error, "file io error"), + 0x2980: (X509Error, "buffer too small"), # PK errors 0x3f80: (PkError, "memory allocation failed"), 0x3f00: (PkError, @@ -175,3 +199,4 @@ cpdef check_error(const int err): if err < 0: exc, msg = __lookup.get(-err, (_ErrorBase, "")) raise exc(-err, msg) + return err diff --git a/mbedtls/x509.pxd b/mbedtls/x509.pxd new file mode 100644 index 00000000..e33b946b --- /dev/null +++ b/mbedtls/x509.pxd @@ -0,0 +1,229 @@ +"""Declarations from `mbedtls/x509*.h`. + + - CSR: Certificate signing request parsing and writing. + - CRL: Certificate revocation list parsing. + - CRT: Certificate parsing and writing. + +""" + +__author__ = "Mathias Laurin" +__copyright__ = "Copyright 2018, Mathias Laurin" +__license__ = "MIT License" + + +cdef extern from "mbedtls/bignum.h": + ctypedef enum mbedtls_mpi: pass + + +cdef extern from "mbedtls/md.h": + ctypedef enum mbedtls_md_type_t: pass + + +cdef extern from "mbedtls/pk.h": + ctypedef enum mbedtls_pk_context: pass + + +cdef extern from "mbedtls/x509_crt.h": + ctypedef enum mbedtls_x509_crt: pass + ctypedef enum mbedtls_x509_crt_profile: pass + ctypedef enum mbedtls_x509write_cert: pass + + # mbedtls_x509_crt + # ---------------- + int mbedtls_x509_crt_parse_der( + mbedtls_x509_crt *chain, + const unsigned char *buf, + size_t buflen) + int mbedtls_x509_crt_parse( + mbedtls_x509_crt *chain, + const unsigned char *buf, + size_t buflen) + int mbedtls_x509_crt_parse_file( + mbedtls_x509_crt *chain, + const char *path) + + # mbedtls_x509_crt_parse_path + + int mbedtls_x509_crt_info( + char *buf, + size_t size, + const char* prefix, + const mbedtls_x509_crt *crt) + + # mbedtls_x509_crt_verify_info + # mbedtls_x509_crt_verify + # mbedtls_x509_crt_verify_with_profile + # mbedtls_x509_crt_check_key_usage + # mbedtls_x509_crt_check_extended_key_usage + + int mbedtls_x509_crt_is_revoked( + const mbedtls_x509_crt *crt, + const mbedtls_x509_crl *crl) + + void mbedtls_x509_crt_init(mbedtls_x509_crt *crt) + void mbedtls_x509_crt_free(mbedtls_x509_crt *crt) + + # mbedtls_x509write_cert + # ---------------------- + + void mbedtls_x509write_crt_init(mbedtls_x509write_cert *ctx) + void mbedtls_x509write_crt_set_version( + mbedtls_x509write_cert *ctx, + int version) + int mbedtls_x509write_crt_set_serial( + mbedtls_x509write_cert *ctx, + mbedtls_mpi *serial) + + int mbedtls_x509write_crt_set_validity( + mbedtls_x509write_cert *ctx, + const char *not_before, + const char *not_after) + int mbedtls_x509write_crt_set_issuer_name( + mbedtls_x509write_cert *ctx, + const char *issuer_name) + int mbedtls_x509write_crt_set_subject_name( + mbedtls_x509write_cert *ctx, + const char *subject_name) + void mbedtls_x509write_crt_set_subject_key( + mbedtls_x509write_cert *ctx, + mbedtls_pk_context *key) + void mbedtls_x509write_crt_set_issuer_key( + mbedtls_x509write_cert *ctx, + mbedtls_pk_context *key) + void mbedtls_x509write_crt_set_md_alg( + mbedtls_x509write_cert *ctx, + mbedtls_md_type_t md_alg) + + # mbedtls_x509write_crt_set_extension + # mbedtls_x509write_crt_set_basic_constraints + + int mbedtls_x509write_crt_set_subject_key_identifier( + mbedtls_x509write_cert *ctx) + int mbedtls_x509write_crt_set_authority_key_identifier( + mbedtls_x509write_cert *ctx) + + # mbedtls_x509write_crt_set_key_usage + # mbedtls_x509write_crt_set_ns_cert_type + + void mbedtls_x509write_crt_free(mbedtls_x509write_cert *ctx) + + int mbedtls_x509write_crt_der( + mbedtls_x509write_cert *ctx, + unsigned char *buf, size_t size, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng) + int mbedtls_x509write_crt_pem( + mbedtls_x509write_cert *ctx, + unsigned char *buf, size_t size, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng) + + +cdef extern from "mbedtls/x509_csr.h": + # Certificate signing request parsing and writing + # ----------------------------------------------- + ctypedef enum mbedtls_x509_csr: pass + ctypedef enum mbedtls_x509write_csr: pass + + # mbedtls_x509_csr + # ---------------- + int mbedtls_x509_csr_parse_der( + mbedtls_x509_csr *csr, + const unsigned char *buf, + size_t buflen) + int mbedtls_x509_csr_parse( + mbedtls_x509_csr *csr, + const unsigned char *buf, + size_t buflen) + int mbedtls_x509_csr_parse_file( + mbedtls_x509_csr *csr, + const char *path) + int mbedtls_x509_csr_info( + char *buf, + size_t size, + const char* prefix, + const mbedtls_x509_csr *csr) + void mbedtls_x509_csr_init(mbedtls_x509_csr *csr) + void mbedtls_x509_csr_free(mbedtls_x509_csr *csr) + + # mbedtls_x509write_csr + # --------------------- + void mbedtls_x509write_csr_init(mbedtls_x509write_csr *ctx) + + int mbedtls_x509write_csr_set_subject_name( + mbedtls_x509write_csr *ctx, + const char *subject_name) + void mbedtls_x509write_csr_set_key( + mbedtls_x509write_csr *ctx, + mbedtls_pk_context *key) + void mbedtls_x509write_csr_set_md_alg( + mbedtls_x509write_csr *ctx, + mbedtls_md_type_t md_alg) + + # mbedtls_x509write_csr_set_key_usage + # mbedtls_x509write_csr_set_ns_cert_type + # mbedtls_x509write_csr_set_extension + + void mbedtls_x509write_csr_free(mbedtls_x509write_csr *ctx) + + int mbedtls_x509write_csr_der( + mbedtls_x509write_csr *ctx, + unsigned char *buf, size_t size, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng) + int mbedtls_x509write_csr_pem( + mbedtls_x509write_csr *ctx, + unsigned char *buf, size_t size, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng) + + +cdef extern from "mbedtls/x509_crl.h": + # Certificate revocation list parsing + # ----------------------------------- + ctypedef enum mbedtls_x509_crl_entry: pass + ctypedef enum mbedtls_x509_crl: pass + + # mbedtls_x509_crl + # ---------------- + int mbedtls_x509_crl_parse_der( + mbedtls_x509_crl *chain, + const unsigned char *buf, + size_t buflen) + int mbedtls_x509_crl_parse( + mbedtls_x509_crl *chain, + const unsigned char *buf, + size_t buflen) + int mbedtls_x509_crl_parse_file( + mbedtls_x509_crl *chain, + const char *path) + int mbedtls_x509_crl_info( + char *buf, + size_t size, + const char *prefix, + const mbedtls_x509_crl *crl) + void mbedtls_x509_crl_init(mbedtls_x509_crl *crl) + void mbedtls_x509_crl_free(mbedtls_x509_crl *crl) + + +cdef class Certificate: + cdef mbedtls_x509_crt _ctx + cpdef _from_buffer(cls, unsigned char[:] buffer) + + +cdef class CertificateWriter: + cdef mbedtls_x509write_cert _ctx + + +cdef class CSR: + cdef mbedtls_x509_csr _ctx + cpdef _from_buffer(cls, unsigned char[:] buffer) + + +cdef class CSRWriter: + cdef mbedtls_x509write_csr _ctx + + +cdef class CRL: + cdef mbedtls_x509_crl _ctx + cpdef _from_buffer(cls, unsigned char[:] buffer) diff --git a/mbedtls/x509.pyx b/mbedtls/x509.pyx new file mode 100644 index 00000000..d06f1ae8 --- /dev/null +++ b/mbedtls/x509.pyx @@ -0,0 +1,473 @@ +"""Structure and functions for parsing and writing X.509 certificates.""" + +__author__ = "Mathias Laurin" +__copyright__ = "Copyright 2018, Mathias Laurin" +__license__ = "MIT License" + + +from libc.stdlib cimport malloc, free +cimport x509 + +cimport mbedtls._mpi as _mpi +cimport mbedtls.pk._pk as _pk + +import base64 + +from mbedtls.exceptions import * + + +cdef class Certificate: + """X.509 certificate.""" + + def __init__(self, buffer): + if buffer is None: + return # Implementation detail. + self._from_buffer(bytearray(buffer)) + + def __cinit__(self): + """Initialize a certificate (chain).""" + x509.mbedtls_x509_crt_init(&self._ctx) + + def __dealloc__(self): + """Unallocate all certificate data.""" + x509.mbedtls_x509_crt_free(&self._ctx) + + def __str__(self): + cdef size_t osize = 2**24 + cdef char* output = malloc(osize * sizeof(char)) + cdef char* prefix = b"" + if not output: + raise MemoryError() + try: + written = check_error(x509.mbedtls_x509_crt_info( + &output[0], osize, prefix, &self._ctx)) + return bytes(output[:written]).decode("utf8") + finally: + free(output) + + cpdef _from_buffer(self, unsigned char[:] buf): + check_error( + x509.mbedtls_x509_crt_parse(&self._ctx, &buf[0], buf.shape[0])) + return self + + def check_revocation(self, CRL crl): + """Return True if the certificate is revoked, False otherwise.""" + return bool(x509.mbedtls_x509_crt_is_revoked(&self._ctx, &crl._ctx)) + + @classmethod + def from_buffer(cls, buffer): + # PEP 543 + return cls(None)._from_buffer(bytearray(buffer)) + + @classmethod + def from_file(cls, path): + # PEP 543, test pathlib + cdef char[:] c_path = bytearray(str(path).encode("utf8")) + cdef Certificate self = cls(None) + check_error(x509.mbedtls_x509_crt_parse_file(&self._ctx, &c_path[0])) + return self + + @classmethod + def from_DER(cls, buffer): + cdef unsigned char[:] c_buffer = bytearray(buffer) + cdef Certificate self = cls(None) + check_error(x509.mbedtls_x509_crt_parse_der( + &self._ctx, &c_buffer[0], c_buffer.shape[0])) + return self + + @staticmethod + def new(start, end, issuer, issuer_key, subject, subject_key, + serial, md_alg): + """Return a new certificate.""" + return CertificateWriter( + start, end, issuer, issuer_key, + subject, subject_key, serial, md_alg).to_certificate() + + +cdef class CertificateWriter: + """CRT writing context. + + This class should not be used directly. + Use `Certificate.new()` instead. + + """ + + def __init__(self, start, end, issuer, issuer_key, + subject, subject_key, serial, md_alg): + super(CertificateWriter, self).__init__() + self.set_validity(start, end) + self.set_issuer(issuer) + self.set_issuer_key(issuer_key) + self.set_subject(subject) + self.set_subject_key(subject_key) + self.set_serial(serial) + self.set_algorithm(md_alg) + + def __cinit__(self): + """Initialize a CRT write context.""" + x509.mbedtls_x509write_crt_init(&self._ctx) + + def __dealloc__(self): + """Free the contents of a CRT write context.""" + x509.mbedtls_x509write_crt_free(&self._ctx) + + def to_DER(self): + """Return the certificate in DER format. + + Warning: + No RNG function is used. + + """ + cdef size_t osize = 4096 + cdef unsigned char* output = malloc( + osize * sizeof(unsigned char)) + if not output: + raise MemoryError() + try: + written = check_error(x509.mbedtls_x509write_crt_der( + &self._ctx, &output[0], osize, NULL, NULL)) + return bytes(output[osize - written:osize]) + finally: + free(output) + + to_bytes = to_DER + + def to_PEM(self): + """Return the Certificate in PEM format. + + Warning: + No RNG function is used. + + """ + cdef size_t osize = 4096 + cdef unsigned char* output = malloc( + osize * sizeof(unsigned char)) + if not output: + raise MemoryError() + try: + check_error(x509.mbedtls_x509write_crt_pem( + &self._ctx, &output[0], osize, NULL, NULL)) + return output.decode("ascii") + finally: + free(output) + + __str__ = to_PEM + + def to_certificate(self): + """Return a Certificate object.""" + return Certificate.from_DER(self.to_DER()) + + def set_version(self, version=3): + """Set the version for a certificate. + + Arg: + version (int): The version between 1 and 3. + + """ + if version not in range(1, 4): + raise ValueError("version not between 1 and 3") + x509.mbedtls_x509write_crt_set_version(&self._ctx, version - 1) + + def set_serial(self, serial): + """Set the serial number for a certificate. + + Arg: + serial (int or bytes): The serial number. + + """ + if not serial: + return + cdef _mpi.MPI ser = _mpi.MPI(serial) + x509.mbedtls_x509write_crt_set_serial(&self._ctx, &ser._ctx) + + def set_validity(self, start, end): + """Set the validity period for a certificate. + + Args: + start (datetime): Begin timestamp. + end (datetime): End timestamp. + + """ + fmt = "%Y%m%d%H%M%S" + cdef char[:] c_start = bytearray(start.strftime(fmt).encode("ascii")) + cdef char[:] c_end = bytearray(end.strftime(fmt).encode("ascii")) + check_error(x509.mbedtls_x509write_crt_set_validity( + &self._ctx, &c_start[0], &c_end[0])) + + def set_issuer(self, issuer): + """Set the issuer name. + + Args: + issuer (str): Comma-separated list of OID types and values: + e.g. "C=UK,I=ARM,CN=mbed TLS CA" + + """ + cdef char[:] c_issuer = bytearray(issuer.encode("utf8")) + check_error(x509.mbedtls_x509write_crt_set_issuer_name( + &self._ctx, &c_issuer[0])) + + def set_subject(self, subject): + """Set the subject name for a certificate. + + Args: + subject (str): Subject name as a comma-separated list + of OID types and values. + e.g. "C=UK,O=ARM,CN=mbed TLS Server 1" + + """ + if not subject: + return + cdef char[:] c_subject = bytearray(subject.encode("utf8")) + check_error(x509.mbedtls_x509write_crt_set_subject_name( + &self._ctx, &c_subject[0])) + + def set_algorithm(self, md_alg): + """Set MD algorithm to use for the signature. + + Args: + md_alg (MDBase): MD algorithm, for ex. `hash.sha1()`. + + """ + x509.mbedtls_x509write_crt_set_md_alg(&self._ctx, md_alg._type) + + def set_subject_key(self, _pk.CipherBase key): + """Set the subject key. + + Args: + key (CipherBase): Subject key. + + """ + x509.mbedtls_x509write_crt_set_subject_key(&self._ctx, &key._ctx) + check_error( + x509.mbedtls_x509write_crt_set_subject_key_identifier(&self._ctx)) + + def set_issuer_key(self, _pk.CipherBase key): + """Set the issuer key. + + Args: + key (CipherBase): Issuer key. + + """ + x509.mbedtls_x509write_crt_set_issuer_key( + &self._ctx, &key._ctx) + check_error( + x509.mbedtls_x509write_crt_set_authority_key_identifier(&self._ctx)) + + +cdef class CSR: + """X.509 certificate signing request parser.""" + + def __init__(self, buffer): + super(CSR, self).__init__() + if buffer is None: + return # Implementation detail. + self._from_buffer(bytearray(buffer)) + + def __cinit__(self): + """Initialize a CSR.""" + x509.mbedtls_x509_csr_init(&self._ctx) + + def __dealloc__(self): + """Unallocate all CSR data.""" + x509.mbedtls_x509_csr_free(&self._ctx) + + def __str__(self): + cdef size_t osize = 2**24 + cdef char* output = malloc(osize * sizeof(char)) + cdef char* prefix = b"" + if not output: + raise MemoryError() + try: + written = check_error(x509.mbedtls_x509_csr_info( + &output[0], osize, prefix, &self._ctx)) + return bytes(output[:written]).decode("utf8") + finally: + free(output) + + cpdef _from_buffer(self, unsigned char[:] buf): + check_error( + x509.mbedtls_x509_csr_parse(&self._ctx, &buf[0], buf.shape[0])) + return self + + @classmethod + def from_buffer(cls, buffer): + # PEP 543 + return cls(None)._from_buffer(bytearray(buffer)) + + @classmethod + def from_file(cls, path): + # PEP 543, test pathlib + cdef char[:] c_path = bytearray(str(path).encode("utf8")) + cdef CSR self = cls(None) + check_error(x509.mbedtls_x509_csr_parse_file(&self._ctx, &c_path[0])) + return self + + @classmethod + def from_DER(cls, buffer): + cdef unsigned char[:] c_buffer = bytearray(buffer) + cdef CSR self = cls(None) + check_error(x509.mbedtls_x509_csr_parse_der( + &self._ctx, &c_buffer[0], c_buffer.shape[0])) + return self + + @staticmethod + def new(key, md_alg, subject): + """Return a new CSR.""" + return CSRWriter(key, md_alg, subject).to_certificate() + + +cdef class CSRWriter: + """X.509 CSR writing context. + + This class should not be used directly. Use `CSR.new()` instead. + + """ + def __init__(self, key, md_alg, subject): + super(CSRWriter, self).__init__() + self.set_key(key) + self.set_algorithm(md_alg) + self.set_subject(subject) + + def __cinit__(self): + """Initialize a CSR context.""" + x509.mbedtls_x509write_csr_init(&self._ctx) + + def __dealloc__(self): + """Free the contents of a CSR context.""" + x509.mbedtls_x509write_csr_free(&self._ctx) + + def set_subject(self, subject): + """Set the subject name for a CSR. + + Args: + subject (str): Subject name as a comma-separated list + of OID types and values. + e.g. "C=UK,O=ARM,CN=mbed TLS Server 1" + + """ + if not subject: + return + cdef char[:] c_subject = bytearray(subject.encode("utf8")) + check_error(x509.mbedtls_x509write_csr_set_subject_name( + &self._ctx, &c_subject[0])) + + def set_key(self, _pk.CipherBase key): + """Set the key for the CSR. + + Args: + key (CipherBase): Asymetric key to include. + + """ + x509.mbedtls_x509write_csr_set_key(&self._ctx, &key._ctx) + + def set_algorithm(self, md_alg): + """Set MD algorithm to use for the signature. + + Args: + md_alg (MDBase): MD algorithm, for ex. `hash.sha1()`. + + """ + x509.mbedtls_x509write_csr_set_md_alg(&self._ctx, md_alg._type) + + def to_DER(self): + """Return the CSR in DER format. + + Warning: + No RNG function is used. + + """ + cdef size_t osize = 4096 + cdef unsigned char* output = malloc( + osize * sizeof(unsigned char)) + if not output: + raise MemoryError() + try: + written = check_error(x509.mbedtls_x509write_csr_der( + &self._ctx, &output[0], osize, NULL, NULL)) + return bytes(output[osize - written:osize]) + finally: + free(output) + + to_bytes = to_DER + + def to_PEM(self): + """Return the CSR in PEM format. + + Warning: + No RNG function is used. + + """ + cdef size_t osize = 4096 + cdef unsigned char* output = malloc( + osize * sizeof(unsigned char)) + if not output: + raise MemoryError + try: + check_error(x509.mbedtls_x509write_csr_pem( + &self._ctx, &output[0], osize, NULL, NULL)) + return output.decode("ascii") + finally: + free(output) + + __str__ = to_PEM + + def to_certificate(self): + """Return a CSR object.""" + return CSR.from_DER(self.to_DER()) + + +cdef class CRL: + """X.509 revocation list.""" + + def __init__(self, buffer): + super(CRL, self).__init__() + if buffer is None: + return # Implementation detail. + self._from_buffer(bytearray(buffer)) + + def __cinit__(self): + """Initialize a CRL (chain).""" + x509.mbedtls_x509_crl_init(&self._ctx) + + def __dealloc__(self): + """Unallocate all CRL data.""" + x509.mbedtls_x509_crl_free(&self._ctx) + + def __str__(self): + cdef size_t osize = 2**24 + cdef char* output = malloc(osize * sizeof(char)) + cdef char* prefix = b"" + if not output: + raise MemoryError() + try: + written = check_error(x509.mbedtls_x509_crl_info( + &output[0], osize, prefix, &self._ctx)) + return bytes(output[:written]).decode("utf8") + finally: + free(output) + + cpdef _from_buffer(self, unsigned char[:] buf): + check_error( + x509.mbedtls_x509_crl_parse(&self._ctx, &buf[0], buf.shape[0])) + return self + + @classmethod + def from_buffer(cls, buffer): + # PEP 543 + return cls(None)._from_buffer(bytearray(buffer)) + + @classmethod + def from_file(cls, path): + # PEP 543, test pathlib + cdef char[:] c_path = bytearray(str(path).encode("utf8")) + cdef CRL self = cls(None) + check_error(x509.mbedtls_x509_crl_parse_file(&self._ctx, &c_path[0])) + return self + + @classmethod + def from_DER(cls, buffer): + cdef unsigned char[:] c_buffer = bytearray(buffer) + cdef CRL self = cls(None) + check_error(x509.mbedtls_x509_crl_parse_der( + &self._ctx, &c_buffer[0], c_buffer.shape[0])) + return self diff --git a/tests/ca/wikipedia.pem b/tests/ca/wikipedia.pem new file mode 100644 index 00000000..56994fdd --- /dev/null +++ b/tests/ca/wikipedia.pem @@ -0,0 +1,48 @@ +-----BEGIN CERTIFICATE----- +MIIIfDCCB2SgAwIBAgIQCDCUYtH+pgrgur/174vFRTANBgkqhkiG9w0BAQsFADBw +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNz +dXJhbmNlIFNlcnZlciBDQTAeFw0xNzEyMjEwMDAwMDBaFw0xOTAxMjQxMjAwMDBa +MHkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T +YW4gRnJhbmNpc2NvMSMwIQYDVQQKExpXaWtpbWVkaWEgRm91bmRhdGlvbiwgSW5j +LjEYMBYGA1UEAwwPKi53aWtpcGVkaWEub3JnMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAE/cBrHlZz40YZjqpIs3sTvwssebIFnhxwfJe3cm2ki4Ij0jLeUCMcPbpt +K3F49QKLsIQN4TZVOuTlBK1uem6H36OCBdIwggXOMB8GA1UdIwQYMBaAFFFo/5Cv +Agd1PMzZZWRiohK4WXI7MB0GA1UdDgQWBBRurRGx7mcc61Td8ipmVMG+0DsoOTCC +AvgGA1UdEQSCAu8wggLrgg8qLndpa2lwZWRpYS5vcmeCDXdpa2lwZWRpYS5vcmeC +ESoubS53aWtpcGVkaWEub3JnghQqLnplcm8ud2lraXBlZGlhLm9yZ4INd2lraW1l +ZGlhLm9yZ4IPKi53aWtpbWVkaWEub3JnghEqLm0ud2lraW1lZGlhLm9yZ4IWKi5w +bGFuZXQud2lraW1lZGlhLm9yZ4INbWVkaWF3aWtpLm9yZ4IPKi5tZWRpYXdpa2ku +b3JnghEqLm0ubWVkaWF3aWtpLm9yZ4INd2lraWJvb2tzLm9yZ4IPKi53aWtpYm9v +a3Mub3JnghEqLm0ud2lraWJvb2tzLm9yZ4IMd2lraWRhdGEub3Jngg4qLndpa2lk +YXRhLm9yZ4IQKi5tLndpa2lkYXRhLm9yZ4IMd2lraW5ld3Mub3Jngg4qLndpa2lu +ZXdzLm9yZ4IQKi5tLndpa2luZXdzLm9yZ4INd2lraXF1b3RlLm9yZ4IPKi53aWtp +cXVvdGUub3JnghEqLm0ud2lraXF1b3RlLm9yZ4IOd2lraXNvdXJjZS5vcmeCECou +d2lraXNvdXJjZS5vcmeCEioubS53aWtpc291cmNlLm9yZ4IPd2lraXZlcnNpdHku +b3JnghEqLndpa2l2ZXJzaXR5Lm9yZ4ITKi5tLndpa2l2ZXJzaXR5Lm9yZ4IOd2lr +aXZveWFnZS5vcmeCECoud2lraXZveWFnZS5vcmeCEioubS53aWtpdm95YWdlLm9y +Z4IOd2lrdGlvbmFyeS5vcmeCECoud2lrdGlvbmFyeS5vcmeCEioubS53aWt0aW9u +YXJ5Lm9yZ4IXd2lraW1lZGlhZm91bmRhdGlvbi5vcmeCGSoud2lraW1lZGlhZm91 +bmRhdGlvbi5vcmeCGyoubS53aWtpbWVkaWFmb3VuZGF0aW9uLm9yZ4ISd21mdXNl +cmNvbnRlbnQub3JnghQqLndtZnVzZXJjb250ZW50Lm9yZ4IGdy53aWtpMA4GA1Ud +DwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0f +BG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItaGEtc2Vy +dmVyLWc2LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt +aGEtc2VydmVyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgGCCsG +AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAECAjCB +gwYIKwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy +dC5jb20wTQYIKwYBBQUHMAKGQWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E +aWdpQ2VydFNIQTJIaWdoQXNzdXJhbmNlU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQC +MAAwggEGBgorBgEEAdZ5AgQCBIH3BIH0APIAdwC72d+8H4pxtZOUI5eqkntHOFeV +CqtS6BqQlmQ2jh7RhQAAAWB6Rb/PAAAEAwBIMEYCIQCYBInG8WHe18O9vaEPnHxH +LYRqOcc7wiG6SCybdpzCTQIhAIgDyjxagryxHN2Y4vraBjPd/OsIi5KWNmf7uXph +IQ6gAHcAh3W/51l8+IxDmV+9827/Vo1HVjb/SrVgwbTq/16ggw8AAAFgekXAKAAA +BAMASDBGAiEAxInIUUoY+UKfTiQfcQt0Bq+RV/oKF6Qvivhful45z3wCIQCUgFZz +FpBXRl1puoXQ8S379GmZm6h8pDEPnrFymBix1DANBgkqhkiG9w0BAQsFAAOCAQEA +Cn7jA1gpa8XvPlZoaYjHBLdcBEAJDKS4at1GHvaXrEGN6wc4tURSlC8J02SV9t9I +QmQ/yVwk4OYzGBqWAqCDNkvCEblead7ENhWEUdGqVzlJT2Pjp2KUtHHLTITmEY2l +GGY7amjEJcyJpxaEbZW8OBoiXajz7DlIl+Inh0mYtAtQl3QK6dnBPRyKBItRvfSy +AjL+a6v0Ad4OHuTjbYpXKRHgKu9ewVxjP2NE668rLy2bCXCdw1H3KX7NtsPHlLW4 +QOtJM8xSDR4B5/V51y6yxilSHgv9rNouLc+7JChE+5aS74OlqwyMIqHg7Tuhbxt9 +w6L7TYVnAT0Usyda5d06Ew== +-----END CERTIFICATE----- diff --git a/tests/ca/wp_crl.pem b/tests/ca/wp_crl.pem new file mode 100644 index 00000000..b161cb94 --- /dev/null +++ b/tests/ca/wp_crl.pem @@ -0,0 +1,787 @@ +-----BEGIN X509 CRL----- +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 +-----END X509 CRL----- diff --git a/tests/ca/wp_intermediate.pem b/tests/ca/wp_intermediate.pem new file mode 100644 index 00000000..8c4c7410 --- /dev/null +++ b/tests/ca/wp_intermediate.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTEvMC0GA1UEAxMmRGlnaUNlcnQgU0hBMiBIaWdoIEFzc3Vy +YW5jZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2 +4C/CJAbIbQRf1+8KZAayfSImZRauQkCbztyfn3YHPsMwVYcZuU+UDlqUH1VWtMIC +Kq/QmO4LQNfE0DtyyBSe75CxEamu0si4QzrZCwvV1ZX1QK/IHe1NnF9Xt4ZQaJn1 +itrSxwUfqJfJ3KSxgoQtxq2lnMcZgqaFD15EWCo3j/018QsIJzJa9buLnqS9UdAn +4t07QjOjBSjEuyjMmqwrIw14xnvmXnG3Sj4I+4G3FhahnSMSTeXXkgisdaScus0X +sh5ENWV/UyU50RwKmmMbGZJ0aAo3wsJSSMs5WqK24V3B3aAguCGikyZvFEohQcft +bZvySC/zA/WiaJJTL17jAgMBAAGjggFJMIIBRTASBgNVHRMBAf8ECDAGAQH/AgEA +MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +NAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy +dC5jb20wSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29t +L0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDA9BgNVHSAENjA0MDIG +BFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQ +UzAdBgNVHQ4EFgQUUWj/kK8CB3U8zNllZGKiErhZcjswHwYDVR0jBBgwFoAUsT7D +aQP4v0cB1JgmGggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABiKlYkD5m3fXPwd +aOpKj4PWUS+Na0QWnqxj9dJubISZi6qBcYRb7TROsLd5kinMLYBq8I4g4Xmk/gNH +E+r1hspZcX30BJZr01lYPf7TMSVcGDiEo+afgv2MW5gxTs14nhr9hctJqvIni5ly +/D6q1UEL2tU2ob8cbkdJf17ZSHwD2f2LSaCYJkJA69aSEaRkCldUxPUd1gJea6zu +xICaEnL6VpPX/78whQYwvwt/Tv9XBZ0k7YXDK/umdaisLRbvfXknsuvCnQsH6qqF +0wGjIChBWUMo0oHjqvbsezt3tkBigAVBRQHvFwY+3sAzm2fTYS5yh+Rp/BIAV0Ae +cPUeybQ= +-----END CERTIFICATE----- diff --git a/tests/test_x509.py b/tests/test_x509.py new file mode 100644 index 00000000..67653f41 --- /dev/null +++ b/tests/test_x509.py @@ -0,0 +1,224 @@ +import base64 +import datetime as dt +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path + +import pytest + +from mbedtls.pk import RSA +from mbedtls import hash +from mbedtls.x509 import * + +# Generate with: +# openssl req -x509 -nodes -days 365 -newkey rsa:1024 -out cert.pem + + +def pem_to_der(pem): + return base64.b64decode( + b"".join(line.encode("ascii") for line in pem.splitlines() + if not line.startswith("-----"))) + + +@pytest.fixture +def now(): + return dt.datetime.utcnow() + + +@pytest.fixture +def issuer_key(): + issuer_key = RSA() + issuer_key.generate(key_size=1024) + return issuer_key + + +@pytest.fixture +def subject_key(): + subject_key = RSA() + subject_key.generate(key_size=1024) + return subject_key + + +class TestCRT: + + @pytest.fixture + def crt_pem(self): + with (Path(__file__).parent / "ca/wikipedia.pem").open() as crt: + return crt.read() + + @pytest.fixture + def crt_der(self, crt_pem): + return pem_to_der(crt_pem) + + def test_from_buffer(self, crt_der): + crt = Certificate.from_buffer(crt_der) + assert "wikipedia.org" in str(crt) + + def test_from_DER(self, crt_der): + crt = Certificate.from_DER(crt_der) + assert "wikipedia.org" in str(crt) + + def test_from_file(self, crt_der, tmpdir): + path = tmpdir.join("key.der") + path.write_binary(crt_der) + crt = Certificate.from_file(path) + assert "wikipedia.org" in str(crt) + + def test_new(self, now, issuer_key, subject_key): + crt = Certificate.new( + start=now, + end=now + dt.timedelta(days=90), + issuer="C=NL,O=PolarSSL,CN=PolarSSL Test CA", + issuer_key=issuer_key, + subject="", + subject_key=subject_key, + serial=0x1234567890, + md_alg=hash.sha1()) + assert "12:34:56:78:90" in str(crt) + + def test_revocation_bad_cast(self, crt_der): + crt = Certificate.from_buffer(crt_der) + with pytest.raises(TypeError): + crt.check_revocation(crt) + + +class TestCRTWriter: + + @pytest.fixture + def crt_writer(self, now, issuer_key, subject_key): + return CertificateWriter( + start=now, end=now + dt.timedelta(days=90), + issuer="C=NL,O=PolarSSL,CN=PolarSSL Test CA", issuer_key=issuer_key, + subject=None, subject_key=subject_key, + md_alg=hash.sha1(), + serial=None) + + def test_to_pem(self, crt_writer): + pem = crt_writer.to_PEM() + assert pem == str(crt_writer.to_PEM()) + assert pem.splitlines()[0] == "-----BEGIN CERTIFICATE-----" + assert pem.splitlines()[-1] == "-----END CERTIFICATE-----" + + def test_to_der(self, crt_writer): + assert pem_to_der(crt_writer.to_PEM()) == crt_writer.to_DER() + + def test_to_bytes(self, crt_writer): + assert crt_writer.to_DER() == crt_writer.to_bytes() + + def test_to_certificate(self, crt_writer): + crt = crt_writer.to_certificate() + assert "cert. version" in str(crt) + assert "PolarSSL" in str(crt) + + def test_set_serial(self, crt_writer): + assert "12:34:56:78:90" not in str(crt_writer.to_certificate()) + + serial = 0x1234567890 + crt_writer.set_serial(serial) + assert "12:34:56:78:90" in str(crt_writer.to_certificate()) + + def test_set_subject(self, crt_writer): + assert "Server 1" not in str(crt_writer.to_certificate()) + + subject = "C=NL,O=PolarSSL,CN=PolarSSL Server 1" + crt_writer.set_subject(subject) + assert "Server 1" in str(crt_writer.to_certificate()) + + +class TestCSR: + + @pytest.fixture + def csr_pem(self, subject_key): + return CSRWriter(subject_key, hash.sha1(), + "C=NL,O=PolarSSL,CN=PolarSSL Server 1").to_PEM() + + @pytest.fixture + def csr_der(self, csr_pem): + return pem_to_der(csr_pem) + + def test_from_buffer(self, csr_der): + csr = CSR.from_buffer(csr_der) + assert "PolarSSL" in str(csr) + + def test_from_DER(self, csr_der): + csr = CSR.from_DER(csr_der) + assert "PolarSSL" in str(csr) + + def test_from_file(self, csr_der, tmpdir): + path = tmpdir.join("key.der") + path.write_binary(csr_der) + csr = CSR.from_file(path) + assert "PolarSSL" in str(csr) + + def test_new(self, subject_key): + csr = CSR.new(subject_key, hash.sha1(), + "C=NL,O=PolarSSL,CN=PolarSSL Server 1") + assert "PolarSSL" in str(csr) + + +class TestCSRWriter: + + @pytest.fixture + def csr_writer(self, subject_key): + return CSRWriter(subject_key, hash.sha1(), + "C=NL,O=PolarSSL,CN=PolarSSL Server 1") + + def test_to_pem(self, csr_writer): + pem = csr_writer.to_PEM() + assert pem == str(csr_writer.to_PEM()) + assert pem.splitlines()[0] == "-----BEGIN CERTIFICATE REQUEST-----" + assert pem.splitlines()[-1] == "-----END CERTIFICATE REQUEST-----" + + def test_to_der(self, csr_writer): + assert pem_to_der(csr_writer.to_PEM()) == csr_writer.to_DER() + + def test_to_bytes(self, csr_writer): + assert csr_writer.to_DER() == csr_writer.to_bytes() + + def test_to_certificate(self, csr_writer): + csr = csr_writer.to_certificate() + + +class TestCRL: + + @pytest.fixture + def crl_pem(self): + with (Path(__file__).parent / "ca/wp_crl.pem").open() as crl: + return crl.read() + + @pytest.fixture + def crl_der(self, crl_pem): + return pem_to_der(crl_pem) + + @pytest.fixture + def crt_pem(self): + with (Path(__file__).parent / "ca/wikipedia.pem").open() as crt: + return crt.read() + + @pytest.fixture + def crt_der(self, crt_pem): + return pem_to_der(crt_pem) + + def test_from_buffer(self, crl_der): + crl = CRL.from_buffer(crl_der) + assert "CRL version" in str(crl) + + def test_from_file(self, crl_der, tmpdir): + path = tmpdir.join("key.der") + path.write_binary(crl_der) + crl = CRL.from_file(path) + assert "CRL version" in str(crl) + + def test_from_der(self, crl_der): + crl = CRL.from_DER(crl_der) + assert "CRL version" in str(crl) + + def test_revocation_false(self, crl_der, crt_der): + crt = Certificate.from_buffer(crt_der) + crl = CRL.from_buffer(crl_der) + assert crt.check_revocation(crl) is False + + @pytest.mark.skip("requires data") + def test_crt_revocation_true(self, crl_der, crt_der): + pass From 95824233127b957d370102b43df369b1dbf6bcd3 Mon Sep 17 00:00:00 2001 From: Mathias Laurin Date: Sat, 24 Feb 2018 21:33:44 +0100 Subject: [PATCH 5/7] RSA API changes Rename import/export functions: - import_() -> from_{buffer,DER,PEM} - export() -> to_{bytes,DER,PEM} The new API is conform to PEP 543 and consistent with x509. --- mbedtls/pk/_pk.pyx | 54 +++++++++++++++++++++++++++-------------- tests/test_pk.py | 60 ++++++++-------------------------------------- 2 files changed, 46 insertions(+), 68 deletions(-) diff --git a/mbedtls/pk/_pk.pyx b/mbedtls/pk/_pk.pyx index 9a8f4736..477ae419 100644 --- a/mbedtls/pk/_pk.pyx +++ b/mbedtls/pk/_pk.pyx @@ -139,7 +139,7 @@ cdef class CipherBase: signature (bytes): The signature to verify. digestmod (optional): The digest name or digest constructor. - Returns: + Return: bool: True if the verification passed, False otherwise. """ @@ -161,7 +161,7 @@ cdef class CipherBase: message (bytes): The message to sign. digestmod (optional): The digest name or digest constructor. - Returns: + Return: bytes or None: The signature or None if the cipher does not contain a private key. @@ -283,7 +283,7 @@ cdef class CipherBase: check_error(_pk.mbedtls_pk_parse_public_key( &self._ctx, &c_key[0], c_key.shape[0])) - def import_(self, key, password=None): + def from_buffer(self, key, password=None): """Import a key (public or private half). The public half is automatically generated upon importing a @@ -300,30 +300,48 @@ cdef class CipherBase: except PkError: self._parse_public_key(key) - def export(self, format="PEM"): - """Export the keys. + from_DER = from_buffer - Arguments: - format (string): One of {"PEM", "DER"}, defaults to PEM. + def from_PEM(self, key, password=None): + """Import a key (public and private half).""" + self.from_buffer(key.encode("ascii"), password=password) + + def to_PEM(self): + """Return the RSA in PEM format. + + Return: + tuple(str, str): The private key and the public key. + + """ + prv, pub = "", "" + if self.has_private(): + prv = self._write_private_key_pem().decode("ascii") + if self.has_public(): + pub = self._write_public_key_pem().decode("ascii") + return prv, pub + + def __str__(self): + return "\n".join(self.to_PEM()) - Returns: + def to_DER(self): + """Return the RSA in DER format. + + Return: tuple(bytes, bytes): The private key and the public key. """ - prv = b"" + prv, pub = b"", b"" if self.has_private(): - if format == "DER": - prv = self._write_private_key_der() - else: - prv = self._write_private_key_pem() - pub = b"" + prv = self._write_private_key_der() if self.has_public(): - if format == "DER": - pub = self._write_public_key_der() - else: - pub = self._write_public_key_pem() + pub = self._write_public_key_der() return prv, pub + to_bytes = to_DER + + def __bytes__(self): + return b"\n".join(self.to_DER()) + cpdef check_pair(CipherBase pub, CipherBase pri): """Check if a public-private pair of keys matches.""" diff --git a/tests/test_pk.py b/tests/test_pk.py index e524614b..9c62d0f5 100644 --- a/tests/test_pk.py +++ b/tests/test_pk.py @@ -84,7 +84,7 @@ def test_rsa_has_private_and_has_public_with_private_key(rsa): assert cipher.has_private() is False assert cipher.has_public() is False - cipher.import_(rsa._write_private_key_der()) + cipher.from_buffer(rsa._write_private_key_der()) assert cipher.has_private() is True assert cipher.has_public() is True @@ -94,47 +94,11 @@ def test_rsa_has_private_and_has_public_with_public_key(rsa): assert cipher.has_private() is False assert cipher.has_public() is False - cipher.import_(rsa._write_public_key_der()) + cipher.from_buffer(rsa._write_public_key_der()) assert cipher.has_private() is False assert cipher.has_public() is True -def test_rsa_write_and_parse_private_key_der(rsa): - prv = rsa._write_private_key_der() - cipher = RSA() - cipher._parse_private_key(prv) - assert check_pair(rsa, cipher) is True # Test private half. - assert check_pair(cipher, rsa) is True # Test public half. - assert check_pair(cipher, cipher) is True - - -def test_rsa_write_and_parse_private_key_pem(rsa): - prv = rsa._write_private_key_pem() - cipher = RSA() - cipher._parse_private_key(prv) - assert check_pair(rsa, cipher) is True # Test private half. - assert check_pair(cipher, rsa) is True # Test public half. - assert check_pair(cipher, cipher) is True - - -def test_rsa_write_and_parse_public_key_der(rsa): - pub = rsa._write_public_key_der() - cipher = RSA() - cipher._parse_public_key(pub) - assert check_pair(rsa, cipher) is False # Test private half. - assert check_pair(cipher, rsa) is True # Test public half. - assert check_pair(cipher, cipher) is False - - -def test_rsa_write_and_parse_public_key_pem(rsa): - pub = rsa._write_public_key_pem() - cipher = RSA() - cipher._parse_public_key(pub) - assert check_pair(rsa, cipher) is False # Test private half. - assert check_pair(cipher, rsa) is True # Test public half. - assert check_pair(cipher, cipher) is False - - def test_rsa_write_public_der_in_private_raises(rsa): pub = rsa._write_public_key_der() cipher = RSA() @@ -151,7 +115,7 @@ def test_rsa_write_private_der_in_public_raises(rsa): def test_rsa_import_public_key(rsa): cipher = RSA() - cipher.import_(rsa._write_public_key_der()) + cipher.from_buffer(rsa._write_public_key_der()) assert check_pair(rsa, cipher) is False # Test private half. assert check_pair(cipher, rsa) is True # Test public half. assert check_pair(cipher, cipher) is False @@ -159,17 +123,16 @@ def test_rsa_import_public_key(rsa): def test_rsa_import_private_key(rsa): cipher = RSA() - cipher.import_(rsa._write_private_key_der()) + cipher.from_buffer(rsa._write_private_key_der()) assert check_pair(rsa, cipher) is True # Test private half. assert check_pair(cipher, rsa) is True # Test public half. assert check_pair(cipher, cipher) is True -@pytest.mark.parametrize("format", ("PEM", "DER")) -def test_rsa_export_private_key(rsa, format): +def test_rsa_to_PEM(rsa): cipher = RSA() - prv, pub = rsa.export(format=format) - cipher.import_(prv) + prv, pub = rsa.to_PEM() + cipher.from_PEM(prv) assert cipher.has_private() is True assert cipher.has_public() is True assert check_pair(rsa, cipher) is True # Test private half. @@ -177,13 +140,10 @@ def test_rsa_export_private_key(rsa, format): assert check_pair(cipher, cipher) is True -@pytest.mark.parametrize("format", ("PEM", "DER")) -def test_rsa_export_private_key_to_file(tmpdir, rsa, format): - prv = tmpdir.join("key.prv") - prv.write_binary(rsa.export(format=format)[0]) - +def test_rsa_to_DER(rsa): cipher = RSA() - cipher.import_(prv.read_binary()) + prv, pub = rsa.to_DER() + cipher.from_DER(prv) assert cipher.has_private() is True assert cipher.has_public() is True assert check_pair(rsa, cipher) is True # Test private half. From 182e3cfb097424a5c8c57bff986a0449d2056987 Mon Sep 17 00:00:00 2001 From: Mathias Laurin Date: Sat, 24 Feb 2018 22:43:49 +0100 Subject: [PATCH 6/7] Cleanup: remove comment --- tests/test_x509.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_x509.py b/tests/test_x509.py index 67653f41..69dff60c 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -11,9 +11,6 @@ from mbedtls import hash from mbedtls.x509 import * -# Generate with: -# openssl req -x509 -nodes -days 365 -newkey rsa:1024 -out cert.pem - def pem_to_der(pem): return base64.b64decode( From 25a8acb53268d4e3b1b3e5d17358650b41ea7987 Mon Sep 17 00:00:00 2001 From: Mathias Laurin Date: Sat, 24 Feb 2018 22:12:32 +0100 Subject: [PATCH 7/7] Update version to 0.8 --- ChangeLog | 23 +++++++++++++--- README.rst | 77 ++++++++++++++++++++++++++++++++++++------------------ setup.py | 26 +++++++++++------- 3 files changed, 89 insertions(+), 37 deletions(-) diff --git a/ChangeLog b/ChangeLog index dc99756d..bf7d4943 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,22 @@ +[0.8] - 2018-02-24 + +Support X.509 Certificates + +* x509/Certificate: X.509 certificate writer and parser. +* x509/CSR: X.509 certificate signing request writer and parser. +* x509/CRL: X.509 certificate revocation list and validation. + +API Changes + +* CipherBase/RSA: import_() method renamed from_buffer() for PEP 543. +* CipherBase/RSA: export(format="PEM") method renamed to_PEM() +* CipherBase/RSA: export(format="DER") method renamed to_DER() +* CipherBase/RSA: from_DER(), from_PEM() to import from DER or PEM. +* CipherBase/RSA: to_bytes() alias to_DER() + + [0.7] - 2018-02-04 -- Add support for Python 2.7 -- Tests ported from nosetest to pytest -- Setup continuous integration +* Add support for Python 2.7, 3.5, and 3.6. +* Tests ported from nosetest to pytest. +* Setup continuous integration. diff --git a/README.rst b/README.rst index 4b9ad952..98221be7 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -========================== -Python wrapper to mbed TLS -========================== +======================================================= +Cryptographic library for Python with Mbed TLS back end +======================================================= .. image:: https://circleci.com/gh/Synss/python-mbedtls/tree/develop.svg?style=svg @@ -11,18 +11,26 @@ Python wrapper to mbed TLS :target: https://coveralls.io/github/Synss/python-mbedtls?branch=develop -`python-mbedtls`_ is a thin wrapper to ARM's mbed TLS library. - -According to the `official mbed TLS website`_ +`python-mbedtls`_ is a free cryptographic library for Python that uses +`mbed TLS`_ for back end. mbed TLS (formerly known as PolarSSL) makes it trivially easy for developers to include cryptographic and SSL/TLS capabilities in their (embedded) products, facilitating this functionality with a minimal coding footprint. -.. _python-mbedtls: https://synss.github.io/python-mbedtls -.. _official mbed TLS website: https://tls.mbed.org +`python-mbedtls` API follows the recommendations from `PEP 452`_: API for +Cryptographic Hash Functions v2.0 and `PEP 272`_ API for Block Encryption +Algorithms v1.0 and can therefore be used as a drop-in replacements to +`PyCrypto`_ or Python's `hashlib`_ and `hmac`_ +.. _python-mbedtls: https://synss.github.io/python-mbedtls +.. _mbed TLS: https://tls.mbed.org +.. _PEP 452: https://www.python.org/dev/peps/pep-0452/ +.. _PEP 272: https://www.python.org/dev/peps/pep-0272/ +.. _PyCrypto: https://www.dlitz.net/software/pycrypto/ +.. _hashlib: https://docs.python.org/3.6/library/hashlib.html +.. _hmac: https://docs.python.org/3.6/library/hmac.html License ======= @@ -42,23 +50,19 @@ The bindings are tested with Python 2.7, 3.4, 3.5, and 3.6. # aptitude install libmbedtls-dev -and the wrapper:: +and the `pyton-mbedtls`:: - python -m pip install python-mbedtls + $ python -m pip install python-mbedtls - -Hashing module (`md.h`) ------------------------ - -Message digest algorithms -~~~~~~~~~~~~~~~~~~~~~~~~~ +Message digest with `mbedtls.hash` +---------------------------------- The `mbedtls.hash` module provides MD5, SHA-1, SHA-2, and RIPEMD-160 secure hashes and message digests. The API follows the recommendations from PEP 452 so that it can be used as a drop-in replacement to e.g. `hashlib` or `PyCrypto`. -Here are the examples from `hashlib` executed with `python-mbedtls`:: +Here are the examples from `hashlib` ported to `python-mbedtls`:: >>> from mbedtls import hash as hashlib >>> m = hashlib.md5() @@ -84,8 +88,8 @@ Using `new()`:: 'cc4a5ce1b3df48aec5d22d1f16b894a0b894eccc' -HMAC algorithm -~~~~~~~~~~~~~~ +HMAC algorithm with `mbedtls.hmac` +---------------------------------- The `mbedtls.hmac` module computes HMAC. The API follows the recommendations from PEP 452 as well. @@ -106,8 +110,8 @@ Warning: per message. -Symmetric cipher module (`cipher.h`) ------------------------------------- +Symmetric cipher with `mbedtls.cipher` +-------------------------------------- The `mbedtls.cipher` module provides symmetric encryption. The API follows the recommendations from PEP 272 so that it can be used as a drop-in replacement to @@ -138,8 +142,8 @@ Example:: b'This is a super-secret message!' -Public key module (`pk.h`) --------------------------- +RSA Public key with `mbedtls.pk` +-------------------------------- The `mbedtls.pk` module provides the RSA cryptosystem. This includes: @@ -173,10 +177,33 @@ Message signature and verification:: True >>> rsa.verify(b"Sorry, wrong message.", sig) False - >>> prv, pub = rsa.export(format="DER") + >>> prv, pub = rsa.to_DER() >>> other = pk.RSA() - >>> other.import_(pub) + >>> other.from_DER(pub) >>> other.has_private() False >>> other.verify(b"Please sign here.", sig) True + + +X.509 Certificate writing and parsing with `mbedtls.x509` +--------------------------------------------------------- + +Create new X.509 certificates:: + + >>> import datetime as dt + >>> from pathlib import Path + >>> from mbedtls.x509 import Certificate, CSR, CRL + >>> now = dt.datetime.utcnow() + >>> crt = Certificate( + ... start=now, end=now + dt.timedelta(days=90), + ... issuer="C=NL,O=PolarSSL,CN=PolarSSL Test CA", issuer_key=issuer_key, + ... subject=None, subject_key=subject_key, + ... md_alg=hash.sha1(), serial=None) + ... + >>> csr = CSR.new(subject_key, hash.sha1(), + "C=NL,O=PolarSSL,CN=PolarSSL Server 1") + +and load existing certificates from file:: + + >>> crl = CRL.from_file("ca/wp_crl.pem") diff --git a/setup.py b/setup.py index 06ccfdd5..9c696df9 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,8 @@ import os +import sys from setuptools import setup, Extension -version = "0.7" +version = "0.8" download_url = "https://github.com/Synss/python-mbedtls/tarball/%s" % version @@ -19,6 +20,15 @@ )) +setup_requires = [ + # Setuptools 18.0 properly handles Cython extensions. + "setuptools>=18.0", + "cython", +] +if sys.version_info < (2, ): + setup_requires.append("pathlib2") + + def readme(): with open("README.rst") as f: return f.read() @@ -27,24 +37,22 @@ def readme(): setup( name="python-mbedtls", version=version, - description="mbed TLS (PolarSSL) wrapper", + description="hash, hmac, RSA, and X.509 with an mbed TLS back end", long_description=readme(), author="Mathias Laurin", - author_email="Mathias.Laurin@users.sf.net", + author_email="Mathias.Laurin@github.com", license="MIT License", url="https://github.com/Synss/python-mbedtls", download_url=download_url, ext_modules=extensions, - setup_requires=[ - # Setuptools 18.0 properly handles Cython extensions. - "setuptools>=18.0", - "cython", - ], + setup_requires=setup_requires, classifiers=[ "Development Status :: 4 - Beta", "Programming Language :: Cython", - "Programming Language :: Python :: 3", "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", "License :: OSI Approved :: MIT License", "Topic :: Security :: Cryptography", ]