diff options
-rw-r--r-- | .coveragerc | 18 | ||||
-rw-r--r-- | src/lib/ffi/ffi.cpp | 21 | ||||
-rwxr-xr-x | src/python/botan.py | 85 | ||||
-rwxr-xr-x | src/scripts/ci/travis/after_success.sh | 2 | ||||
-rwxr-xr-x | src/scripts/ci/travis/install.sh | 2 |
5 files changed, 111 insertions, 17 deletions
diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..d93af43e2 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,18 @@ +# .coveragerc to control coverage.py +[run] +branch = True + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain if non-runnable code isn't run: + if 0: + def main + if __name__ == .__main__.: + + # Exclude tests + def test + diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp index c5decdcf2..621195ea3 100644 --- a/src/lib/ffi/ffi.cpp +++ b/src/lib/ffi/ffi.cpp @@ -1310,8 +1310,27 @@ int botan_x509_cert_path_verify(botan_x509_cert_t cert, const char* dir) int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key) { + try + { + if(key == nullptr) + return -1; + + *key = nullptr; + +#if defined(BOTAN_HAS_RSA) + std::unique_ptr<Botan::Public_Key> publicKey(safe_get(cert).subject_public_key()); + *key = new botan_pubkey_struct(publicKey.release()); + return 0; +#else return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; - //return BOTAN_FFI_DO(Botan::X509_Certificate, cert, { return write_vec_output(out, out_len, cert.subject_public_key_bits()); }); +#endif + } + catch(std::exception& e) + { + log_exception(BOTAN_CURRENT_FUNCTION, e.what()); + } + + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } int botan_x509_cert_get_issuer_dn(botan_x509_cert_t cert, diff --git a/src/python/botan.py b/src/python/botan.py index da59c2a05..1cb141ef5 100755 --- a/src/python/botan.py +++ b/src/python/botan.py @@ -20,6 +20,8 @@ module should be used only with the library version it accompanied. import sys from ctypes import * from binascii import hexlify, unhexlify, b2a_base64 +from datetime import datetime +import time """ Module initialization @@ -595,25 +597,54 @@ class pk_op_key_agreement(object): lambda b,bl: botan.botan_pk_op_key_agreement(self.op, b, bl, other, len(other), salt, len(salt))) - +""" +X.509 certificates +""" class x509_cert(object): - def __init__(self, filename): - botan.botan_x509_cert_load_file.argtypes = [POINTER(c_void_p), c_char_p] - self.x509_cert = c_void_p(0) - botan.botan_x509_cert_load_file(byref(self.x509_cert), _ctype_str(filename)) + def __init__(self, filename=None, buf=None): + if filename is None and buf is None: + raise ArgumentError("No filename or buf given") + if filename is not None and buf is not None: + raise ArgumentError("Both filename and buf given") + elif filename is not None: + botan.botan_x509_cert_load_file.argtypes = [POINTER(c_void_p), c_char_p] + self.x509_cert = c_void_p(0) + botan.botan_x509_cert_load_file(byref(self.x509_cert), _ctype_str(filename)) + elif buf is not None: + botan.botan_x509_cert_load.argtypes = [POINTER(c_void_p), POINTER(c_char), c_size_t] + self.x509_cert = c_void_p(0) + botan.botan_x509_cert_load(byref(self.x509_cert), _ctype_bits(buf), len(buf)) def __del__(self): botan.botan_x509_cert_destroy.argtypes = [c_void_p] botan.botan_x509_cert_destroy(self.x509_cert) - # TODO: have these convert to a python datetime def time_starts(self): botan.botan_x509_cert_get_time_starts.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] - return _call_fn_returning_string(16, lambda b,bl: botan.botan_x509_cert_get_time_starts(self.x509_cert, b, bl)) + starts = _call_fn_returning_string(16, lambda b,bl: botan.botan_x509_cert_get_time_starts(self.x509_cert, b, bl)) + if len(starts) == 13: + # UTC time + struct_time = time.strptime(starts, "%y%m%d%H%M%SZ") + elif len(starts) == 15: + # Generalized time + struct_time = time.strptime(starts, "%Y%m%d%H%M%SZ") + else: + raise Exception("Wrong date/time format") + + return datetime.fromtimestamp(time.mktime(struct_time)) def time_expires(self): botan.botan_x509_cert_get_time_expires.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] - return _call_fn_returning_string(16, lambda b,bl: botan.botan_x509_cert_get_time_expires(self.x509_cert, b, bl)) + expires = _call_fn_returning_string(16, lambda b,bl: botan.botan_x509_cert_get_time_expires(self.x509_cert, b, bl)) + if len(expires) == 13: + # UTC time + struct_time = time.strptime(expires, "%y%m%d%H%M%SZ") + elif len(expires) == 15: + # Generalized time + struct_time = time.strptime(expires, "%Y%m%d%H%M%SZ") + else: + raise Exception("Wrong date/time format") + return datetime.fromtimestamp(time.mktime(struct_time)) def to_string(self): botan.botan_x509_cert_to_string.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] @@ -642,6 +673,17 @@ class x509_cert(object): botan.botan_x509_cert_get_public_key_bits.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_vec(0, lambda b,bl: botan.botan_x509_cert_get_public_key_bits(self.x509_cert, b, bl)) + def subject_public_key(self): + botan.botan_x509_cert_get_public_key.argtypes = [c_void_p, c_void_p] + pub = c_void_p(0) + botan.botan_x509_cert_get_public_key(self.x509_cert, byref(pub)) + + return public_key(pub) + + def subject_dn(self, key, index): + botan.botan_x509_cert_get_subject_dn.argtypes = [c_void_p, c_char_p, c_size_t, POINTER(c_char), POINTER(c_size_t)] + return _call_fn_returning_string(0, lambda b,bl: botan.botan_x509_cert_get_subject_dn(self.x509_cert, _ctype_str(key), index, b, bl)) + """ Tests and examples @@ -836,19 +878,30 @@ def test(): (dh_grp, hex_encode(a_key), hex_encode(b_key))) def test_certs(): - cert = x509_cert("src/tests/data/ecc/CSCA.CSCA.csca-germany.1.crt") + cert = x509_cert(filename="src/tests/data/ecc/CSCA.CSCA.csca-germany.1.crt") print("CSCA (Germany) Certificate\nDetails:") - print("SHA-1 fingerprint: %s" % cert.fingerprint("SHA-1")) - print("Expected: 32:42:1C:C3:EC:54:D7:E9:43:EC:51:F0:19:23:BD:85:1D:F2:1B:B9") + print("SHA-1 fingerprint: %s" % cert.fingerprint("SHA-1")) + print("Expected: 32:42:1C:C3:EC:54:D7:E9:43:EC:51:F0:19:23:BD:85:1D:F2:1B:B9") - print("Not before: %s" % cert.time_starts()) - print("Not after: %s" % cert.time_expires()) + print("Not before: %s" % cert.time_starts()) + print("Not after: %s" % cert.time_expires()) - print("Serial number: %s" % hex_encode(cert.serial_number())) - print("Authority Key ID: %s" % hex_encode(cert.authority_key_id())) - print("Subject Key ID: %s" % hex_encode(cert.subject_key_id())) + print("Serial number: %s" % hex_encode(cert.serial_number())) + print("Authority Key ID: %s" % hex_encode(cert.authority_key_id())) + print("Subject Key ID: %s" % hex_encode(cert.subject_key_id())) print("Public key bits:\n%s\n" % b2a_base64(cert.subject_public_key_bits())) + pubkey = cert.subject_public_key() + print("Public key algo: %s" % pubkey.algo_name()) + print("Public key strength: %s" % pubkey.estimated_strength() + " bits" ) + + dn_fields = ("Name", "Email", "Organization", "Organizational Unit", "Country") + for field in dn_fields: + try: + print("%s: %s" % (field, cert.subject_dn(field, 0))) + except Exception: + print("Field: %s not found in certificate" % field) + print(cert.to_string()) test_version() diff --git a/src/scripts/ci/travis/after_success.sh b/src/scripts/ci/travis/after_success.sh index 178586b6d..a9acab3c9 100755 --- a/src/scripts/ci/travis/after_success.sh +++ b/src/scripts/ci/travis/after_success.sh @@ -8,5 +8,7 @@ if [ "$BUILD_MODE" = "coverage" ]; then /tmp/usr/bin/lcov --gcov-tool "$GCOV" --remove coverage.info 'tests/*' '/usr/*' --output-file coverage.info /tmp/usr/bin/lcov --gcov-tool "$GCOV" --list coverage.info + LD_LIBRARY_PATH=. coverage run --branch src/python/botan.py + codecov fi diff --git a/src/scripts/ci/travis/install.sh b/src/scripts/ci/travis/install.sh index 67dd82cfe..0e49ad363 100755 --- a/src/scripts/ci/travis/install.sh +++ b/src/scripts/ci/travis/install.sh @@ -8,6 +8,8 @@ if [ "$BUILD_MODE" = "coverage" ]; then export PREFIX="/tmp" make -C lcov-1.11/ install + pip install --user coverage + pip install --user codecov fi |