diff --git a/.gitignore b/.gitignore index 61d01f96326..5e70f6452a1 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ vgcore.* /*.sublime-project /*.sublime-workspace /.editorconfig +/.vscode # Archive files *.tgz diff --git a/configure.py b/configure.py index 10bc5822669..799e36dbca7 100755 --- a/configure.py +++ b/configure.py @@ -41,8 +41,8 @@ class InternalError(Exception): pass -def flatten(l): - return sum(l, []) +def flatten(lst): + return sum(lst, []) def normalize_source_path(source): """ @@ -1586,7 +1586,7 @@ def cc_compile_flags(self, options): def _so_link_search(osname, debug_info): so_link_typ = [osname, 'default'] if debug_info: - so_link_typ = [l + '-debug' for l in so_link_typ] + so_link_typ + so_link_typ = [link + '-debug' for link in so_link_typ] + so_link_typ return so_link_typ def so_link_command_for(self, osname, options): @@ -3408,7 +3408,7 @@ def run_compiler_preproc(options, ccinfo, source_file, default_return, extra_fla cc_output = run_compiler(options, ccinfo, default_return, ccinfo.preproc_flags.split(' ') + extra_flags + [source_file]) def cleanup_output(output): - return ('\n'.join([l for l in output.splitlines() if l.startswith('#') is False])).strip() + return ('\n'.join([line for line in output.splitlines() if not line.startswith('#')])).strip() return cleanup_output(cc_output) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index cf512d8f923..4d612c88ae2 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -103,6 +103,10 @@ The following enum values are defined in the FFI header: While decrypting in an AEAD mode, the tag failed to verify. +.. cpp:enumerator:: BOTAN_FFI_ERROR_NO_VALUE = -3 + + The requested value was not available or does not exist. + .. cpp:enumerator:: BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE = -10 Functions which write a variable amount of space return this if the indicated @@ -1627,12 +1631,6 @@ X.509 Certificates Destroy the certificate object -.. cpp:function:: int botan_x509_cert_gen_selfsigned(botan_x509_cert_t* cert, \ - botan_privkey_t key, \ - botan_rng_t rng, \ - const char* common_name, \ - const char* org_name) - .. cpp:function:: int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len) Return the time the certificate becomes valid, as a string in form @@ -1667,6 +1665,23 @@ X.509 Certificates Return the subject key ID set in the certificate, which may be empty. +.. cpp:function::int botan_x509_cert_is_ca(botan_x509_cert_t cert, int* is_ca, size_t* limit) + + Checks whether the certificate is a CA certificate and sets ``is_ca`` to 1 if it is, 0 otherwise. + If it is a CA certificate, ``limit`` is set to the path limit, otherwise 0. + +.. cpp:function::int botan_x509_cert_get_allowed_usage(botan_x509_cert_t cert, uint32_t* usage) + + Sets ``usage`` the key usage constraints. + +.. cpp:function::int botan_x509_cert_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) + + Get the OCSP responder. + +.. cpp:function::int botan_x509_cert_is_self_signed(botan_x509_cert_t cert, int* out) + + Checks whether the certificate is self signed and sets ``out`` to 1 if it is, 0 otherwise. + .. cpp:function:: int botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert, \ uint8_t out[], size_t* out_len) @@ -1681,18 +1696,34 @@ X.509 Certificates Get the public key included in this certificate as a newly allocated object +.. cpp:function:: int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, const char* key, size_t* len) + + Get the number of elements for a key in the isssuer DN field. + .. cpp:function:: int botan_x509_cert_get_issuer_dn(botan_x509_cert_t cert, \ const char* key, size_t index, \ uint8_t out[], size_t* out_len) Get a value from the issuer DN field. +.. cpp:function:: int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, const char* key, size_t* len) + + Get the number of elements for a key in the subject DN field. + .. cpp:function:: int botan_x509_cert_get_subject_dn(botan_x509_cert_t cert, \ const char* key, size_t index, \ uint8_t out[], size_t* out_len) Get a value from the subject DN field. +.. cpp:function:: int botan_x509_cert_get_subject_dn(botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) + + Get the subject name. + +.. cpp:function:: int botan_x509_cert_get_issuer_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) + + Get the issuer name. + .. cpp:function:: int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len) Format the certificate as a free-form string. @@ -1702,6 +1733,10 @@ X.509 Certificates View the certificate as a free-form string. +.. cpp:function::int botan_x509_cert_view_pem(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) + + View the certificate as a PEM string. + .. cpp:enum:: botan_x509_cert_key_constraints Certificate key usage constraints. Allowed values: `NO_CONSTRAINTS`, @@ -1767,6 +1802,239 @@ X.509 Certificates Return a (statically allocated) string associated with the verification result, or NULL if the code is not known. + +.. cpp:type:: opaque* botan_x509_ext_ip_addr_blocks_t + + An opaque data type for an X.509 IP Address Blocks extension (RFC 3779). Don't mess with it. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) + + Destroy the IP Address Blocks object. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_create(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) + + Create a new IP Address Blocks object. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_create_from_cert(botan_x509_cert_t cert, \ + botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) + + Get an IP Address Blocks object from a certificate. Cannot be mutated. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_add_ip_addr(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, \ + const uint8_t* min, \ + const uint8_t* max, \ + int ipv6, \ + uint8_t* safi) + + Add a new IP Address to the extension. Set ``ipv6`` to 0 if the address is v4, 1 if it is v6. + ``safi`` may be NULL. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) + + Make the extension contain no allowed IP addresses for the specified IP version. + Set ``ipv6`` to 0 for v4, 1 for v6. ``safi`` may be NULL. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) + + Mark the specified IP version as "inherit". Set ``ipv6`` to 0 for v4, 1 for v6. ``safi`` may be NULL. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, \ + size_t* v4_count, \ + size_t* v6_count) + + Retrieve the counts of v4/v6 entries in the extension. + v4 entries always precede v6 entries. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, \ + size_t i, \ + int* has_safi, \ + uint8_t* safi, \ + int* present, \ + size_t* count) + + Retrieve information about an entry in the extension. + Set ``ipv6`` to 0 to indicate the index is v4, 1 to indicate it is v6. + Set ``i`` to the index you want to access. + You can get these values from :cpp:func:`botan_x509_ext_ip_addr_blocks_get_counts`. + ``has_afi`` will be set to 1 if the entry has a SAFI, 0 otherwise. + If a SAFI is present, ``safi`` will be set to its value, otherwise it will not be written to. + ``present`` will be set to one to indicate a value, 0 otherwise (inherit). + ``count`` will be set to the number of address pairs present for the entry if present. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, \ + size_t i, \ + size_t entry, \ + uint8_t min_out[], \ + uint8_t max_out[], \ + size_t* out_len) + + Retrieve a single address pair for an entry in the extension. + Retrieve information about an entry in the extension. + Set ``ipv6`` to 0 to indicate the index is v4, 1 to indicate it is v6. + Set ``i`` to the index you want to access. + Set ``entry`` to the index of the address pair you want to access. + ``ipv6`` and ``i`` are retrieved from :cpp:func:`botan_x509_ext_ip_addr_blocks_get_counts`, ``entry`` is the inferred from the ``count`` parameter of :cpp:func:`botan_x509_ext_ip_addr_blocks_get_family`. + ``min_out`` and ``max_out`` will be set to the minimum and maximum of the IP range. + You must provide 4 / 16 bytes of buffer space for each for IP v4 / v6 respectively. + +.. cpp:type:: opaque* botan_x509_ext_as_blocks_t + + An opaque data type for an X.509 AS Blocks extension (RFC 3779). Don't mess with it. + +.. cpp:function::int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) + + Destroy the AS Blocks object. + +.. cpp:function::int botan_x509_ext_as_blocks_create(botan_x509_ext_as_blocks_t* as_blocks) + + Create a new AS Blocks object. + +.. cpp:function::int botan_x509_ext_as_blocks_create_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks) + + Get an AS Blocks object from a certificate. Cannot be mutated. + +.. cpp:function::int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) + + Add an asnum to the extension. + +.. cpp:function::int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks) + + Make the extension contain no allowed asnum's + +.. cpp:function::int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks) + + Mark the asnum entry as "inherit". + +.. cpp:function::int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) + +.. cpp:function::int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks) + +.. cpp:function::int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks) + +.. cpp:function::int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) + + If the extension has an asnum entry, ``present`` will be set to 1, otherwise 0 (inherit). + If an entry is present ``count`` will be set to the number of elements. + +.. cpp:function::int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) + + Retrieve information on a single asnum entry. + +.. cpp:function::int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) + +.. cpp:function::int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) + +.. cpp:type:: opaque* botan_x509_cert_params_builder_t + +.. cpp:function::int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder) + + Destroy the Certificate Params Builder object. + +.. cpp:function::int botan_x509_cert_params_builder_create(botan_x509_cert_params_builder_t* builder_obj); + + Create a new Certificate Params Builder object. + +.. cpp:function::int botan_x509_cert_params_builder_add_common_name(botan_x509_cert_params_builder_t builder, const char* name); + +.. cpp:function::int botan_x509_cert_params_builder_add_country(botan_x509_cert_params_builder_t builder, const char* country); + +.. cpp:function::int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); + +.. cpp:function::int botan_x509_cert_params_builder_add_locality(botan_x509_cert_params_builder_t builder, const char* locality); + +.. cpp:function::int botan_x509_cert_params_builder_add_serial_number(botan_x509_cert_params_builder_t builder, const char* serial_number); + +.. cpp:function::int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); + +.. cpp:function::int botan_x509_cert_params_builder_add_organizational_unit(botan_x509_cert_params_builder_t builder, const char* org_unit); + +.. cpp:function::int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); + +.. cpp:function::int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns); + +.. cpp:function::int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); + +.. cpp:function::int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); + +.. cpp:function::int botan_x509_cert_params_builder_add_ip(botan_x509_cert_params_builder_t builder, uint32_t ipv4); + +.. cpp:function::int botan_x509_cert_params_builder_add_allowed_usage(botan_x509_cert_params_builder_t builder, uint32_t usage); + + See :cpp:enum:`botan_x509_cert_key_constraints` for allowed values. + +.. cpp:function::int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); + +.. cpp:function::int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit=None); + + Mark the certificate for CA usage. + +.. cpp:function::int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, \ + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int is_critical); + +.. cpp:function::int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, \ + botan_x509_ext_as_blocks_t as_blocks, int is_critical); + +.. cpp:function::int botan_x509_cert_params_builder_into_self_signed(botan_x509_cert_t* cert_obj, \ + botan_privkey_t key, \ + botan_x509_cert_params_builder_t builder, \ + botan_rng_t rng, \ + uint64_t not_before, \ + uint64_t not_after, \ + const botan_mp_t* serial_number, \ + const char* hash_fn, \ + const char* padding) + + Create a new self-signed X.509 certificate. ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. + +.. cpp:type:: opaque* botan_x509_pkcs10_req_t + + An opaque data type for a PKCS #10 certificate request. Don't mess with it. + +.. cpp:function::int botan_x509_cert_params_builder_into_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, \ + botan_privkey_t key, \ + botan_x509_cert_params_builder_t builder, \ + botan_rng_t rng, \ + const char* hash_fn, \ + const char* padding, \ + const char* challenge_password) + + Create a PCKS #10 certificate request. ``challenge_password``, ``hash_fn`` and ``padding`` may be NULL. + +.. cpp:function::int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) + + Destroy the PKCS #10 certificate request object. + +.. cpp:function::int botan_x509_pkcs10_req_load_file(botan_x509_pkcs10_req_t* req_obj, const char* req_path) + +.. cpp:function::int botan_x509_pkcs10_req_load(botan_x509_pkcs10_req_t* req_obj, const uint8_t req_bits[], size_t req_bits_len) + +.. cpp:function::int int botan_x509_pkcs10_req_get_public_key(botan_x509_pkcs10_req_t req, botan_pubkey_t* key) + +.. cpp:function::int int botan_x509_pkcs10_req_get_allowed_usage(botan_x509_pkcs10_req_t req, uint32_t* usage) + +.. cpp:function::int int botan_x509_pkcs10_req_is_ca(botan_x509_pkcs10_req_t req, int* is_ca, size_t* limit) + +.. cpp:function::int int botan_x509_pkcs10_req_verify_signature(botan_x509_pkcs10_req_t req, botan_pubkey_t key, int* result) + +.. cpp:function::int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view) + +.. cpp:function::int int botan_x509_pkcs10_req_view_der(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_bin_fn view) + +.. cpp:function::int botan_x509_pkcs10_req_sign(botan_x509_cert_t* subject_cert, \ + botan_x509_pkcs10_req_t subject_req, \ + botan_x509_cert_t issuing_cert, \ + botan_privkey_t issuing_key, \ + botan_rng_t rng, \ + uint64_t not_before, \ + uint64_t not_after, \ + const botan_mp_t* serial_number, \ + const char* hash_fn, \ + const char* padding) + + Sign a PKCS #10 certificate request. ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. + X.509 Certificate Revocation Lists ---------------------------------------- @@ -1783,6 +2051,45 @@ X.509 Certificate Revocation Lists Load a CRL from a file. +.. cpp:function:: int botan_x509_crl_create(botan_x509_crl_t* crl_obj, \ + botan_rng_t rng, \ + botan_x509_cert_t ca_cert, \ + botan_privkey_t ca_key, \ + uint64_t issue_time, \ + uint32_t next_update, \ + const char* hash_fn, \ + const char* padding) + +.. cpp:enum:: botan_x509_crl_reason_code + + CRL Reason codes. Allowed values: `UNSPECIFIED`, + `KEY_COMPROMISE`, `CA_COMPROMISE`, `AFFILIATION_CHANGED`, + `SUPERSEDED`, `CESSATION_OF_OPERATION`, `CERTIFICATE_HOLD`, + `REMOVE_FROM_CRL`, `PRIVILIGE_WITHDRAWN`, `AA_COMPROMISE`. + +.. cpp:function:: int botan_x509_crl_update(botan_x509_crl_t* crl_obj, \ + botan_x509_crl_t last_crl, \ + botan_rng_t rng, \ + botan_x509_cert_t ca_cert, \ + botan_privkey_t ca_key, \ + uint64_t issue_time, \ + uint32_t next_update, \ + const botan_x509_cert_t* revoked, \ + size_t revoked_len, \ + uint8_t reason, \ + const char* hash_fn, \ + const char* padding) + +.. cpp:function:: int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count); + +.. cpp:function:: int botan_x509_crl_get_entry(botan_x509_crl_t crl, size_t i, botan_mp_t serial, uint64_t* expire_time, uint8_t* reason) + +.. cpp:function:: int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key, int* result) + +.. cpp:function:: int botan_x509_crl_view_pem(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_str_fn view) + +.. cpp:function:: int botan_x509_crl_view_der(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_bin_fn view) + .. cpp:function:: int botan_x509_crl_destroy(botan_x509_crl_t crl) Destroy the CRL object. diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst index f2e633df7dc..94b74506d44 100644 --- a/doc/api_ref/python.rst +++ b/doc/api_ref/python.rst @@ -561,6 +561,14 @@ Multiple Precision Integers (MPI) Most of the usual arithmetic operators (``__add__``, ``__mul__``, etc) are defined. + .. py:classmethod:: from_bytes(buf) + + Create a new MPI object from the big-endian binary encoding produced by ``to_bytes()``. + + .. py:method:: to_bytes() + + Return a big-endian binary encoding of the number. + .. py:method:: inverse_mod(modulus) Return the inverse of ``self`` modulo ``modulus``, or zero if no inverse exists @@ -715,6 +723,147 @@ HOTP in. If the code did verify and resync_range was zero, then the next counter will always be counter+1. +X509CertificateBuilder +----------------------------------------- +.. versionadded:: 3.9.0 + +.. py:class:: X509CertificateBuilder(opts, expire_time=None) + + .. py:method:: add_common_name(name) + + .. py:method:: add_country(country) + + .. py:method:: add_state(state) + + .. py:method:: add_locality(locality) + + .. py:method:: add_serial_number(serial_number) + + .. py:method:: add_organization(organization) + + .. py:method:: add_organizational_unit(org_unit) + + .. py:method:: add_email(email) + + .. py:method:: add_dns(dns) + + .. py:method:: add_uri(uri) + + .. py:method:: add_xmpp(xmpp) + + .. py:method:: add_ipv4(ipv4) + + .. py:method:: add_allowed_usage(usage_list) + + .. py:method:: add_allowed_extended_usage(oid) + + .. py:method:: set_as_ca_certificate(limit) + + .. py:method:: add_ext_ip_addr_blocks(ip_addr_blocks, is_critical) + + .. py:method:: add_ext_as_blocks(as_blocks, is_critical) + + .. py:method:: create_self_signed(key, rng, not_before, not_after, serial_number=None, hash_fn=None, padding=None) + + Create a self-signed certificate from the given certificate options. + ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. + + .. py:method:: create_req(key, rng, hash_fn=None, padding=None, challenge_password=None) + + Create a PKCS #10 certificate request that can later be signed. + +X509ExtIPAddrBlocks +----------------------------------------- + +.. versionadded:: 3.9.0 + +.. py:class:: X509ExtIPAddrBlocks(cert=None) + + .. py:method:: add_addr(ip, safi=None) + + Add a single IP address to the extension. ``ip`` is expected to be a ``list[int]`` + of length 4/16 for IPv4/IPv6. + + .. py:method:: add_range(min_, max_, safi=None) + + Add an IP address range to the extension. + + .. py:method:: restrict(ipv6, safi=None) + + Make the extension contain no allowed IP addresses for the given SAFI (if any). + Set ``ipv6`` to True to indicate IPv6, False for IPv4. + + .. py:method:: inherit(ipv6, safi=None) + + Mark the specified IP version and SAFI (if any) as "inherit". + + .. py:method:: addresses() + + Get the IP addresses registered in the extension. + +X509ExtASBlocks +----------------------------------------- + +.. versionadded:: 3.9.0 + +.. py:class:: X509ExtASBlocks(cert=None) + + .. py:method:: add_asnum(asnum): + + Add a single asnum to the extension. + + .. py:method:: add_asnum_range(min_, max_) + + Add an asnum range to the extension. + + .. py:method:: restrict_asnum() + + Make the extension contain no allowed asnum's. + + .. py:method:: inherit_asnum() + + Mark the asnum entry as "inherit". + + .. py:method:: add_rdi(rdi): + + .. py:method:: add_rdi_range(min_, max_) + + .. py:method:: restrict_rdi() + + .. py:method:: inherit_rdi() + + .. py:method:: asnum() + + Get the asnum(s) registered in the extension. + + .. py:method:: rdi() + +PKCS10Req +----------------------------------------- +.. versionadded:: 3.9.0 + +.. py:class:: PKCS10Req() + + .. py:method:: public_key() + + Get the public key associated with the signing request. + + .. py:method:: allowed_usage() + + Return a list of all the key constraints listed in the signing request. + + .. py:method:: verify(key) + + Verify the signature of the signing request. + + .. py:method:: sign(issuing_cert, issuing_key, rng, not_before, not_after, hash_fn=None, padding=None) + + ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. + + .. py:method:: to_pem() + + .. py:method:: to_der() + X509Cert ----------------------------------------- @@ -736,6 +885,10 @@ X509Cert Format the certificate as a free-form string. + .. py:method:: to_pem() + + Format the certificate as a PEM string. + .. py:method:: fingerprint(hash_algo='SHA-256') Return a fingerprint for the certificate, which is basically just a hash @@ -790,7 +943,31 @@ X509Cert Return True if the certificates Key Usage extension contains all constraints given in ``usage_list``. Also return True if the certificate doesn't have this extension. - Example usage constraints are: ``"DIGITAL_SIGNATURE"``, ``"KEY_CERT_SIGN"``, ``"CRL_SIGN"``. + Example usage constraints are: ``X509KeyConstraints.DIGITAL_SIGNATURE"``, ``X509KeyConstraints.KEY_CERT_SIGN``, ``X509KeyConstraints.CRL_SIGN``. + + .. py:method:: allowed_usages() + + Return a list of all the key constraints listed in the certificate. + + .. py:method:: is_ca() + + Return (True, limit) if the certificate is marked for CA usage, else (False, 0) + + .. py:method:: ocsp_responder() + + Return the OCSP responder. + + .. py:method:: is_self_signed() + + Return True if the certificate was self-signed. + + .. py:method:: ext_ip_addr_blocks() + + Return the certificate's IP Address Blocks extension. + + .. py:method:: ext_as_blocks() + + Return the certificate's AS Blocks extension. .. py:method:: verify(intermediates=None, \ trusted=None, \ @@ -840,6 +1017,31 @@ X509CRL A CRL in PEM or DER format can be loaded from a file, with the ``filename`` argument, or from a bytestring, with the ``buf`` argument. + .. py:classmethod:: create(rng, ca_cert, ca_key, issue_time, next_update, hash_fn=None, padding=None) + + Create a new CRL for the given CA. + ``issue_time`` is expected to be the time since the UNIX epoch, in seconds, ``next_update`` the time in seconds until the next update. + + + .. py:method:: revoke(rng, ca_cert, ca_key, issue_time, next_update, revoked, reason, hash_fn=None, padding=None) + + Revoke certificates issued by the CA. + ``issue_time`` is expected to be the time since the UNIX epoch, in seconds, ``next_update`` the time in seconds until the next update. + Revoked is expected to be a list of certificates you want to revoked, reason should be of instance ``X509CRLReason``. + This method returns a new CRL, it does not modify the existing one! + + .. py:method:: revoked() + + Return entries listed in the CRL. + + .. py:method:: verify(key) + + Verify the signature of the CRL. + + .. py:method:: to_pem() + + .. py:method:: to_der() + diff --git a/src/cli/perf_x509.cpp b/src/cli/perf_x509.cpp index 92429892e12..cd7c975f035 100644 --- a/src/cli/perf_x509.cpp +++ b/src/cli/perf_x509.cpp @@ -16,9 +16,9 @@ #include #include #include + #include #include #include - #include #endif namespace Botan_CLI { @@ -40,14 +40,23 @@ class PerfTest_ASN1_Parsing final : public PerfTest { } static CA create_ca(Botan::RandomNumberGenerator& rng) { - auto root_cert_options = Botan::X509_Cert_Options("Benchmark Root/DE/RS/CS"); - root_cert_options.dns = "unobtainium.example.com"; - root_cert_options.email = "idont@exist.com"; - root_cert_options.is_CA = true; - auto root_key = create_private_key(rng); BOTAN_ASSERT_NONNULL(root_key); - auto root_cert = Botan::X509::create_self_signed_cert(root_cert_options, *root_key, get_hash_function(), rng); + + Botan::CertificateParametersBuilder root_cert_params; + root_cert_params.add_common_name("Benchmark Root") + .add_country("DE") + .add_organization("RS") + .add_organizational_unit("CS") + .add_dns("unobtainium.example.com") + .add_email("idont@exist.com") + .set_as_ca_certificate(); + + const auto not_before = std::chrono::system_clock::now(); + const auto not_after = not_before + std::chrono::seconds(86400); + + auto root_cert = + root_cert_params.into_self_signed_cert(not_before, not_after, *root_key, rng, get_hash_function()); auto ca = Botan::X509_CA(root_cert, *root_key, get_hash_function(), rng); return CA{ diff --git a/src/configs/sphinx/conf.py b/src/configs/sphinx/conf.py index c27c89ab993..30455619f1d 100644 --- a/src/configs/sphinx/conf.py +++ b/src/configs/sphinx/conf.py @@ -101,7 +101,7 @@ def parse_version_file(version_path): try: # On Arch this is python-sphinx-furo - import furo + import furo # noqa: F401 html_theme = "furo" # Add a small edit button to each document to allow visitors to easily @@ -111,7 +111,7 @@ def parse_version_file(version_path): 'source_branch': 'master', 'source_directory': 'doc/', } -except ImportError as e: +except ImportError: print("Could not import furo theme; falling back to agago") html_theme = 'agogo' html_theme_path = [] diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index ff3792961bb..253e1040e40 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2140,6 +2140,12 @@ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_serial_number(botan_x509_cert_t c BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_subject_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_is_ca(botan_x509_cert_t cert, int* is_ca, size_t* limit); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_get_allowed_usage(botan_x509_cert_t cert, uint32_t* usage); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_is_self_signed(botan_x509_cert_t cert, int* out); + BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); BOTAN_FFI_EXPORT(3, 0) @@ -2147,21 +2153,34 @@ int botan_x509_cert_view_public_key_bits(botan_x509_cert_t cert, botan_view_ctx BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, const char* key, size_t* len); + /* TODO(Botan4) this should use char for the out param */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_issuer_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, const char* key, size_t* len); + /* TODO(Botan4) this should use char for the out param */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_subject_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_get_subject_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_get_issuer_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); + BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len); BOTAN_FFI_EXPORT(3, 0) int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_view_pem(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); + /* Must match values of Key_Constraints in key_constraints.h */ enum botan_x509_cert_key_constraints /* NOLINT(*-enum-size) */ { NO_CONSTRAINTS = 0, @@ -2210,6 +2229,214 @@ int botan_x509_cert_verify(int* validation_result, */ BOTAN_FFI_EXPORT(2, 8) const char* botan_x509_cert_validation_status(int code); +/* +* X.509 extensions +*/ +typedef struct botan_x509_ext_ip_addr_blocks_struct* botan_x509_ext_ip_addr_blocks_t; + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_create(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_ip_addr_blocks_create_from_cert(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks, + botan_x509_cert_t cert); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_ip_addr_blocks_add_ip_addr( + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, const uint8_t* min, const uint8_t* max, int ipv6, uint8_t* safi); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + size_t* v4_count, + size_t* v6_count); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, + size_t i, + int* has_safi, + uint8_t* safi, + int* present, + size_t* count); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, + size_t i, + size_t entry, + uint8_t min_out[], + uint8_t max_out[], + size_t* out_len); + +typedef struct botan_x509_ext_as_blocks_struct* botan_x509_ext_as_blocks_t; + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_create(botan_x509_ext_as_blocks_t* as_blocks); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_as_blocks_create_from_cert(botan_x509_ext_as_blocks_t* as_blocks, botan_x509_cert_t cert); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max); + +/* +* X.509 cert param builder +*/ +typedef struct botan_x509_cert_params_builder_struct* botan_x509_cert_params_builder_t; + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_create(botan_x509_cert_params_builder_t* builder_obj); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_common_name(botan_x509_cert_params_builder_t builder, const char* name); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_country(botan_x509_cert_params_builder_t builder, const char* country); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_locality(botan_x509_cert_params_builder_t builder, const char* locality); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_serial_number(botan_x509_cert_params_builder_t builder, + const char* serial_number); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_organizational_unit(botan_x509_cert_params_builder_t builder, + const char* org_unit); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_ipv4(botan_x509_cert_params_builder_t builder, uint32_t ipv4); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_allowed_usage(botan_x509_cert_params_builder_t builder, uint32_t usage); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, + botan_asn1_oid_t oid); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t* limit); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int is_critical); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, + botan_x509_ext_as_blocks_t as_blocks, + int is_critical); + +/* +* X.509 cert creation +*/ +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_into_self_signed(botan_x509_cert_t* cert_obj, + botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, + const botan_mp_t* serial_number, + const char* hash_fn, + const char* padding); + +typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_into_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, + botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + const char* hash_fn, + const char* padding, + const char* challenge_password); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_load_file(botan_x509_pkcs10_req_t* req_obj, const char* req_path); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_load(botan_x509_pkcs10_req_t* req_obj, const uint8_t req_bits[], size_t req_bits_len); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_get_public_key(botan_x509_pkcs10_req_t req, botan_pubkey_t* key); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_get_allowed_usage(botan_x509_pkcs10_req_t req, uint32_t* usage); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_is_ca(botan_x509_pkcs10_req_t req, int* is_ca, size_t* limit); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_verify_signature(botan_x509_pkcs10_req_t req, botan_pubkey_t key, int* result); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_view_der(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_bin_fn view); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_sign(botan_x509_cert_t* subject_cert, + botan_x509_pkcs10_req_t subject_req, + botan_x509_cert_t issuing_cert, + botan_privkey_t issuing_key, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, + const botan_mp_t* serial_number, + const char* hash_fn, + const char* padding); + /* * X.509 CRL **************************/ @@ -2220,6 +2447,55 @@ BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_crl_create(botan_x509_crl_t* crl_obj, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const char* hash_fn, + const char* padding); + +/* Must match values of CRL_Code in pkix_enums.h */ +enum botan_x509_crl_reason_code /* NOLINT(*-enum-size) */ { + UNSPECIFIED = 0, + KEY_COMPROMISE = 1, + CA_COMPROMISE = 2, + AFFILIATION_CHANGED = 3, + SUPERSEDED = 4, + CESSATION_OF_OPERATION = 5, + CERTIFICATE_HOLD = 6, + REMOVE_FROM_CRL = 8, + PRIVILIGE_WITHDRAWN = 9, + AA_COMPROMISE = 10 +}; + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_crl_update(botan_x509_crl_t* crl_obj, + botan_x509_crl_t last_crl, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const botan_x509_cert_t* revoked, + size_t revoked_len, + uint8_t reason, + const char* hash_fn, + const char* padding); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_crl_get_entry(botan_x509_crl_t crl, size_t i, botan_mp_t serial, uint64_t* expire_time, uint8_t* reason); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key, int* result); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_view_pem(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_str_fn view); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_view_der(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_bin_fn view); + BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_destroy(botan_x509_crl_t crl); /** diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 55756ca9ba6..6f0f301c58b 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -1,31 +1,58 @@ /* * (C) 2015,2017,2018 Jack Lloyd +* (C) 2025 Dominik Schricker * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include +#include +#include +#include #include +#include #include #include -#if defined(BOTAN_HAS_X509_CERTIFICATES) - #include - #include - #include - #include -#endif +namespace { +std::chrono::system_clock::time_point timepoint_from_timestamp(uint64_t time_since_epoch) { + return std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); +} -extern "C" { +Botan::X509_Time time_from_timestamp(uint64_t time_since_epoch) { + return Botan::X509_Time(timepoint_from_timestamp(time_since_epoch)); +} -using namespace Botan_FFI; +template +T default_from_ptr(U* value) { + T ret; + if(value != nullptr) { + ret = value; + } + return ret; +} -#if defined(BOTAN_HAS_X509_CERTIFICATES) +template +std::optional optional_from_ptr(T* value) { + if(value != nullptr) { + return *value; + } + return std::nullopt; +} + +std::optional optional_from_ptr(const char* value) { + if(value != nullptr) { + return std::string(value); + } + return std::nullopt; +} +} // namespace -BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); +extern "C" { -#endif +using namespace Botan_FFI; int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* cert_path) { if(cert_obj == nullptr || cert_path == nullptr) { @@ -97,6 +124,22 @@ int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key) #endif } +int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, const char* key, size_t* len) { + if(key == nullptr || len == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + auto issuer_info = c.issuer_info(key); + *len = issuer_info.size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_get_issuer_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) { #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -115,6 +158,22 @@ int botan_x509_cert_get_issuer_dn( #endif } +int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, const char* key, size_t* len) { + if(key == nullptr || len == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + auto issuer_info = c.subject_info(key); + *len = issuer_info.size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_get_subject_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) { #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -133,8 +192,33 @@ int botan_x509_cert_get_subject_dn( #endif } +int botan_x509_cert_get_subject_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, + [=](const auto& c) { return invoke_view_callback(view, ctx, c.subject_dn().to_string()); }); +#else + BOTAN_UNUSED(cert, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_get_issuer_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, + [=](const auto& c) { return invoke_view_callback(view, ctx, c.subject_dn().to_string()); }); +#else + BOTAN_UNUSED(cert, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) return copy_view_str(reinterpret_cast(out), out_len, botan_x509_cert_view_as_string, cert); +#else + BOTAN_UNUSED(cert, out, out_len) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif } int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { @@ -146,6 +230,15 @@ int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, b #endif } +int botan_x509_cert_view_pem(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return invoke_view_callback(view, ctx, c.PEM_encode()); }); +#else + BOTAN_UNUSED(cert, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { @@ -161,6 +254,74 @@ int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage #endif } +int botan_x509_cert_get_allowed_usage(botan_x509_cert_t cert, uint32_t* usage) { + if(usage == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + *usage = c.constraints().value(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_is_ca(botan_x509_cert_t cert, int* is_ca, size_t* limit) { + if(is_ca == nullptr || limit == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + if(c.is_CA_cert()) { + *is_ca = 1; + *limit = c.path_limit(); + } else { + *is_ca = 0; + *limit = 0; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, + [=](const auto& c) -> int { return invoke_view_callback(view, ctx, c.ocsp_responder()); }); +#else + BOTAN_UNUSED(cert, ctx, view) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_is_self_signed(botan_x509_cert_t cert, int* out) { + if(out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) { + if(c.is_self_signed()) { + *out = 1; + } else { + *out = 0; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert, out) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_destroy(botan_x509_cert_t cert) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_CHECKED_DELETE(cert); @@ -355,12 +516,6 @@ const char* botan_x509_cert_validation_status(int code) { #endif } -#if defined(BOTAN_HAS_X509_CERTIFICATES) - -BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); - -#endif - int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path) { if(crl_obj == nullptr || crl_path == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; @@ -395,6 +550,152 @@ int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], siz #endif } +int botan_x509_crl_create(botan_x509_crl_t* crl_obj, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const char* hash_fn, + const char* padding) { + if(crl_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& rng_ = safe_get(rng); + auto ca = Botan::X509_CA(safe_get(ca_cert), + safe_get(ca_key), + default_from_ptr(hash_fn), + default_from_ptr(padding), + rng_); + auto crl = std::make_unique( + ca.new_crl(rng_, timepoint_from_timestamp(issue_time), std::chrono::seconds(next_update))); + return ffi_new_object(crl_obj, std::move(crl)); + }); +#else + BOTAN_UNUSED(rng, ca_cert, ca_key, hash_fn, padding, issue_time, next_update); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_update(botan_x509_crl_t* crl_obj, + botan_x509_crl_t last_crl, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const botan_x509_cert_t* revoked, + size_t revoked_len, + uint8_t reason, + const char* hash_fn, + const char* padding) { + if(crl_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + if(revoked_len == 0) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + auto& rng_ = safe_get(rng); + auto ca = Botan::X509_CA(safe_get(ca_cert), + safe_get(ca_key), + default_from_ptr(hash_fn), + default_from_ptr(padding), + rng_); + + std::vector entries; + for(size_t i = 0; i < revoked_len; i++) { + entries.push_back(Botan::CRL_Entry(safe_get(revoked[i]), static_cast(reason))); + } + + auto crl = std::make_unique(ca.update_crl( + safe_get(last_crl), entries, rng_, timepoint_from_timestamp(issue_time), std::chrono::seconds(next_update))); + return ffi_new_object(crl_obj, std::move(crl)); + }); +#else + BOTAN_UNUSED( + last_crl, rng, ca_cert, ca_key, hash_fn, padding, issue_time, next_update, revoked, revoked_len, reason); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count) { + if(count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) { + *count = c.get_revoked().size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(crl); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_get_entry( + botan_x509_crl_t crl, size_t i, botan_mp_t serial, uint64_t* expire_time, uint8_t* reason) { + if(expire_time == nullptr || reason == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + const auto& entries = safe_get(crl).get_revoked(); + if(i >= entries.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + *reason = static_cast(entries[i].reason_code()); + *expire_time = entries[i].expire_time().time_since_epoch(); + safe_get(serial)._assign_from_bytes(entries[i].serial_number()); + return BOTAN_FFI_SUCCESS; +#else + BOTAN_UNUSED(crl, i, serial); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key, int* result) { + if(result == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + bool ok = safe_get(crl).check_signature(safe_get(key)); + if(ok) { + *result = 1; + } else { + *result = 0; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(crl, key); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_view_pem(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) -> int { return invoke_view_callback(view, ctx, c.PEM_encode()); }); +#else + BOTAN_UNUSED(crl, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_view_der(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_bin_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) -> int { return invoke_view_callback(view, ctx, c.BER_encode()); }); +#else + BOTAN_UNUSED(crl, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_crl_destroy(botan_x509_crl_t crl) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_CHECKED_DELETE(crl); @@ -491,4 +792,391 @@ int botan_x509_cert_verify_with_crl(int* result_code, return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } + +int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(builder); +#else + BOTAN_UNUSED(builder); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_params_builder_create(botan_x509_cert_params_builder_t* builder_obj) { + if(builder_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto co = std::make_unique(); + return ffi_new_object(builder_obj, std::move(co)); + }); +#else + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) + #define X509_GET_CERT_PARAMS_BUILDER_STRING(FIELD_NAME) \ + int botan_x509_cert_params_builder_add_##FIELD_NAME(botan_x509_cert_params_builder_t builder, \ + const char* value) { \ + if(value == nullptr) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.add_##FIELD_NAME(value); }); \ + } +#else + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) + #define X509_GET_CERT_PARAMS_BUILDER_STRING(FIELD_NAME) \ + int botan_x509_cert_params_builder_add_##FIELD_NAME(botan_x509_cert_params_builder_t builder, \ + const char* value) { \ + if(value == nullptr) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + BOTAN_UNUSED(builder); \ + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; \ + } +#endif + +X509_GET_CERT_PARAMS_BUILDER_STRING(common_name) +X509_GET_CERT_PARAMS_BUILDER_STRING(country) +X509_GET_CERT_PARAMS_BUILDER_STRING(state) +X509_GET_CERT_PARAMS_BUILDER_STRING(locality) +X509_GET_CERT_PARAMS_BUILDER_STRING(serial_number) +X509_GET_CERT_PARAMS_BUILDER_STRING(organization) +X509_GET_CERT_PARAMS_BUILDER_STRING(organizational_unit) +X509_GET_CERT_PARAMS_BUILDER_STRING(email) +X509_GET_CERT_PARAMS_BUILDER_STRING(dns) +X509_GET_CERT_PARAMS_BUILDER_STRING(uri) +X509_GET_CERT_PARAMS_BUILDER_STRING(xmpp) + +int botan_x509_cert_params_builder_add_ipv4(botan_x509_cert_params_builder_t builder, uint32_t ipv4) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.add_ipv4(ipv4); }); +#else + BOTAN_UNUSED(builder, ipv4); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_params_builder_add_allowed_usage(botan_x509_cert_params_builder_t builder, uint32_t usage) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.add_allowed_usage(Botan::Key_Constraints(usage)); }); +#else + BOTAN_UNUSED(builder, usage); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, + botan_asn1_oid_t oid) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(builder).add_allowed_extended_usage(safe_get(oid)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(builder, oid); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t* limit) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(builder, [=](auto& o) { + auto limit_ = optional_from_ptr(limit); + o.set_as_ca_certificate(limit_); + }); +#else + BOTAN_UNUSED(builder, limit); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int is_critical) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + if(is_critical != 0 && is_critical != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + try { + safe_get(builder).add_extension(safe_get(ip_addr_blocks).copy(), static_cast(is_critical)); + } catch(Botan::Invalid_Argument&) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(builder, ip_addr_blocks, is_critical); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, + botan_x509_ext_as_blocks_t as_blocks, + int is_critical) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + if(is_critical != 0 && is_critical != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + try { + safe_get(builder).add_extension(safe_get(as_blocks).copy(), static_cast(is_critical)); + } catch(Botan::Invalid_Argument&) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(builder, as_blocks, is_critical); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_params_builder_into_self_signed(botan_x509_cert_t* cert_obj, + botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, + const botan_mp_t* serial_number, + const char* hash_fn, + const char* padding) { + if(cert_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto hash_fn_ = optional_from_ptr(hash_fn); + auto padding_ = optional_from_ptr(padding); + + std::unique_ptr cert; + if(serial_number != nullptr && false) { + // TODO + auto serial_no = safe_get(*serial_number); + } else { + cert = std::make_unique( + safe_get(builder).into_self_signed_cert(timepoint_from_timestamp(not_before), + timepoint_from_timestamp(not_after), + safe_get(key), + safe_get(rng), + hash_fn_, + padding_)); + } + + return ffi_new_object(cert_obj, std::move(cert)); + }); +#else + BOTAN_UNUSED(key, builder, rng, not_before, not_after, hash_fn, padding); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_params_builder_into_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, + botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + const char* hash_fn, + const char* padding, + const char* challenge_password) { + if(req_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto req = std::make_unique( + safe_get(builder).into_pkcs10_request(safe_get(key), + safe_get(rng), + optional_from_ptr(hash_fn), + optional_from_ptr(padding), + optional_from_ptr(challenge_password))); + return ffi_new_object(req_obj, std::move(req)); + }); +#else + BOTAN_UNUSED(key, builder, rng, padding, hash_fn, challenge_password); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(req); +#else + BOTAN_UNUSED(req); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_load_file(botan_x509_pkcs10_req_t* req_obj, const char* req_path) { + if(req_obj == nullptr || req_path == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + return ffi_guard_thunk(__func__, [=]() -> int { + auto req = std::make_unique(req_path); + return ffi_new_object(req_obj, std::move(req)); + }); +#else + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_load(botan_x509_pkcs10_req_t* req_obj, const uint8_t req_bits[], size_t req_bits_len) { + if(req_obj == nullptr || req_bits == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + Botan::DataSource_Memory bits(req_bits, req_bits_len); + auto req = std::make_unique(bits); + return ffi_new_object(req_obj, std::move(req)); + }); +#else + BOTAN_UNUSED(req_bits_len); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { return invoke_view_callback(view, ctx, r.PEM_encode()); }); +#else + BOTAN_UNUSED(req, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_view_der(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_bin_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { return invoke_view_callback(view, ctx, r.BER_encode()); }); +#else + BOTAN_UNUSED(crl, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_get_public_key(botan_x509_pkcs10_req_t req, botan_pubkey_t* key) { + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto public_key = safe_get(req).subject_public_key(); + return ffi_new_object(key, std::move(public_key)); + }); +#else + BOTAN_UNUSED(req); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_get_allowed_usage(botan_x509_pkcs10_req_t req, uint32_t* usage) { + if(usage == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { + *usage = r.constraints().value(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_is_ca(botan_x509_pkcs10_req_t req, int* is_ca, size_t* limit) { + if(is_ca == nullptr || limit == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { + if(r.is_CA()) { + *is_ca = 1; + // TODO + if(r.path_length_constraint().has_value()) { + *limit = r.path_length_constraint().value(); + } else { + *limit = 32; + } + } else { + *is_ca = 0; + *limit = 0; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_verify_signature(botan_x509_pkcs10_req_t req, botan_pubkey_t key, int* result) { + if(result == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + bool ok = safe_get(req).check_signature(safe_get(key)); + if(ok) { + *result = 1; + } else { + *result = 0; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(req, key); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_sign(botan_x509_cert_t* subject_cert, + botan_x509_pkcs10_req_t subject_req, + botan_x509_cert_t issuing_cert, + botan_privkey_t issuing_key, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, + const botan_mp_t* serial_number, + const char* hash_fn, + const char* padding) { + if(subject_cert == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& rng_ = safe_get(rng); + + auto ca = Botan::X509_CA(safe_get(issuing_cert), + safe_get(issuing_key), + default_from_ptr(hash_fn), + default_from_ptr(padding), + rng_); + + std::unique_ptr cert; + if(serial_number != nullptr) { + auto serial_no = safe_get(*serial_number); + cert = std::make_unique(ca.sign_request( + safe_get(subject_req), rng_, serial_no, time_from_timestamp(not_before), time_from_timestamp(not_after))); + } else { + cert = std::make_unique(ca.sign_request( + safe_get(subject_req), rng_, time_from_timestamp(not_before), time_from_timestamp(not_after))); + } + + return ffi_new_object(subject_cert, std::move(cert)); + }); +#else + BOTAN_UNUSED(subject_req, issuing_cert, issuing_key, rng, not_before, not_after, hash_fn, padding); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} } diff --git a/src/lib/ffi/ffi_cert.h b/src/lib/ffi/ffi_cert.h new file mode 100644 index 00000000000..32e2c1f6852 --- /dev/null +++ b/src/lib/ffi/ffi_cert.h @@ -0,0 +1,33 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_FFI_CERT_H_ +#define BOTAN_FFI_CERT_H_ + +#include + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + #include + #include + #include + #include + #include + #include +#endif + +extern "C" { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_params_builder_struct, Botan::CertificateParametersBuilder, 0x92597C7D); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_pkcs10_req_struct, Botan::PKCS10_Request, 0x87F0690A); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); + +#endif +} + +#endif diff --git a/src/lib/ffi/ffi_cert_ext.cpp b/src/lib/ffi/ffi_cert_ext.cpp new file mode 100644 index 00000000000..b73b6f39e90 --- /dev/null +++ b/src/lib/ffi/ffi_cert_ext.cpp @@ -0,0 +1,586 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace { +template +void ip_addr_blocks_ext_add_address(Botan::Cert_Extension::IPAddressBlocks& ext, + const uint8_t* min, + const uint8_t* max, + std::optional safi) { + const size_t version_octets = static_cast(V); + + std::array min_{}; + std::array max_{}; + std::copy(min, min + version_octets, min_.begin()); + std::copy(max, max + version_octets, max_.begin()); + ext.add_address(min_, max_, safi); +} + +template +int ip_addr_blocks_get_family(const Botan::Cert_Extension::IPAddressBlocks::IPAddressFamily& family, + int* present, + size_t* count) { + if(!std::holds_alternative>(family.addr_choice())) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + auto v4 = std::get>(family.addr_choice()); + + if(!v4.ranges().has_value()) { + *present = 0; + } else { + *present = 1; + *count = v4.ranges().value().size(); + } + return BOTAN_FFI_SUCCESS; +} + +template +int ip_addr_blocks_get_address(const Botan::Cert_Extension::IPAddressBlocks::IPAddressFamily::AddrChoice& choice, + size_t entry, + uint8_t min_out[], + uint8_t max_out[], + size_t* out_len) { + if(!std::holds_alternative>(choice)) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + auto v4 = std::get>(choice); + + if(!v4.ranges().has_value() || entry >= v4.ranges().value().size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto entry_ = v4.ranges().value().at(entry); + + auto ret = Botan_FFI::write_vec_output(min_out, out_len, entry_.min().value()); + if(ret != BOTAN_FFI_SUCCESS) { + return ret; + } + return Botan_FFI::write_vec_output(max_out, out_len, entry_.max().value()); +} + +} // namespace + +extern "C" { + +using namespace Botan_FFI; + +int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(ip_addr_blocks); +#else + BOTAN_UNUSED(opts); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_create(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) { + if(ip_addr_blocks == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + auto ext = std::make_unique(); + return ffi_new_object(ip_addr_blocks, std::move(ext), true); + }); +#else + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_create_from_cert(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks, + botan_x509_cert_t cert) { + if(ip_addr_blocks == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto* const ext = + safe_get(cert).v3_extensions().get_extension_object_as(); + + if(ext == nullptr) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + return ffi_new_object(ip_addr_blocks, + std::unique_ptr( + dynamic_cast(ext->copy().release())), + false); + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_add_ip_addr( + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, const uint8_t* min, const uint8_t* max, int ipv6, uint8_t* safi) { + if(min == nullptr || max == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto& ext = safe_get(ip_addr_blocks); + if(!ip_addr_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + std::optional safi_; + if(safi != nullptr) { + safi_ = *safi; + } + + if(ipv6 == 0) { + ip_addr_blocks_ext_add_address(ext, min, max, safi_); + } else { + ip_addr_blocks_ext_add_address(ext, min, max, safi_); + } + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ip_addr_blocks, addr_length); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) { +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto& ext = safe_get(ip_addr_blocks); + if(!ip_addr_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + std::optional safi_; + if(safi != nullptr) { + safi_ = *safi; + } + + if(ipv6 == 0) { + ext.restrict(safi_); + } else { + ext.restrict(safi_); + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ip_addr_blocks, ipv6, safi); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) { +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto& ext = safe_get(ip_addr_blocks); + if(!ip_addr_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + std::optional safi_; + if(safi != nullptr) { + safi_ = *safi; + } + + if(ipv6 == 0) { + ext.inherit(safi_); + } else { + ext.inherit(safi_); + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ip_addr_blocks, ipv6, safi); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + size_t* v4_count, + size_t* v6_count) { + if(v4_count == nullptr || v6_count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& ext = safe_get(ip_addr_blocks); + size_t v4 = 0; + size_t v6 = 0; + + for(const auto& entry : ext.addr_blocks()) { + if(entry.afi() == 1) { + v4++; + } else { + v6++; + } + } + *v4_count = v4; + *v6_count = v6; + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ip_addr_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, + size_t i, + int* has_safi, + uint8_t* safi, + int* present, + size_t* count) { + if(has_safi == nullptr || safi == nullptr || present == nullptr || count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + const auto& addr_blocks = safe_get(ip_addr_blocks).addr_blocks(); + + if(i >= addr_blocks.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + const auto& family = addr_blocks.at(i); + if(family.safi().has_value()) { + *has_safi = 1; + *safi = family.safi().value(); + } else { + *has_safi = 0; + } + + if(ipv6 == 0) { + return ip_addr_blocks_get_family( + family, present, count); + } else { + return ip_addr_blocks_get_family( + family, present, count); + } + }); +#else + BOTAN_UNUSED(ip_addr_blocks, i, ipv6); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, + size_t i, + size_t entry, + uint8_t min_out[], + uint8_t max_out[], + size_t* out_len) { + if(out_len == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + const auto& addr_blocks = safe_get(ip_addr_blocks).addr_blocks(); + + if(i >= addr_blocks.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + const auto& choice = addr_blocks.at(i).addr_choice(); + + if(ipv6 == 0) { + return ip_addr_blocks_get_address( + choice, entry, min_out, max_out, out_len); + } else { + return ip_addr_blocks_get_address( + choice, entry, min_out, max_out, out_len); + } + }); +#else + BOTAN_UNUSED(ip_addr_blocks, ipv6, i, entry, min_out, max_out); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(as_blocks); +#else + BOTAN_UNUSED(opts); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_create(botan_x509_ext_as_blocks_t* as_blocks) { + if(as_blocks == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + auto ext = std::make_unique(); + return ffi_new_object(as_blocks, std::move(ext), true); + }); +#else + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_create_from_cert(botan_x509_ext_as_blocks_t* as_blocks, botan_x509_cert_t cert) { + if(as_blocks == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto* const ext = safe_get(cert).v3_extensions().get_extension_object_as(); + + if(ext == nullptr) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + return ffi_new_object(as_blocks, + std::unique_ptr( + dynamic_cast(ext->copy().release())), + false); + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.add_asnum(min, max); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks, min, max); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.restrict_asnum(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.inherit_asnum(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.add_rdi(min, max); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks, min, max); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.restrict_rdi(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.inherit_rdi(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) { + if(present == nullptr || count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& asnum = safe_get(as_blocks).as_identifiers().asnum(); + + if(!asnum.has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& ranges = asnum.value().ranges(); + + if(!ranges.has_value()) { + *present = 0; + return BOTAN_FFI_SUCCESS; + } + + *present = 1; + *count = ranges.value().size(); + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, + size_t i, + uint32_t* min, + uint32_t* max) { + if(min == nullptr || max == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& asnum = safe_get(as_blocks).as_identifiers().asnum(); + + if(!asnum.has_value() || !asnum.value().ranges().has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& range = asnum.value().ranges().value(); + if(i >= range.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + *min = range.at(i).min(); + *max = range.at(i).max(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks, i); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) { + if(present == nullptr || count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& rdi = safe_get(as_blocks).as_identifiers().rdi(); + + if(!rdi.has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& ranges = rdi.value().ranges(); + + if(!ranges.has_value()) { + *present = 0; + return BOTAN_FFI_SUCCESS; + } + + *present = 1; + *count = ranges.value().size(); + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) { + if(min == nullptr || max == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& rdi = safe_get(as_blocks).as_identifiers().rdi(); + + if(!rdi.has_value() || !rdi.value().ranges().has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& range = rdi.value().ranges().value(); + if(i >= range.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + *min = range.at(i).min(); + *max = range.at(i).max(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks, i); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} +} diff --git a/src/lib/ffi/ffi_cert_ext.h b/src/lib/ffi/ffi_cert_ext.h new file mode 100644 index 00000000000..72b01c7bf07 --- /dev/null +++ b/src/lib/ffi/ffi_cert_ext.h @@ -0,0 +1,50 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_FFI_X509_RPKI_H_ +#define BOTAN_FFI_X509_RPKI_H_ + +#include + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + #include +#endif + +extern "C" { + +using namespace Botan_FFI; + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +struct botan_x509_ext_ip_addr_blocks_struct final + : public botan_struct { + public: + explicit botan_x509_ext_ip_addr_blocks_struct(std::unique_ptr obj, + bool writable) : + botan_struct(std::move(obj)), m_writable(writable) {} + + bool writable() const { return m_writable; } + + private: + bool m_writable; +}; + +struct botan_x509_ext_as_blocks_struct final : public botan_struct { + public: + explicit botan_x509_ext_as_blocks_struct(std::unique_ptr obj, bool writable) : + botan_struct(std::move(obj)), m_writable(writable) {} + + bool writable() const { return m_writable; } + + private: + bool m_writable; +}; + +#endif +} + +#endif diff --git a/src/lib/ffi/info.txt b/src/lib/ffi/info.txt index a10f8727187..37455603776 100644 --- a/src/lib/ffi/info.txt +++ b/src/lib/ffi/info.txt @@ -8,6 +8,8 @@ brief -> "C API for Botan's functionality" +ffi_cert.h +ffi_cert_ext.h ffi_ec.h ffi_mp.h ffi_oid.h diff --git a/src/lib/utils/types.h b/src/lib/utils/types.h index 47756252a65..2c0b8d02124 100644 --- a/src/lib/utils/types.h +++ b/src/lib/utils/types.h @@ -86,7 +86,7 @@ namespace Botan { * TLS::Client, TLS::Server, TLS::Policy, TLS::Protocol_Version, TLS::Callbacks, TLS::Ciphersuite, * TLS::Session, TLS::Session_Summary, TLS::Session_Manager, Credentials_Manager *
X.509
-* X509_Certificate, X509_CRL, X509_CA, Certificate_Extension, PKCS10_Request, X509_Cert_Options, +* X509_Certificate, X509_CRL, X509_CA, Certificate_Extension, PKCS10_Request, CertificateParametersBuilder, * Certificate_Store, Certificate_Store_In_SQL, Certificate_Store_In_SQLite *
eXtendable Output Functions
* @ref Ascon_XOF128 "Ascon-XOF128", @ref SHAKE_XOF "SHAKE" diff --git a/src/lib/x509/certstor.cpp b/src/lib/x509/certstor.cpp index 377cc52d83c..5ad5076ddeb 100644 --- a/src/lib/x509/certstor.cpp +++ b/src/lib/x509/certstor.cpp @@ -129,6 +129,17 @@ std::optional Certificate_Store_In_Memory::find_cert_by_raw_su return std::nullopt; } +std::optional Certificate_Store_In_Memory::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + for(const auto& cert : m_certs) { + if(cert.issuer_dn() == issuer_dn && std::ranges::equal(cert.serial_number(), serial_number)) { + return cert; + } + } + + return std::nullopt; +} + void Certificate_Store_In_Memory::add_crl(const X509_CRL& crl) { const X509_DN& crl_issuer = crl.issuer_dn(); diff --git a/src/lib/x509/certstor.h b/src/lib/x509/certstor.h index f802fdfb003..03bbc715aef 100644 --- a/src/lib/x509/certstor.h +++ b/src/lib/x509/certstor.h @@ -56,6 +56,16 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store /* NOLINT(*-special-member-functi virtual std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const = 0; + /** + * Find a certificate by searching for one with a matching issuer DN and + * serial number. Used for CMS or PKCS#7. + * @param issuer_dn the distinguished name of the issuer + * @param serial_number the certificate's serial number + * @return a matching certificate or nullopt otherwise + */ + virtual std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const = 0; + /** * Finds a CRL for the given certificate * @param subject the subject certificate @@ -134,6 +144,9 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store_In_Memory final : public Certific std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Finds a CRL for the given certificate */ diff --git a/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp b/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp index 06ff290b3fe..f1c891407e3 100644 --- a/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp +++ b/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp @@ -56,6 +56,7 @@ Flatfile_Certificate_Store::Flatfile_Certificate_Store(std::string_view file, bo m_dn_to_cert[cert.subject_dn()].push_back(cert); m_pubkey_sha1_to_cert.emplace(cert.subject_public_key_bitstring_sha1(), cert); m_subject_dn_sha256_to_cert.emplace(cert.raw_subject_dn_sha256(), cert); + m_issuer_dn_to_cert[cert.issuer_dn()].push_back(cert); } else if(!ignore_non_ca) { throw Invalid_Argument("Flatfile_Certificate_Store received non CA cert " + cert.subject_dn().to_string()); } @@ -72,17 +73,17 @@ std::vector Flatfile_Certificate_Store::all_subjects() const { std::vector Flatfile_Certificate_Store::find_all_certs(const X509_DN& subject_dn, const std::vector& key_id) const { - std::vector found_certs; - try { - const auto certs = m_dn_to_cert.at(subject_dn); + if(!m_dn_to_cert.contains(subject_dn)) { + return {}; + } - for(const auto& cert : certs) { - if(key_id.empty() || key_id == cert.subject_key_id()) { - found_certs.push_back(cert); - } + const auto& certs = m_dn_to_cert.at(subject_dn); + + std::vector found_certs; + for(const auto& cert : certs) { + if(key_id.empty() || key_id == cert.subject_key_id()) { + found_certs.push_back(cert); } - } catch(const std::out_of_range&) { - return {}; } return found_certs; @@ -118,6 +119,23 @@ std::optional Flatfile_Certificate_Store::find_cert_by_raw_sub return std::nullopt; } +std::optional Flatfile_Certificate_Store::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + if(!m_issuer_dn_to_cert.contains(issuer_dn)) { + return std::nullopt; + } + + const auto& certs = m_issuer_dn_to_cert.at(issuer_dn); + + for(const auto& cert : certs) { + if(std::ranges::equal(cert.serial_number(), serial_number)) { + return cert; + } + } + + return std::nullopt; +} + std::optional Flatfile_Certificate_Store::find_crl_for(const X509_Certificate& subject) const { BOTAN_UNUSED(subject); return {}; diff --git a/src/lib/x509/certstor_flatfile/certstor_flatfile.h b/src/lib/x509/certstor_flatfile/certstor_flatfile.h index f61380da2d3..8e8bce4857a 100644 --- a/src/lib/x509/certstor_flatfile/certstor_flatfile.h +++ b/src/lib/x509/certstor_flatfile/certstor_flatfile.h @@ -60,6 +60,9 @@ class BOTAN_PUBLIC_API(2, 11) Flatfile_Certificate_Store final : public Certific std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Fetching CRLs is not supported by this certificate store. This will * always return an empty list. @@ -71,6 +74,7 @@ class BOTAN_PUBLIC_API(2, 11) Flatfile_Certificate_Store final : public Certific std::map> m_dn_to_cert; std::map, std::optional> m_pubkey_sha1_to_cert; std::map, std::optional> m_subject_dn_sha256_to_cert; + std::map> m_issuer_dn_to_cert; }; } // namespace Botan diff --git a/src/lib/x509/certstor_sql/certstor_sql.cpp b/src/lib/x509/certstor_sql/certstor_sql.cpp index 0adb0396c78..94266d85663 100644 --- a/src/lib/x509/certstor_sql/certstor_sql.cpp +++ b/src/lib/x509/certstor_sql/certstor_sql.cpp @@ -107,6 +107,11 @@ std::optional Certificate_Store_In_SQL::find_cert_by_raw_subje throw Not_Implemented("Certificate_Store_In_SQL::find_cert_by_raw_subject_dn_sha256"); } +std::optional Certificate_Store_In_SQL::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& /*issuer_dn*/, std::span /*serial_number*/) const { + throw Not_Implemented("Certificate_Store_In_SQL::find_cert_by_issuer_dn_and_serial_number"); +} + std::optional Certificate_Store_In_SQL::find_crl_for(const X509_Certificate& subject) const { auto all_crls = generate_crls(); diff --git a/src/lib/x509/certstor_sql/certstor_sql.h b/src/lib/x509/certstor_sql/certstor_sql.h index 0ef0d45a980..47a53b3668b 100644 --- a/src/lib/x509/certstor_sql/certstor_sql.h +++ b/src/lib/x509/certstor_sql/certstor_sql.h @@ -53,6 +53,9 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store_In_SQL : public Certificate_Store std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Returns all subject DNs known to the store instance. */ diff --git a/src/lib/x509/certstor_system/certstor_system.cpp b/src/lib/x509/certstor_system/certstor_system.cpp index 28a83f6da49..77769da5462 100644 --- a/src/lib/x509/certstor_system/certstor_system.cpp +++ b/src/lib/x509/certstor_system/certstor_system.cpp @@ -52,6 +52,11 @@ std::optional System_Certificate_Store::find_cert_by_raw_subje return m_system_store->find_cert_by_raw_subject_dn_sha256(subject_hash); } +std::optional System_Certificate_Store::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + return m_system_store->find_cert_by_issuer_dn_and_serial_number(issuer_dn, serial_number); +} + std::optional System_Certificate_Store::find_crl_for(const X509_Certificate& subject) const { return m_system_store->find_crl_for(subject); } diff --git a/src/lib/x509/certstor_system/certstor_system.h b/src/lib/x509/certstor_system/certstor_system.h index 83412711cf3..a9f0d968b0c 100644 --- a/src/lib/x509/certstor_system/certstor_system.h +++ b/src/lib/x509/certstor_system/certstor_system.h @@ -26,6 +26,9 @@ class BOTAN_PUBLIC_API(2, 11) System_Certificate_Store final : public Certificat std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + std::optional find_crl_for(const X509_Certificate& subject) const override; std::vector all_subjects() const override; diff --git a/src/lib/x509/certstor_system_macos/certstor_macos.cpp b/src/lib/x509/certstor_system_macos/certstor_macos.cpp index e5893e905af..8adb58854a0 100644 --- a/src/lib/x509/certstor_system_macos/certstor_macos.cpp +++ b/src/lib/x509/certstor_system_macos/certstor_macos.cpp @@ -392,6 +392,30 @@ std::optional Certificate_Store_MacOS::find_cert_by_raw_subjec throw Not_Implemented("Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256"); } +std::optional Certificate_Store_MacOS::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + Certificate_Store_MacOS_Impl::Query query; + /* + Directly using kSecAttrSerialNumber can't find the certificate + Maybe macOS has a special encoding for the serial number + + query.addParameter(kSecAttrSerialNumber, serial_number); + */ + query.addParameter(kSecAttrIssuer, normalizeAndSerialize(issuer_dn)); + + /* + This is a temporary solution + Use only the issuer DN to find all certificates and filters the serial number, but may affect performance + */ + for(const auto& cert : m_impl->findAll(std::move(query))) { + if(std::ranges::equal(cert.serial_number(), serial_number)) { + return cert; + } + } + + return std::nullopt; +} + std::optional Certificate_Store_MacOS::find_crl_for(const X509_Certificate& subject) const { BOTAN_UNUSED(subject); return {}; diff --git a/src/lib/x509/certstor_system_macos/certstor_macos.h b/src/lib/x509/certstor_system_macos/certstor_macos.h index d24f4c394d0..0706e3ec040 100644 --- a/src/lib/x509/certstor_system_macos/certstor_macos.h +++ b/src/lib/x509/certstor_system_macos/certstor_macos.h @@ -63,6 +63,9 @@ class BOTAN_PUBLIC_API(2, 10) Certificate_Store_MacOS final : public Certificate std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Fetching CRLs is not supported by the keychain on macOS. This will * always return an empty list. diff --git a/src/lib/x509/certstor_system_windows/certstor_windows.cpp b/src/lib/x509/certstor_system_windows/certstor_windows.cpp index 8528733f77c..41ad3d1542a 100644 --- a/src/lib/x509/certstor_system_windows/certstor_windows.cpp +++ b/src/lib/x509/certstor_system_windows/certstor_windows.cpp @@ -219,6 +219,26 @@ std::optional Certificate_Store_Windows::find_cert_by_raw_subj throw Not_Implemented("Certificate_Store_Windows::find_cert_by_raw_subject_dn_sha256"); } +std::optional Certificate_Store_Windows::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + std::vector dn_data = issuer_dn.BER_encode(); + + _CRYPTOAPI_BLOB blob; + blob.cbData = static_cast(dn_data.size()); + blob.pbData = reinterpret_cast(dn_data.data()); + + auto filter = [&](const std::vector& certs, const X509_Certificate& cert) { + return !already_contains_certificate(certs, cert) && std::ranges::equal(cert.serial_number(), serial_number); + }; + + const auto certs = search_cert_stores(blob, CERT_FIND_ISSUER_NAME, filter, true); + if(certs.empty()) { + return std::nullopt; + } + + return certs.front(); +} + std::optional Certificate_Store_Windows::find_crl_for(const X509_Certificate& subject) const { // TODO: this could be implemented by using the CertFindCRLInStore function BOTAN_UNUSED(subject); diff --git a/src/lib/x509/certstor_system_windows/certstor_windows.h b/src/lib/x509/certstor_system_windows/certstor_windows.h index cdb836ed7a6..5e9766b4703 100644 --- a/src/lib/x509/certstor_system_windows/certstor_windows.h +++ b/src/lib/x509/certstor_system_windows/certstor_windows.h @@ -59,6 +59,9 @@ class BOTAN_PUBLIC_API(2, 11) Certificate_Store_Windows final : public Certifica std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Not Yet Implemented * @return nullptr; diff --git a/src/lib/x509/info.txt b/src/lib/x509/info.txt index 831783bedee..dd05c67a99f 100644 --- a/src/lib/x509/info.txt +++ b/src/lib/x509/info.txt @@ -1,6 +1,6 @@ X509_CERTIFICATES -> 20201106 -X509 -> 20201106 +X509 -> 20250716 OCSP -> 20201106 @@ -22,6 +22,7 @@ ocsp.h pkcs10.h pkix_enums.h pkix_types.h +x509_builder.h x509_ca.h x509_crl.h x509_ext.h diff --git a/src/lib/x509/pkix_enums.h b/src/lib/x509/pkix_enums.h index 3cdfb27e764..4bc7dd863e0 100644 --- a/src/lib/x509/pkix_enums.h +++ b/src/lib/x509/pkix_enums.h @@ -167,6 +167,8 @@ class BOTAN_PUBLIC_API(3, 0) Key_Constraints final { void operator|=(Key_Constraints::Bits other) { m_value |= other; } + void operator|=(Key_Constraints other) { m_value |= other.m_value; } + // Return true if all bits in mask are set bool includes(Key_Constraints::Bits other) const { return (m_value & other) == other; } diff --git a/src/lib/x509/x509_builder.cpp b/src/lib/x509/x509_builder.cpp new file mode 100644 index 00000000000..d4f5bc30b2e --- /dev/null +++ b/src/lib/x509/x509_builder.cpp @@ -0,0 +1,288 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include + +namespace Botan { + +class CertificateParametersBuilder::State final { + public: + const X509_DN& subject_dn() const { return m_subject_dn; } + + Extensions finalize_extensions(const Public_Key& key) const { + auto extensions = m_extensions; + + extensions.replace(setup_alt_name(extensions)); + + extensions.add_new( + std::make_unique(m_is_ca_request, m_path_limit.value_or(0)), true); + + const Key_Constraints usage = this->usage(); + + if(!usage.empty()) { + if(!usage.compatible_with(key)) { + throw Invalid_Argument("The requested key usage is incompatible with the algorithm"); + } + + extensions.add_new(std::make_unique(usage), true); + } + + if(!m_ext_usage.empty()) { + extensions.add_new(std::make_unique(m_ext_usage)); + } + + return extensions; + } + + Key_Constraints usage() const { + if(m_is_ca_request) { + return Key_Constraints::ca_constraints(); + } else { + return m_usage; + } + } + + void add_common_name(std::string_view cn) { add_subject_dn("common_name", "X520.CommonName", cn); } + + void add_country(std::string_view country) { add_subject_dn("country", "X520.Country", country); } + + void add_state(std::string_view state) { add_subject_dn("state", "X520.State", state); } + + void add_locality(std::string_view locality) { add_subject_dn("locality", "X520.Locality", locality); } + + void add_serial_number(std::string_view sn) { add_subject_dn("serial_number", "X520.SerialNumber", sn); } + + void add_organization(std::string_view org) { add_subject_dn("organization", "X520.Organization", org); } + + void add_organizational_unit(std::string_view org_unit) { + add_subject_dn("organizational_unit", "X520.OrganizationalUnit", org_unit); + } + + void add_extension(std::unique_ptr extn, bool is_critical) { + if(!m_extensions.add_new(std::move(extn), is_critical)) { + throw Invalid_Argument("CertificateParametersBuilder::add_extension: cannot add same extension twice"); + } + } + + void add_email(std::string_view email) { m_email.emplace_back(email); } + + void add_dns(std::string_view dns) { m_dns.emplace_back(dns); } + + void add_uri(std::string_view uri) { m_uri.emplace_back(uri); } + + void add_xmpp(std::string_view xmpp) { m_xmpp.emplace_back(xmpp); } + + void add_ipv4(uint32_t ipv4) { m_ipv4.push_back(ipv4); } + + void add_allowed_usage(Key_Constraints usage) { m_usage |= usage; } + + void add_allowed_extended_usage(const OID& usage) { m_ext_usage.push_back(usage); } + + void set_as_ca_certificate(std::optional path_limit) { + if(m_is_ca_request) { + throw Invalid_State("CertificateParametersBuilder::set_as_ca_certificate cannot be called twice"); + } else { + m_is_ca_request = true; + m_path_limit = path_limit; + } + } + + private: + void add_subject_dn(std::string_view fn_suffix, std::string_view attr, std::string_view value) { + const auto oid = OID::from_string(attr); + const size_t ub = X509_DN::lookup_ub(oid); + + if(value.empty()) { + throw Invalid_Argument(fmt("CertificateParametersBuilder::add_{}: empty name is prohibited", fn_suffix)); + } + + if(value.size() > ub) { + throw Invalid_Argument( + fmt("CertificateParametersBuilder::add_{}: name exceeds maximum allowed length ({}) for this type", + fn_suffix, + ub)); + } + m_subject_dn.add_attribute(oid, value); + } + + std::unique_ptr setup_alt_name(const Extensions& extensions) const { + AlternativeName subject_alt; + + /* + If the extension was already created in extensions we need to merge the + values provided with the extension value + */ + if(const auto* ext = extensions.get_extension_object_as()) { + subject_alt = ext->get_alt_name(); + } + + for(const auto& dns : m_dns) { + subject_alt.add_dns(dns); + } + for(const auto& uri : m_uri) { + subject_alt.add_uri(uri); + } + for(const auto& email : m_email) { + subject_alt.add_email(email); + } + for(const auto& xmpp : m_xmpp) { + subject_alt.add_other_name(OID::from_string("PKIX.XMPPAddr"), ASN1_String(xmpp, ASN1_Type::Utf8String)); + } + for(const uint32_t ipv4 : m_ipv4) { + subject_alt.add_ipv4_address(ipv4); + } + + return std::make_unique(subject_alt); + } + + X509_DN m_subject_dn; + Extensions m_extensions; + Key_Constraints m_usage; + std::vector m_email; + std::vector m_dns; + std::vector m_uri; + std::vector m_xmpp; + std::vector m_ipv4; + std::vector m_ext_usage; + bool m_is_ca_request = false; + std::optional m_path_limit; +}; + +CertificateParametersBuilder::CertificateParametersBuilder() : + m_state(std::make_unique()) {} + +CertificateParametersBuilder::CertificateParametersBuilder(CertificateParametersBuilder&& other) noexcept = default; + +CertificateParametersBuilder::~CertificateParametersBuilder() = default; + +X509_Certificate CertificateParametersBuilder::into_self_signed_cert(std::chrono::system_clock::time_point not_before, + std::chrono::system_clock::time_point not_after, + const Private_Key& key, + RandomNumberGenerator& rng, + std::optional hash_fn, + std::optional padding) const { + auto signer_p = X509_Object::choose_sig_format(key, rng, hash_fn.value_or(""), padding.value_or("")); + auto& signer = *signer_p; + + const AlgorithmIdentifier sig_algo = signer.algorithm_identifier(); + BOTAN_ASSERT_NOMSG(sig_algo.oid().has_value()); + + Extensions extensions = m_state->finalize_extensions(key); + + const std::vector pub_key = key.subject_public_key(); + auto skid = std::make_unique(pub_key, signer.hash_function()); + + extensions.add_new(std::make_unique(skid->get_key_id())); + extensions.add_new(std::move(skid)); + + const auto& subject_dn = m_state->subject_dn(); + + return X509_CA::make_cert( + signer, rng, sig_algo, pub_key, X509_Time(not_before), X509_Time(not_after), subject_dn, subject_dn, extensions); +} + +PKCS10_Request CertificateParametersBuilder::into_pkcs10_request( + const Private_Key& key, + RandomNumberGenerator& rng, + std::optional hash_fn, + std::optional padding, + std::optional challenge_password) const { + const auto& subject_dn = m_state->subject_dn(); + + const auto extensions = m_state->finalize_extensions(key); + + return PKCS10_Request::create( + key, subject_dn, extensions, hash_fn.value_or(""), rng, padding.value_or(""), challenge_password.value_or("")); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_common_name(std::string_view cn) { + m_state->add_common_name(cn); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_country(std::string_view country) { + m_state->add_country(country); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_organization(std::string_view org) { + m_state->add_organization(org); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_organizational_unit(std::string_view org_unit) { + m_state->add_organizational_unit(org_unit); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_locality(std::string_view locality) { + m_state->add_locality(locality); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_state(std::string_view state) { + m_state->add_state(state); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_serial_number(std::string_view serial) { + m_state->add_serial_number(serial); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_email(std::string_view email) { + m_state->add_email(email); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_uri(std::string_view uri) { + m_state->add_uri(uri); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_dns(std::string_view dns) { + m_state->add_dns(dns); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_ipv4(uint32_t ipv4) { + m_state->add_ipv4(ipv4); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_xmpp(std::string_view xmpp) { + m_state->add_xmpp(xmpp); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_allowed_usage(Key_Constraints kc) { + m_state->add_allowed_usage(kc); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_allowed_extended_usage(const OID& usage) { + m_state->add_allowed_extended_usage(usage); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_extension(std::unique_ptr extn, + bool is_critical) { + m_state->add_extension(std::move(extn), is_critical); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::set_as_ca_certificate(std::optional path_limit) { + m_state->set_as_ca_certificate(path_limit); + return (*this); +} + +} // namespace Botan diff --git a/src/lib/x509/x509_builder.h b/src/lib/x509/x509_builder.h new file mode 100644 index 00000000000..a8c7b178480 --- /dev/null +++ b/src/lib/x509/x509_builder.h @@ -0,0 +1,184 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_CERT_PARAM_BUILDER_H_ +#define BOTAN_X509_CERT_PARAM_BUILDER_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +class Certificate_Extension; +class Private_Key; +class RandomNumberGenerator; +class OID; + +/** +* Certificate Metadata Builder +* +* Allows incrementally forming metadata included in a certificate or PKCS10 request. +* +* The various add_ methods each return a reference to this, allowing for convenient method chaining. +*/ +class BOTAN_PUBLIC_API(3, 9) CertificateParametersBuilder final { + public: + CertificateParametersBuilder(); + + CertificateParametersBuilder(const CertificateParametersBuilder& other) = delete; + CertificateParametersBuilder& operator=(const CertificateParametersBuilder& other) = delete; + + CertificateParametersBuilder(CertificateParametersBuilder&& other) noexcept; + CertificateParametersBuilder& operator=(CertificateParametersBuilder&& other) = delete; + ~CertificateParametersBuilder(); + + /** + * Create a self-signed X.509 certificate from these parameters. + * + * @param not_before the initial validity time of the generated certificate + * @param not_after the final validity time of the generated certificate + * @param key the private key + * @param rng the rng to use + * @param hash_fn the hash function to use; if unset a reasonable default is used + * @param padding optional padding scheme (for specifying RSA-PSS vs RSA-PKCS1, + * otherwise not necessary) + * + * @return newly created self-signed certificate + */ + X509_Certificate into_self_signed_cert(std::chrono::system_clock::time_point not_before, + std::chrono::system_clock::time_point not_after, + const Private_Key& key, + RandomNumberGenerator& rng, + std::optional hash_fn = {}, + std::optional padding = {}) const; + + /** + * Create a PKCS10 request + * + * @param key the private key + * @param rng the rng to use + * @param hash_fn the hash function to use; if unset a reasonable default is used + * @param padding optional padding scheme (for specifying RSA-PSS vs RSA-PKCS1, + * otherwise not necessary) + * @param challenge_password an optional challenge password included in the PKCS10 request + * (originally intended as a preshared key to allow authenticating a + * later revocation request, but now obsolete in most contexts) + * + * @return newly created self-signed certificate + */ + PKCS10_Request into_pkcs10_request(const Private_Key& key, + RandomNumberGenerator& rng, + std::optional hash_fn = {}, + std::optional padding = {}, + std::optional challenge_password = {}) const; + + /** + * Add an additional common name to the certificate metadata + */ + CertificateParametersBuilder& add_common_name(std::string_view cn); + + /** + * Add an additional country name to the certificate metadata + */ + CertificateParametersBuilder& add_country(std::string_view country); + + /** + * Add an additional organization name to the certificate metadata + */ + CertificateParametersBuilder& add_organization(std::string_view org); + + /** + * Add an additional organization unit name to the certificate metadata + */ + CertificateParametersBuilder& add_organizational_unit(std::string_view org_unit); + + /** + * Add an additional locality name to the certificate metadata + */ + CertificateParametersBuilder& add_locality(std::string_view locality); + + /** + * Add an additional state name to the certificate metadata + */ + CertificateParametersBuilder& add_state(std::string_view state); + + /** + * Add an additional serial number to the certificate metadata + * + * Note this is the X.520 serial number included in the DN, and has nothing + * to do with the serial number of the issued certificate. + */ + CertificateParametersBuilder& add_serial_number(std::string_view serial); + + /** + * Add an additional email address to the certificate metadata + */ + CertificateParametersBuilder& add_email(std::string_view email); + + /** + * Add an additional URI to the certificate metadata + */ + CertificateParametersBuilder& add_uri(std::string_view uri); + + /** + * Add an additional DNS name to the certificate metadata + */ + CertificateParametersBuilder& add_dns(std::string_view dns); + + /** + * Add an additional IPv4 address to the certificate metadata + */ + CertificateParametersBuilder& add_ipv4(uint32_t ipv4); + + /** + * Add a XMPP name to the certificate metadata + */ + CertificateParametersBuilder& add_xmpp(std::string_view xmpp); + + /** + * Add another allowed usage to the KeyUsage extension + * + * This is cummulative; all usages indicated are ORed together + */ + CertificateParametersBuilder& add_allowed_usage(Key_Constraints kc); + + /** + * Add another allowed usage to the ExtendedKeyUsage extension + */ + CertificateParametersBuilder& add_allowed_extended_usage(const OID& usage); + + /** + * Add an arbitrary certificate extension + * + * The extension + */ + CertificateParametersBuilder& add_extension(std::unique_ptr extn, + bool is_critical = false); + + /** + * Set the parameters as being for a CA certificate + * + * If the path limit is specified then the BasicConstraints extension will include + * that value as the maximum chain length issuable by the resulting cert. + * + * It is not allowed to call set_as_ca_certificate more than once; further calls will + * result in an Invalid_State exception. + */ + CertificateParametersBuilder& set_as_ca_certificate(std::optional path_limit = {}); + + private: + class State; + std::unique_ptr m_state; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/x509/x509opt.cpp b/src/lib/x509/x509opt.cpp index f045c5cfd22..f17a89f2090 100644 --- a/src/lib/x509/x509opt.cpp +++ b/src/lib/x509/x509opt.cpp @@ -7,6 +7,7 @@ #include +#include #include #include @@ -92,4 +93,74 @@ X509_Cert_Options::X509_Cert_Options(std::string_view initial_opts, uint32_t exp } } +CertificateParametersBuilder X509_Cert_Options::into_builder() const { + auto builder = CertificateParametersBuilder(); + if(!this->common_name.empty()) { + builder.add_common_name(this->common_name); + } + if(!this->country.empty()) { + builder.add_country(this->country); + } + if(!this->organization.empty()) { + builder.add_organization(this->organization); + } + if(!this->org_unit.empty()) { + builder.add_organizational_unit(this->org_unit); + } + for(const auto& ou : this->more_org_units) { + if(!ou.empty()) { + builder.add_organizational_unit(ou); + } + } + if(!this->locality.empty()) { + builder.add_locality(this->locality); + } + if(!this->state.empty()) { + builder.add_state(this->state); + } + if(!this->serial_number.empty()) { + builder.add_serial_number(this->serial_number); + } + if(!this->email.empty()) { + builder.add_email(this->email); + } + if(!this->uri.empty()) { + builder.add_uri(this->uri); + } + if(!this->ip.empty()) { + if(auto ipv4 = string_to_ipv4(this->ip)) { + builder.add_ipv4(*ipv4); + } else { + throw Invalid_Argument(fmt("Invalid IPv4 address '{}'", this->ip)); + } + } + + if(!this->dns.empty()) { + builder.add_dns(this->dns); + } + for(const auto& nm : this->more_dns) { + if(!nm.empty()) { + builder.add_dns(nm); + } + } + if(!this->xmpp.empty()) { + builder.add_xmpp(this->xmpp); + } + if(this->is_CA) { + builder.set_as_ca_certificate(this->path_limit); + } + if(!this->constraints.empty()) { + builder.add_allowed_usage(this->constraints); + } + for(const OID& usage : this->ex_constraints) { + builder.add_allowed_extended_usage(usage); + } + + for(auto& [extn, is_critical] : this->extensions.extensions()) { + builder.add_extension(std::move(extn), is_critical); + } + + return builder; +} + } // namespace Botan diff --git a/src/lib/x509/x509self.cpp b/src/lib/x509/x509self.cpp index fe66501fda3..7533a3c15d1 100644 --- a/src/lib/x509/x509self.cpp +++ b/src/lib/x509/x509self.cpp @@ -7,76 +7,7 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Botan { - -namespace { - -/* -* Load information from the X509_Cert_Options -*/ -X509_DN load_dn_info(const X509_Cert_Options& opts) { - X509_DN subject_dn; - - subject_dn.add_attribute("X520.CommonName", opts.common_name); - subject_dn.add_attribute("X520.Country", opts.country); - subject_dn.add_attribute("X520.State", opts.state); - subject_dn.add_attribute("X520.Locality", opts.locality); - subject_dn.add_attribute("X520.Organization", opts.organization); - subject_dn.add_attribute("X520.OrganizationalUnit", opts.org_unit); - subject_dn.add_attribute("X520.SerialNumber", opts.serial_number); - - for(const auto& extra_ou : opts.more_org_units) { - subject_dn.add_attribute("X520.OrganizationalUnit", extra_ou); - } - - return subject_dn; -} - -auto create_alt_name_ext(const X509_Cert_Options& opts, const Extensions& extensions) { - AlternativeName subject_alt; - - /* - If the extension was already created in opts.extension we need to - merge the values provided in opts with the values set in the extension. - */ - if(const auto* ext = extensions.get_extension_object_as()) { - subject_alt = ext->get_alt_name(); - } - - subject_alt.add_dns(opts.dns); - for(const auto& nm : opts.more_dns) { - subject_alt.add_dns(nm); - } - subject_alt.add_uri(opts.uri); - subject_alt.add_email(opts.email); - if(!opts.ip.empty()) { - if(auto ipv4 = string_to_ipv4(opts.ip)) { - subject_alt.add_ipv4_address(*ipv4); - } else { - throw Invalid_Argument(fmt("Invalid IPv4 address '{}'", opts.ip)); - } - } - - if(!opts.xmpp.empty()) { - subject_alt.add_other_name(OID::from_string("PKIX.XMPPAddr"), ASN1_String(opts.xmpp, ASN1_Type::Utf8String)); - } - - return std::make_unique(subject_alt); -} - -} // namespace - -namespace X509 { +namespace Botan::X509 { /* * Create a new self-signed X.509 certificate @@ -85,37 +16,13 @@ X509_Certificate create_self_signed_cert(const X509_Cert_Options& opts, const Private_Key& key, std::string_view hash_fn, RandomNumberGenerator& rng) { - const std::vector pub_key = X509::BER_encode(key); - auto signer = X509_Object::choose_sig_format(key, rng, hash_fn, opts.padding_scheme); - const AlgorithmIdentifier sig_algo = signer->algorithm_identifier(); - BOTAN_ASSERT_NOMSG(sig_algo.oid().has_value()); + auto not_before = opts.start.to_std_timepoint(); + auto not_after = opts.end.to_std_timepoint(); - const auto subject_dn = load_dn_info(opts); + const std::optional padding = + (opts.padding_scheme.empty()) ? std::nullopt : std::optional(opts.padding_scheme); - Extensions extensions = opts.extensions; - - const auto constraints = opts.is_CA ? Key_Constraints::ca_constraints() : opts.constraints; - - if(!constraints.compatible_with(key)) { - throw Invalid_Argument("The requested key constraints are incompatible with the algorithm"); - } - - extensions.add_new(std::make_unique(opts.is_CA, opts.path_limit), true); - - if(!constraints.empty()) { - extensions.add_new(std::make_unique(constraints), true); - } - - auto skid = std::make_unique(pub_key, signer->hash_function()); - - extensions.add_new(std::make_unique(skid->get_key_id())); - extensions.add_new(std::move(skid)); - - extensions.replace(create_alt_name_ext(opts, extensions)); - - extensions.add_new(std::make_unique(opts.ex_constraints)); - - return X509_CA::make_cert(*signer, rng, sig_algo, pub_key, opts.start, opts.end, subject_dn, subject_dn, extensions); + return opts.into_builder().into_self_signed_cert(not_before, not_after, key, rng, hash_fn, padding); } /* @@ -125,31 +32,14 @@ PKCS10_Request create_cert_req(const X509_Cert_Options& opts, const Private_Key& key, std::string_view hash_fn, RandomNumberGenerator& rng) { - const auto subject_dn = load_dn_info(opts); - - const auto constraints = opts.is_CA ? Key_Constraints::ca_constraints() : opts.constraints; - - if(!constraints.compatible_with(key)) { - throw Invalid_Argument("The requested key constraints are incompatible with the algorithm"); - } - - Extensions extensions = opts.extensions; + const std::optional challenge_password = + (opts.challenge.empty()) ? std::nullopt : std::optional(opts.challenge); + const std::optional padding = + (opts.padding_scheme.empty()) ? std::nullopt : std::optional(opts.padding_scheme); - extensions.add_new(std::make_unique(opts.is_CA, opts.path_limit)); + // opts.start and opts.end are ignored here - if(!constraints.empty()) { - extensions.add_new(std::make_unique(constraints)); - } - - extensions.replace(create_alt_name_ext(opts, extensions)); - - if(!opts.ex_constraints.empty()) { - extensions.add_new(std::make_unique(opts.ex_constraints)); - } - - return PKCS10_Request::create(key, subject_dn, extensions, hash_fn, rng, opts.padding_scheme, opts.challenge); + return opts.into_builder().into_pkcs10_request(key, rng, hash_fn, padding, challenge_password); } -} // namespace X509 - -} // namespace Botan +} // namespace Botan::X509 diff --git a/src/lib/x509/x509self.h b/src/lib/x509/x509self.h index 4362f9e1b09..d9332014348 100644 --- a/src/lib/x509/x509self.h +++ b/src/lib/x509/x509self.h @@ -10,13 +10,18 @@ #include #include +#include #include +#include +#include namespace Botan { class RandomNumberGenerator; class Private_Key; +// Older interface for creating PKCS10 requests and self-signed certificates follows + /** * Options for X.509 certificates. */ @@ -177,6 +182,8 @@ class BOTAN_PUBLIC_API(2, 0) X509_Cert_Options final { */ void add_ex_constraint(std::string_view name); + CertificateParametersBuilder into_builder() const; + /** * Construct a new options object * @param opts define the common name of this object. An example for this diff --git a/src/python/botan3.py b/src/python/botan3.py index fa71882abcb..d23ab7f9c31 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -7,6 +7,7 @@ (C) 2015,2017,2018,2019,2023 Jack Lloyd (C) 2015 Uri Blumenthal (extensions and patches) (C) 2024 Amos Treiber, René Meusel - Rohde & Schwarz Cybersecurity +(C) 2025 Dominik Schricker Botan is released under the Simplified BSD License (see license.txt) @@ -16,15 +17,17 @@ It uses botan's ffi module, which exposes a C API. """ +from __future__ import annotations from ctypes import CDLL, CFUNCTYPE, POINTER, byref, create_string_buffer, \ - c_void_p, c_size_t, c_uint8, c_uint32, c_uint64, c_int, c_uint, c_char, c_char_p, addressof -from typing import Callable + c_void_p, c_size_t, c_uint8, c_uint32, c_uint64, c_int, c_uint, c_char, c_char_p, addressof, Array +from typing import Callable, Any, Union, List, Tuple from sys import platform from time import strptime, mktime, time as system_time from binascii import hexlify from datetime import datetime from collections.abc import Iterable +from enum import IntEnum # This Python module requires the FFI API version introduced in Botan 3.10.0 # @@ -52,7 +55,7 @@ def __init__(self, message, rc=0): super().__init__(formatted_msg) - def error_code(self): + def error_code(self) -> int: return self.__rc # @@ -488,24 +491,104 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_get_serial_number, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_authority_key_id, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_subject_key_id, [c_void_p, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_is_ca, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_get_allowed_usage, [c_void_p, POINTER(c_uint32)]) + ffi_api(dll.botan_x509_cert_get_ocsp_responder, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_cert_is_self_signed, [c_void_p, POINTER(c_int)]) + ffi_api(dll.botan_x509_cert_get_public_key_bits, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_view_public_key_bits, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) ffi_api(dll.botan_x509_cert_get_public_key, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_get_issuer_dn_count, [c_void_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_issuer_dn, [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_get_subject_dn_count, [c_void_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_subject_dn, [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_get_subject_name, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_cert_get_issuer_name, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_cert_to_string, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_view_as_string, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_cert_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) ffi_api(dll.botan_x509_cert_allowed_usage, [c_void_p, c_uint]) - ffi_api(dll.botan_x509_cert_hostname_match, [c_void_p, c_char_p], [-1]) + ffi_api(dll.botan_x509_cert_hostname_match, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_verify, [POINTER(c_int), c_void_p, c_void_p, c_size_t, c_void_p, c_size_t, c_char_p, c_size_t, c_char_p, c_uint64]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_destroy, [c_void_p]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_create, [c_void_p]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_create_from_cert, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_add_ip_addr, + [c_void_p, c_char_p, c_char_p, c_int, POINTER(c_uint8)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_restrict, [c_void_p, c_int, POINTER(c_uint8)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_inherit, [c_void_p, c_int, POINTER(c_uint8)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_counts, [c_void_p, POINTER(c_size_t), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_family, + [c_void_p, c_int, c_size_t, POINTER(c_int), POINTER(c_uint8), POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_address, + [c_void_p, c_int, c_size_t, c_size_t, c_char_p, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_as_blocks_destroy, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_create, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_create_from_cert, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_add_asnum, [c_void_p, c_uint32, c_uint32]) + ffi_api(dll.botan_x509_ext_as_blocks_restrict_asnum, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_inherit_asnum, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_add_rdi, [c_void_p, c_uint32, c_uint32]) + ffi_api(dll.botan_x509_ext_as_blocks_restrict_rdi, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_inherit_rdi, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_get_asnum, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_as_blocks_get_asnum_at, [c_void_p, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) + ffi_api(dll.botan_x509_ext_as_blocks_get_rdi, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_as_blocks_get_rdi_at, [c_void_p, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) + ffi_api(dll.botan_x509_cert_params_builder_destroy, [c_void_p]) + ffi_api(dll.botan_x509_cert_params_builder_create, [c_void_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_common_name, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_country, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_state, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_locality, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_serial_number, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_organization, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_organizational_unit, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_email, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_dns, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_uri, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_xmpp, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_ipv4, [c_void_p, c_uint32]) + ffi_api(dll.botan_x509_cert_params_builder_add_allowed_usage, [c_void_p, c_uint32]) + ffi_api(dll.botan_x509_cert_params_builder_add_allowed_extended_usage, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_params_builder_set_as_ca_certificate, [c_void_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_params_builder_add_ext_ip_addr_blocks, [c_void_p, c_void_p, c_int]) + ffi_api(dll.botan_x509_cert_params_builder_add_ext_as_blocks, [c_void_p, c_void_p, c_int]) + ffi_api(dll.botan_x509_cert_params_builder_into_self_signed, + [c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_void_p, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_into_pkcs10_req, + [c_void_p, c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_pkcs10_req_destroy, [c_void_p]) + ffi_api(dll.botan_x509_pkcs10_req_load_file, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_pkcs10_req_load, [c_void_p, c_char_p, c_size_t]) + ffi_api(dll.botan_x509_pkcs10_req_get_public_key, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_pkcs10_req_get_allowed_usage, [c_void_p, POINTER(c_uint32)]) + ffi_api(dll.botan_x509_pkcs10_req_is_ca, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_pkcs10_req_verify_signature, [c_void_p, c_void_p, POINTER(c_int)]) + ffi_api(dll.botan_x509_pkcs10_req_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_pkcs10_req_view_der, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) + ffi_api(dll.botan_x509_pkcs10_req_sign, + [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_void_p, c_char_p, c_char_p]) dll.botan_x509_cert_validation_status.argtypes = [c_int] dll.botan_x509_cert_validation_status.restype = c_char_p # X509 CRL - ffi_api(dll.botan_x509_crl_load, [c_void_p, c_char_p, c_size_t]) ffi_api(dll.botan_x509_crl_load_file, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_crl_load, [c_void_p, c_char_p, c_size_t]) + ffi_api(dll.botan_x509_crl_create, + [c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint32, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_crl_update, + [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint32, c_void_p, c_size_t, c_uint8, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_crl_get_count, [c_void_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_crl_get_entry, + [c_void_p, c_size_t, c_void_p, POINTER(c_uint64), POINTER(c_uint8)]) + ffi_api(dll.botan_x509_crl_verify_signature, [c_void_p, c_void_p, POINTER(c_int)]) + ffi_api(dll.botan_x509_crl_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_crl_view_der, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) ffi_api(dll.botan_x509_crl_destroy, [c_void_p]) ffi_api(dll.botan_x509_is_revoked, [c_void_p, c_void_p], [-1]) ffi_api(dll.botan_x509_cert_verify_with_crl, @@ -578,12 +661,12 @@ def ffi_api(fn, args, allowed_errors=None): # # Internal utilities # -def _call_fn_returning_sz(fn): +def _call_fn_returning_sz(fn) -> int: sz = c_size_t(0) fn(byref(sz)) return int(sz.value) -def _call_fn_returning_vec(guess, fn): +def _call_fn_returning_vec(guess, fn) -> bytes: buf = create_string_buffer(guess) buf_len = c_size_t(len(buf)) @@ -595,7 +678,7 @@ def _call_fn_returning_vec(guess, fn): assert buf_len.value <= len(buf) return buf.raw[0:int(buf_len.value)] -def _call_fn_returning_vec_pair(guess1, guess2, fn): +def _call_fn_returning_vec_pair(guess1, guess2, fn) -> tuple[bytes, bytes]: buf1 = create_string_buffer(guess1) buf1_len = c_size_t(len(buf1)) @@ -626,7 +709,7 @@ def _view_bin_fn(_ctx, buf_val, buf_len): _view_bin_fn.output = buf_val[0:buf_len] return 0 -def _call_fn_viewing_vec(fn): +def _call_fn_viewing_vec(fn) -> bytes: fn(None, _view_bin_fn) result = _view_bin_fn.output _view_bin_fn.output = None @@ -637,22 +720,22 @@ def _view_str_fn(_ctx, str_val, _str_len): _view_str_fn.output = str_val return 0 -def _call_fn_viewing_str(fn): +def _call_fn_viewing_str(fn) -> str: fn(None, _view_str_fn) result = _view_str_fn.output.decode('utf8') _view_str_fn.output = None return result -def _ctype_str(s): +def _ctype_str(s: str | None) -> bytes | None: if s is None: return None assert isinstance(s, str) return s.encode('utf-8') -def _ctype_to_str(s): +def _ctype_to_str(s: bytes) -> str: return s.decode('utf-8') -def _ctype_bits(s): +def _ctype_bits(s: str | bytes) -> bytes: if isinstance(s, bytes): return s elif isinstance(s, str): @@ -666,28 +749,34 @@ def _ctype_bufout(buf): def _hex_encode(buf): return hexlify(buf).decode('ascii') + +# +# Internal types +# +_MPIArg = Union[str, "MPI", Any, None] + # # Versioning # -def version_major(): +def version_major() -> int: return int(_DLL.botan_version_major()) -def version_minor(): +def version_minor() -> int: return int(_DLL.botan_version_minor()) -def version_patch(): +def version_patch() -> int: return int(_DLL.botan_version_patch()) -def ffi_api_version(): +def ffi_api_version() -> int: return int(_DLL.botan_ffi_api_version()) -def version_string(): +def version_string() -> int: return _DLL.botan_version_string().decode('ascii') # # Utilities # -def const_time_compare(x, y): +def const_time_compare(x: str | bytes, y: str | bytes) -> bool: xbits = _ctype_bits(x) ybits = _ctype_bits(y) len_x = len(xbits) @@ -716,7 +805,7 @@ def handle_(self): class TPM2Context(TPM2Object): """TPM 2.0 Context object""" - def __init__(self, tcti_name_maybe_with_conf: str = None, tcti_conf: str = None): + def __init__(self, tcti_name_maybe_with_conf: str | None = None, tcti_conf: str | None = None): """Construct a TPM2Context object with optional TCTI name and configuration.""" obj = c_void_p(0) @@ -735,7 +824,7 @@ def supports_botan_crypto_backend() -> bool: rc = _DLL.botan_tpm2_supports_crypto_backend() return rc == 1 - def enable_botan_crypto_backend(self, rng): + def enable_botan_crypto_backend(self, rng: RandomNumberGenerator): """Enables the Botan-based crypto backend. The passed rng MUST NOT be dependent on the TPM.""" # By keeping a reference to the passed-in RNG object, we make sure @@ -781,7 +870,7 @@ def __init__(self, ctx: TPM2Context): # class RandomNumberGenerator: # Can also use type "system" - def __init__(self, rng_type='system', **kwargs): + def __init__(self, rng_type: str = 'system', **kwargs): """Constructs a RandomNumberGenerator of type rng_type Available RNG types are:: @@ -813,27 +902,26 @@ def __del__(self): def handle_(self): return self.__obj - def reseed(self, bits=256): + def reseed(self, bits: int = 256): _DLL.botan_rng_reseed(self.__obj, bits) - def reseed_from_rng(self, source_rng, bits=256): + def reseed_from_rng(self, source_rng: RandomNumberGenerator, bits: int = 256): _DLL.botan_rng_reseed_from_rng(self.__obj, source_rng.handle_(), bits) - def add_entropy(self, seed): + def add_entropy(self, seed: str | bytes): seedbits = _ctype_bits(seed) _DLL.botan_rng_add_entropy(self.__obj, seedbits, len(seedbits)) - def get(self, length): + def get(self, length: int) -> bytes: out = create_string_buffer(length) - l = c_size_t(length) - _DLL.botan_rng_get(self.__obj, out, l) + _DLL.botan_rng_get(self.__obj, out, c_size_t(length)) return _ctype_bufout(out) # # Block cipher # class BlockCipher: - def __init__(self, algo): + def __init__(self, algo: str | c_void_p): if isinstance(algo, c_void_p): self.__obj = algo @@ -856,10 +944,10 @@ def __init__(self, algo): def __del__(self): _DLL.botan_block_cipher_destroy(self.__obj) - def set_key(self, key): + def set_key(self, key: bytes): _DLL.botan_block_cipher_set_key(self.__obj, key, len(key)) - def encrypt(self, pt): + def encrypt(self, pt: bytes) -> Array[c_char]: if len(pt) % self.block_size() != 0: raise Exception("Invalid input must be multiple of block size") @@ -868,7 +956,7 @@ def encrypt(self, pt): _DLL.botan_block_cipher_encrypt_blocks(self.__obj, pt, output, blocks) return output - def decrypt(self, ct): + def decrypt(self, ct: bytes) -> Array[c_char]: if len(ct) % self.block_size() != 0: raise Exception("Invalid input must be multiple of block size") @@ -877,22 +965,22 @@ def decrypt(self, ct): _DLL.botan_block_cipher_decrypt_blocks(self.__obj, ct, output, blocks) return output - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_block_cipher_name(self.__obj, b, bl)) def clear(self): _DLL.botan_block_cipher_clear(self.__obj) - def block_size(self): + def block_size(self) -> int: return self.__block_size - def minimum_keylength(self): + def minimum_keylength(self) -> int: return self.__min_keylen - def maximum_keylength(self): + def maximum_keylength(self) -> int: return self.__max_keylen - def keylength_modulo(self): + def keylength_modulo(self) -> int: return self.__mod_keylen @@ -900,7 +988,7 @@ def keylength_modulo(self): # Hash function # class HashFunction: - def __init__(self, algo): + def __init__(self, algo: str | c_void_p): if isinstance(algo, c_void_p): self.__obj = algo @@ -909,34 +997,34 @@ def __init__(self, algo): self.__obj = c_void_p(0) _DLL.botan_hash_init(byref(self.__obj), _ctype_str(algo), flags) - self.__output_length = _call_fn_returning_sz(lambda l: _DLL.botan_hash_output_length(self.__obj, l)) - self.__block_size = _call_fn_returning_sz(lambda l: _DLL.botan_hash_block_size(self.__obj, l)) + self.__output_length = _call_fn_returning_sz(lambda length: _DLL.botan_hash_output_length(self.__obj, length)) + self.__block_size = _call_fn_returning_sz(lambda length: _DLL.botan_hash_block_size(self.__obj, length)) def __del__(self): _DLL.botan_hash_destroy(self.__obj) - def copy_state(self): + def copy_state(self) -> HashFunction: copy = c_void_p(0) _DLL.botan_hash_copy_state(byref(copy), self.__obj) return HashFunction(copy) - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_hash_name(self.__obj, b, bl)) def clear(self): _DLL.botan_hash_clear(self.__obj) - def output_length(self): + def output_length(self) -> int: return self.__output_length - def block_size(self): + def block_size(self) -> int: return self.__block_size - def update(self, x): + def update(self, x: str | bytes): bits = _ctype_bits(x) _DLL.botan_hash_update(self.__obj, bits, len(bits)) - def final(self): + def final(self) -> bytes: out = create_string_buffer(self.output_length()) _DLL.botan_hash_final(self.__obj, out) return _ctype_bufout(out) @@ -945,7 +1033,7 @@ def final(self): # Message authentication codes # class MsgAuthCode: - def __init__(self, algo): + def __init__(self, algo: str): flags = c_uint32(0) # always zero in this API version self.__obj = c_void_p(0) _DLL.botan_mac_init(byref(self.__obj), _ctype_str(algo), flags) @@ -969,38 +1057,38 @@ def __del__(self): def clear(self): _DLL.botan_mac_clear(self.__obj) - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_mac_name(self.__obj, b, bl)) - def output_length(self): + def output_length(self) -> int: return self.__output_length - def minimum_keylength(self): + def minimum_keylength(self) -> int: return self.__min_keylen - def maximum_keylength(self): + def maximum_keylength(self) -> int: return self.__max_keylen - def keylength_modulo(self): + def keylength_modulo(self) -> int: return self.__mod_keylen - def set_key(self, key): + def set_key(self, key: bytes): _DLL.botan_mac_set_key(self.__obj, key, len(key)) - def set_nonce(self, nonce): + def set_nonce(self, nonce: bytes): _DLL.botan_mac_set_nonce(self.__obj, nonce, len(nonce)) - def update(self, x): + def update(self, x: str | bytes): bits = _ctype_bits(x) _DLL.botan_mac_update(self.__obj, bits, len(bits)) - def final(self): + def final(self) -> bytes: out = create_string_buffer(self.output_length()) _DLL.botan_mac_final(self.__obj, out) return _ctype_bufout(out) class SymmetricCipher: - def __init__(self, algo, encrypt=True): + def __init__(self, algo: str, encrypt: bool = True): flags = 0 if encrypt else 1 self.__obj = c_void_p(0) _DLL.botan_cipher_init(byref(self.__obj), _ctype_str(algo), flags) @@ -1010,50 +1098,50 @@ def __init__(self, algo, encrypt=True): def __del__(self): _DLL.botan_cipher_destroy(self.__obj) - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_cipher_name(self.__obj, b, bl)) - def default_nonce_length(self): - l = c_size_t(0) - _DLL.botan_cipher_get_default_nonce_length(self.__obj, byref(l)) - return l.value + def default_nonce_length(self) -> int: + length = c_size_t(0) + _DLL.botan_cipher_get_default_nonce_length(self.__obj, byref(length)) + return length.value - def update_granularity(self): - l = c_size_t(0) - _DLL.botan_cipher_get_update_granularity(self.__obj, byref(l)) - return l.value + def update_granularity(self) -> int: + length = c_size_t(0) + _DLL.botan_cipher_get_update_granularity(self.__obj, byref(length)) + return length.value - def ideal_update_granularity(self): - l = c_size_t(0) - _DLL.botan_cipher_get_ideal_update_granularity(self.__obj, byref(l)) - return l.value + def ideal_update_granularity(self) -> int: + length = c_size_t(0) + _DLL.botan_cipher_get_ideal_update_granularity(self.__obj, byref(length)) + return length.value - def key_length(self): + def key_length(self) -> tuple[int, int]: kmin = c_size_t(0) kmax = c_size_t(0) _DLL.botan_cipher_query_keylen(self.__obj, byref(kmin), byref(kmax)) return kmin.value, kmax.value - def minimum_keylength(self): - l = c_size_t(0) - _DLL.botan_cipher_get_keyspec(self.__obj, byref(l), None, None) - return l.value + def minimum_keylength(self) -> int: + length = c_size_t(0) + _DLL.botan_cipher_get_keyspec(self.__obj, byref(length), None, None) + return length.value - def maximum_keylength(self): - l = c_size_t(0) - _DLL.botan_cipher_get_keyspec(self.__obj, None, byref(l), None) - return l.value + def maximum_keylength(self) -> int: + length = c_size_t(0) + _DLL.botan_cipher_get_keyspec(self.__obj, None, byref(length), None) + return length.value - def tag_length(self): - l = c_size_t(0) - _DLL.botan_cipher_get_tag_length(self.__obj, byref(l)) - return l.value + def tag_length(self) -> int: + length = c_size_t(0) + _DLL.botan_cipher_get_tag_length(self.__obj, byref(length)) + return length.value - def is_authenticated(self): + def is_authenticated(self) -> bool: rc = _DLL.botan_cipher_is_authenticated(self.__obj) return rc == 1 - def valid_nonce_length(self, nonce_len): + def valid_nonce_length(self, nonce_len) -> bool: rc = _DLL.botan_cipher_valid_nonce_length(self.__obj, nonce_len) return rc == 1 @@ -1063,16 +1151,16 @@ def reset(self): def clear(self): _DLL.botan_cipher_clear(self.__obj) - def set_key(self, key): + def set_key(self, key: bytes): _DLL.botan_cipher_set_key(self.__obj, key, len(key)) - def set_assoc_data(self, ad): + def set_assoc_data(self, ad: bytes): _DLL.botan_cipher_set_associated_data(self.__obj, ad, len(ad)) - def start(self, nonce): + def start(self, nonce: bytes): _DLL.botan_cipher_start(self.__obj, nonce, len(nonce)) - def _update(self, txt, final): + def _update(self, txt: str | bytes | None, final: bool): inp = txt if txt else '' bits = _ctype_bits(inp) @@ -1100,13 +1188,13 @@ def _update(self, txt, final): assert inp_consumed.value == inp_sz.value return out.raw[0:int(out_written.value)] - def update(self, txt): + def update(self, txt: str | bytes): return self._update(txt, False) - def finish(self, txt=None): + def finish(self, txt: str | bytes | None = None): return self._update(txt, True) -def bcrypt(passwd, rng_obj, work_factor=10): +def bcrypt(passwd: str, rng_obj: RandomNumberGenerator, work_factor=10): """ Bcrypt password hashing """ @@ -1120,14 +1208,14 @@ def bcrypt(passwd, rng_obj, work_factor=10): b = b[:-1] return _ctype_to_str(b) -def check_bcrypt(passwd, passwd_hash): +def check_bcrypt(passwd: str, passwd_hash: str): rc = _DLL.botan_bcrypt_is_valid(_ctype_str(passwd), _ctype_str(passwd_hash)) return rc == 0 # # PBKDF # -def pbkdf(algo, password, out_len, iterations=100000, salt=None): +def pbkdf(algo: str, password: str, out_len: int, iterations: int = 100000, salt: bytes | None = None) -> tuple[bytes, int, bytes]: if salt is None: salt = RandomNumberGenerator().get(12) @@ -1139,7 +1227,7 @@ def pbkdf(algo, password, out_len, iterations=100000, salt=None): salt, len(salt)) return (salt, iterations, out_buf.raw) -def pbkdf_timed(algo, password, out_len, ms_to_run=300, salt=None): +def pbkdf_timed(algo: str, password: str, out_len: int, ms_to_run: int = 300, salt: bytes | None = None) -> tuple[bytes, int, bytes]: if salt is None: salt = RandomNumberGenerator().get(12) @@ -1156,9 +1244,9 @@ def pbkdf_timed(algo, password, out_len, ms_to_run=300, salt=None): # # Scrypt # -def scrypt(out_len, password, salt, n=1024, r=8, p=8): +def scrypt(out_len: int, password: str, salt: str | bytes, n: int = 1024, r: int = 8, p: int = 8) -> bytes: out_buf = create_string_buffer(out_len) - passbits = _ctype_str(password) + passbits = _ctype_bits(password) saltbits = _ctype_bits(salt) _DLL.botan_pwdhash(_ctype_str("Scrypt"), n, r, p, @@ -1177,9 +1265,9 @@ def scrypt(out_len, password, salt, n=1024, r=8, p=8): # p specifies the parallelism # # returns an output of out_len bytes -def argon2(variant, out_len, password, salt, m=256, t=1, p=1): +def argon2(variant: str, out_len: int, password: str, salt: str | bytes, m: int = 256, t: int = 1, p: int = 1) -> bytes: out_buf = create_string_buffer(out_len) - passbits = _ctype_str(password) + passbits = _ctype_bits(password) saltbits = _ctype_bits(salt) _DLL.botan_pwdhash(_ctype_str(variant), m, t, p, @@ -1192,7 +1280,7 @@ def argon2(variant, out_len, password, salt, m=256, t=1, p=1): # # KDF # -def kdf(algo, secret, out_len, salt, label): +def kdf(algo: str, secret: bytes, out_len: int, salt: bytes, label: bytes) -> bytes: out_buf = create_string_buffer(out_len) out_sz = c_size_t(out_len) _DLL.botan_kdf(_ctype_str(algo), out_buf, out_sz, @@ -1205,20 +1293,20 @@ def kdf(algo, secret, out_len, salt, label): # Public key # class PublicKey: # pylint: disable=invalid-name - def __init__(self, obj=None): + def __init__(self, obj: c_void_p | None = None): if not obj: obj = c_void_p(0) self.__obj = obj @classmethod - def load(cls, val): + def load(cls, val: str | bytes) -> PublicKey: pub = PublicKey() bits = _ctype_bits(val) _DLL.botan_pubkey_load(byref(pub.handle_()), bits, len(bits)) return pub @classmethod - def load_rsa(cls, n, e): + def load_rsa(cls, n: _MPIArg, e: _MPIArg) -> PublicKey: pub = PublicKey() n = MPI(n) e = MPI(e) @@ -1226,7 +1314,7 @@ def load_rsa(cls, n, e): return pub @classmethod - def load_dsa(cls, p, q, g, y): + def load_dsa(cls, p: _MPIArg, q: _MPIArg, g: _MPIArg, y: _MPIArg) -> PublicKey: pub = PublicKey() p = MPI(p) q = MPI(q) @@ -1236,7 +1324,7 @@ def load_dsa(cls, p, q, g, y): return pub @classmethod - def load_dh(cls, p, g, y): + def load_dh(cls, p: _MPIArg, g: _MPIArg, y: _MPIArg) -> PublicKey: pub = PublicKey() p = MPI(p) g = MPI(g) @@ -1245,7 +1333,7 @@ def load_dh(cls, p, g, y): return pub @classmethod - def load_elgamal(cls, p, q, g, y): + def load_elgamal(cls, p: _MPIArg, q: _MPIArg, g: _MPIArg, y: _MPIArg) -> PublicKey: pub = PublicKey() p = MPI(p) q = MPI(q) @@ -1255,7 +1343,7 @@ def load_elgamal(cls, p, q, g, y): return pub @classmethod - def load_ecdsa(cls, curve, pub_x, pub_y): + def load_ecdsa(cls, curve: str, pub_x: _MPIArg, pub_y: _MPIArg) -> PublicKey: pub = PublicKey() pub_x = MPI(pub_x) pub_y = MPI(pub_y) @@ -1263,13 +1351,13 @@ def load_ecdsa(cls, curve, pub_x, pub_y): return pub @classmethod - def load_ecdsa_sec1(cls, curve, sec1_encoding): + def load_ecdsa_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_ecdsa_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) return pub @classmethod - def load_ecdh(cls, curve, pub_x, pub_y): + def load_ecdh(cls, curve: str, pub_x: _MPIArg, pub_y: _MPIArg) -> PublicKey: pub = PublicKey() pub_x = MPI(pub_x) pub_y = MPI(pub_y) @@ -1277,13 +1365,13 @@ def load_ecdh(cls, curve, pub_x, pub_y): return pub @classmethod - def load_ecdh_sec1(cls, curve, sec1_encoding): + def load_ecdh_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_ecdh_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) return pub @classmethod - def load_sm2(cls, curve, pub_x, pub_y): + def load_sm2(cls, curve: str, pub_x: _MPIArg, pub_y: _MPIArg) -> PublicKey: pub = PublicKey() pub_x = MPI(pub_x) pub_y = MPI(pub_y) @@ -1291,43 +1379,43 @@ def load_sm2(cls, curve, pub_x, pub_y): return pub @classmethod - def load_sm2_sec1(cls, curve, sec1_encoding): + def load_sm2_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_sm2_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) return pub @classmethod - def load_kyber(cls, key): + def load_kyber(cls, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_kyber(byref(pub.handle_()), key, len(key)) return pub @classmethod - def load_ml_kem(cls, mlkem_mode, key): + def load_ml_kem(cls, mlkem_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_ml_kem(byref(pub.handle_()), key, len(key), _ctype_str(mlkem_mode)) return pub @classmethod - def load_ml_dsa(cls, mldsa_mode, key): + def load_ml_dsa(cls, mldsa_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_ml_dsa(byref(pub.handle_()), key, len(key), _ctype_str(mldsa_mode)) return pub @classmethod - def load_slh_dsa(cls, slhdsa_mode, key): + def load_slh_dsa(cls, slhdsa_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_slh_dsa(byref(pub.handle_()), key, len(key), _ctype_str(slhdsa_mode)) return pub @classmethod - def load_frodokem(cls, frodo_mode, key): + def load_frodokem(cls, frodo_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_frodokem(byref(pub.handle_()), key, len(key), _ctype_str(frodo_mode)) return pub @classmethod - def load_classic_mceliece(cls, cmce_mode, key): + def load_classic_mceliece(cls, cmce_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_classic_mceliece(byref(pub.handle_()), key, len(key), _ctype_str(cmce_mode)) return pub @@ -1338,39 +1426,39 @@ def __del__(self): def handle_(self): return self.__obj - def check_key(self, rng_obj, strong=True): + def check_key(self, rng_obj: RandomNumberGenerator, strong: bool = True) -> bool: flags = 1 if strong else 0 rc = _DLL.botan_pubkey_check_key(self.__obj, rng_obj.handle_(), flags) return rc == 0 - def estimated_strength(self): + def estimated_strength(self) -> int: r = c_size_t(0) _DLL.botan_pubkey_estimated_strength(self.__obj, byref(r)) return r.value - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_pubkey_algo_name(self.__obj, b, bl)) - def export(self, pem=False): + def export(self, pem: bool = False) -> str | bytes: if pem: return self.to_pem() else: return self.to_der() - def to_der(self): + def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_der(self.__obj, vc, vfn)) - def to_pem(self): + def to_pem(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_pubkey_view_pem(self.__obj, vc, vfn)) - def to_raw(self): + def to_raw(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_raw(self.__obj, vc, vfn)) - def view_kyber_raw_key(self): + def view_kyber_raw_key(self) -> bytes: """Deprecated: use to_raw() instead""" return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_kyber_raw_key(self.__obj, vc, vfn)) - def fingerprint(self, hash_algorithm='SHA-256'): + def fingerprint(self, hash_algorithm: str = 'SHA-256') -> str: n = HashFunction(hash_algorithm).output_length() buf = create_string_buffer(n) buf_len = c_size_t(n) @@ -1378,30 +1466,30 @@ def fingerprint(self, hash_algorithm='SHA-256'): _DLL.botan_pubkey_fingerprint(self.__obj, _ctype_str(hash_algorithm), buf, byref(buf_len)) return _hex_encode(buf[0:int(buf_len.value)]) - def get_field(self, field_name): + def get_field(self, field_name: str) -> int: v = MPI() _DLL.botan_pubkey_get_field(v.handle_(), self.__obj, _ctype_str(field_name)) return int(v) - def object_identifier(self): + def object_identifier(self) -> OID: oid = OID() _DLL.botan_pubkey_oid(byref(oid.handle_()), self.__obj) return oid - def get_public_point(self): + def get_public_point(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_ec_public_point(self.__obj, vc, vfn)) # # Private Key # class PrivateKey: - def __init__(self, obj=None): + def __init__(self, obj: c_void_p | None = None): if not obj: obj = c_void_p(0) self.__obj = obj @classmethod - def load(cls, val, passphrase=""): + def load(cls, val: str | bytes, passphrase: str = "") -> PrivateKey: priv = PrivateKey() rng_obj = c_void_p(0) # unused in recent versions bits = _ctype_bits(val) @@ -1409,7 +1497,7 @@ def load(cls, val, passphrase=""): return priv @classmethod - def create(cls, algo, params, rng_obj): + def create(cls, algo: str, params: str | int | tuple[int, int], rng_obj: RandomNumberGenerator) -> PrivateKey: if algo == 'rsa': algo = 'RSA' params = "%d" % (params) @@ -1425,6 +1513,7 @@ def create(cls, algo, params, rng_obj): else: algo = 'ECDH' elif algo in ['mce', 'mceliece']: + # TODO(Botan4) remove this case algo = 'McEliece' params = "%d,%d" % (params[0], params[1]) @@ -1433,13 +1522,13 @@ def create(cls, algo, params, rng_obj): return priv @classmethod - def create_ec(cls, algo, ec_group, rng_obj): + def create_ec(cls, algo: str, ec_group: ECGroup, rng_obj: RandomNumberGenerator) -> PrivateKey: obj = c_void_p(0) _DLL.botan_ec_privkey_create(byref(obj), _ctype_str(algo), ec_group.handle_(), rng_obj.handle_()) return PrivateKey(obj) @classmethod - def load_rsa(cls, p, q, e): + def load_rsa(cls, p: _MPIArg, q: _MPIArg, e: _MPIArg) -> PrivateKey: priv = PrivateKey() p = MPI(p) q = MPI(q) @@ -1448,7 +1537,7 @@ def load_rsa(cls, p, q, e): return priv @classmethod - def load_dsa(cls, p, q, g, x): + def load_dsa(cls, p: _MPIArg, q: _MPIArg, g: _MPIArg, x: _MPIArg) -> PrivateKey: priv = PrivateKey() p = MPI(p) q = MPI(q) @@ -1458,7 +1547,7 @@ def load_dsa(cls, p, q, g, x): return priv @classmethod - def load_dh(cls, p, g, x): + def load_dh(cls, p: _MPIArg, g: _MPIArg, x: _MPIArg) -> PrivateKey: priv = PrivateKey() p = MPI(p) g = MPI(g) @@ -1467,7 +1556,7 @@ def load_dh(cls, p, g, x): return priv @classmethod - def load_elgamal(cls, p, q, g, x): + def load_elgamal(cls, p: _MPIArg, q: _MPIArg, g: _MPIArg, x: _MPIArg) -> PrivateKey: priv = PrivateKey() p = MPI(p) q = MPI(q) @@ -1477,34 +1566,34 @@ def load_elgamal(cls, p, q, g, x): return priv @classmethod - def load_ecdsa(cls, curve, x): + def load_ecdsa(cls, curve: str, x: _MPIArg) -> PrivateKey: priv = PrivateKey() x = MPI(x) _DLL.botan_privkey_load_ecdsa(byref(priv.handle_()), x.handle_(), _ctype_str(curve)) return priv @classmethod - def load_ecdh(cls, curve, x): + def load_ecdh(cls, curve: str, x: _MPIArg) -> PrivateKey: priv = PrivateKey() x = MPI(x) _DLL.botan_privkey_load_ecdh(byref(priv.handle_()), x.handle_(), _ctype_str(curve)) return priv @classmethod - def load_sm2(cls, curve, x): + def load_sm2(cls, curve: str, x: _MPIArg) -> PrivateKey: priv = PrivateKey() x = MPI(x) _DLL.botan_privkey_load_sm2(byref(priv.handle_()), x.handle_(), _ctype_str(curve)) return priv @classmethod - def load_kyber(cls, key): + def load_kyber(cls, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_kyber(byref(priv.handle_()), key, len(key)) return priv @classmethod - def load_ml_kem(cls, mlkem_mode, key): + def load_ml_kem(cls, mlkem_mode: str, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_ml_kem(byref(priv.handle_()), key, len(key), _ctype_str(mlkem_mode)) return priv @@ -1516,19 +1605,19 @@ def load_ml_dsa(cls, mldsa_mode, key): return priv @classmethod - def load_slh_dsa(cls, slh_dsa, key): + def load_slh_dsa(cls, slh_dsa: str, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_slh_dsa(byref(priv.handle_()), key, len(key), _ctype_str(slh_dsa)) return priv @classmethod - def load_frodokem(cls, frodo_mode, key): + def load_frodokem(cls, frodo_mode: str, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_frodokem(byref(priv.handle_()), key, len(key), _ctype_str(frodo_mode)) return priv @classmethod - def load_classic_mceliece(cls, cmce_mode, key): + def load_classic_mceliece(cls, cmce_mode: str, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_classic_mceliece(byref(priv.handle_()), key, len(key), _ctype_str(cmce_mode)) return priv @@ -1539,39 +1628,39 @@ def __del__(self): def handle_(self): return self.__obj - def check_key(self, rng_obj, strong=True): + def check_key(self, rng_obj: RandomNumberGenerator, strong: bool = True) -> bool: flags = 1 if strong else 0 rc = _DLL.botan_privkey_check_key(self.__obj, rng_obj.handle_(), flags) return rc == 0 - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_privkey_algo_name(self.__obj, b, bl)) - def get_public_key(self): + def get_public_key(self) -> PublicKey: pub = PublicKey() _DLL.botan_privkey_export_pubkey(byref(pub.handle_()), self.__obj) return pub - def to_der(self): + def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_der(self.__obj, vc, vfn)) - def to_pem(self): + def to_pem(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_privkey_view_pem(self.__obj, vc, vfn)) - def to_raw(self): + def to_raw(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_raw(self.__obj, vc, vfn)) - def view_kyber_raw_key(self): + def view_kyber_raw_key(self) -> bytes: """Deprecated: use to_raw() instead""" return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_kyber_raw_key(self.__obj, vc, vfn)) - def export(self, pem=False): + def export(self, pem: bool = False) -> str | bytes: if pem: return self.to_pem() else: return self.to_der() - def export_encrypted(self, passphrase, rng, pem=False, msec=300, cipher=None, pbkdf=None): # pylint: disable=redefined-outer-name + def export_encrypted(self, passphrase: str, rng: RandomNumberGenerator, pem: bool = False, msec: int = 300, cipher: str | None = None, pbkdf: str | None = None): # pylint: disable=redefined-outer-name if pem: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_privkey_view_encrypted_pem_timed( @@ -1583,30 +1672,30 @@ def export_encrypted(self, passphrase, rng, pem=False, msec=300, cipher=None, pb self.__obj, rng.handle_(), _ctype_str(passphrase), _ctype_str(cipher), _ctype_str(pbkdf), c_size_t(msec), vc, vfn)) - def get_field(self, field_name): + def get_field(self, field_name: str) -> int: v = MPI() _DLL.botan_privkey_get_field(v.handle_(), self.__obj, _ctype_str(field_name)) return int(v) - def object_identifier(self): + def object_identifier(self) -> OID: oid = OID() _DLL.botan_privkey_oid(byref(oid.handle_()), self.__obj) return oid - def stateful_operation(self): + def stateful_operation(self) -> bool: r = c_int(0) _DLL.botan_privkey_stateful_operation(self.__obj, byref(r)) if r.value == 0: return False return True - def remaining_operations(self): + def remaining_operations(self) -> int: r = c_uint64(0) _DLL.botan_privkey_remaining_operations(self.__obj, byref(r)) return r.value class PKEncrypt: - def __init__(self, key, padding): + def __init__(self, key: PublicKey, padding: str): self.__obj = c_void_p(0) flags = c_uint32(0) # always zero in this ABI _DLL.botan_pk_op_encrypt_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) @@ -1614,7 +1703,7 @@ def __init__(self, key, padding): def __del__(self): _DLL.botan_pk_op_encrypt_destroy(self.__obj) - def encrypt(self, msg, rng_obj): + def encrypt(self, msg: bytes, rng_obj: RandomNumberGenerator) -> bytes: outbuf_sz = c_size_t(0) _DLL.botan_pk_op_encrypt_output_length(self.__obj, len(msg), byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) @@ -1623,7 +1712,7 @@ def encrypt(self, msg, rng_obj): class PKDecrypt: - def __init__(self, key, padding): + def __init__(self, key: PrivateKey, padding: str): self.__obj = c_void_p(0) flags = c_uint32(0) # always zero in this ABI _DLL.botan_pk_op_decrypt_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) @@ -1631,7 +1720,7 @@ def __init__(self, key, padding): def __del__(self): _DLL.botan_pk_op_decrypt_destroy(self.__obj) - def decrypt(self, msg): + def decrypt(self, msg: bytes) -> bytes: outbuf_sz = c_size_t(0) _DLL.botan_pk_op_decrypt_output_length(self.__obj, len(msg), byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) @@ -1640,7 +1729,7 @@ def decrypt(self, msg): return outbuf.raw[0:int(outbuf_sz.value)] class PKSign: # pylint: disable=invalid-name - def __init__(self, key, padding, der=False): + def __init__(self, key: PrivateKey, padding: str, der: bool = False): self.__obj = c_void_p(0) flags = c_uint32(1) if der else c_uint32(0) _DLL.botan_pk_op_sign_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) @@ -1648,10 +1737,10 @@ def __init__(self, key, padding, der=False): def __del__(self): _DLL.botan_pk_op_sign_destroy(self.__obj) - def update(self, msg): + def update(self, msg: str | bytes): _DLL.botan_pk_op_sign_update(self.__obj, _ctype_bits(msg), len(msg)) - def finish(self, rng_obj): + def finish(self, rng_obj: RandomNumberGenerator) -> bytes: outbuf_sz = c_size_t(0) _DLL.botan_pk_op_sign_output_length(self.__obj, byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) @@ -1659,7 +1748,7 @@ def finish(self, rng_obj): return outbuf.raw[0:int(outbuf_sz.value)] class PKVerify: - def __init__(self, key, padding, der=False): + def __init__(self, key: PublicKey, padding: str, der: bool = False): self.__obj = c_void_p(0) flags = c_uint32(1) if der else c_uint32(0) _DLL.botan_pk_op_verify_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) @@ -1667,11 +1756,11 @@ def __init__(self, key, padding, der=False): def __del__(self): _DLL.botan_pk_op_verify_destroy(self.__obj) - def update(self, msg): + def update(self, msg: str | bytes): bits = _ctype_bits(msg) _DLL.botan_pk_op_verify_update(self.__obj, bits, len(bits)) - def check_signature(self, signature): + def check_signature(self, signature: str | bytes) -> bool: bits = _ctype_bits(signature) rc = _DLL.botan_pk_op_verify_finish(self.__obj, bits, len(bits)) if rc == 0: @@ -1679,7 +1768,7 @@ def check_signature(self, signature): return False class PKKeyAgreement: - def __init__(self, key, kdf_name): + def __init__(self, key: PrivateKey, kdf_name: str): self.__obj = c_void_p(0) flags = c_uint32(0) # always zero in this ABI _DLL.botan_pk_op_key_agreement_create(byref(self.__obj), key.handle_(), _ctype_str(kdf_name), flags) @@ -1690,15 +1779,15 @@ def __init__(self, key, kdf_name): def __del__(self): _DLL.botan_pk_op_key_agreement_destroy(self.__obj) - def public_value(self): + def public_value(self) -> bytes: return self.m_public_value - def underlying_output_length(self): + def underlying_output_length(self) -> int: out_len = c_size_t(0) _DLL.botan_pk_op_key_agreement_size(self.__obj, byref(out_len)) return out_len.value - def agree(self, other, key_len, salt): + def agree(self, other: bytes, key_len: int, salt: bytes) -> bytes: if key_len == 0: key_len = self.underlying_output_length() return _call_fn_returning_vec(key_len, lambda b, bl: @@ -1707,22 +1796,22 @@ def agree(self, other, key_len, salt): salt, len(salt))) class KemEncrypt: - def __init__(self, key, params): + def __init__(self, key: PublicKey, params: str): self.__obj = c_void_p(0) _DLL.botan_pk_op_kem_encrypt_create(byref(self.__obj), key.handle_(), _ctype_str(params)) def __del__(self): _DLL.botan_pk_op_kem_encrypt_destroy(self.__obj) - def shared_key_length(self, desired_key_len): + def shared_key_length(self, desired_key_len: int) -> int: return _call_fn_returning_sz( - lambda l: _DLL.botan_pk_op_kem_encrypt_shared_key_length(self.__obj, desired_key_len, l)) + lambda len: _DLL.botan_pk_op_kem_encrypt_shared_key_length(self.__obj, desired_key_len, len)) - def encapsulated_key_length(self): + def encapsulated_key_length(self) -> int: return _call_fn_returning_sz( - lambda l: _DLL.botan_pk_op_kem_encrypt_encapsulated_key_length(self.__obj, l)) + lambda len: _DLL.botan_pk_op_kem_encrypt_encapsulated_key_length(self.__obj, len)) - def create_shared_key(self, rng, salt, desired_key_len): + def create_shared_key(self, rng: RandomNumberGenerator, salt: bytes, desired_key_len: int) -> tuple[bytes, bytes]: shared_key_len = self.shared_key_length(desired_key_len) shared_key_buf = create_string_buffer(shared_key_len) @@ -1747,18 +1836,18 @@ def create_shared_key(self, rng, salt, desired_key_len): return (shared_key, encapsulated_key) class KemDecrypt: - def __init__(self, key, params): + def __init__(self, key: PrivateKey, params: str): self.__obj = c_void_p(0) _DLL.botan_pk_op_kem_decrypt_create(byref(self.__obj), key.handle_(), _ctype_str(params)) def __del__(self): _DLL.botan_pk_op_kem_decrypt_destroy(self.__obj) - def shared_key_length(self, desired_key_len): + def shared_key_length(self, desired_key_len: int) -> int: return _call_fn_returning_sz( - lambda l: _DLL.botan_pk_op_kem_decrypt_shared_key_length(self.__obj, desired_key_len, l)) + lambda len: _DLL.botan_pk_op_kem_decrypt_shared_key_length(self.__obj, desired_key_len, len)) - def decrypt_shared_key(self, salt, desired_key_len, encapsulated_key): + def decrypt_shared_key(self, salt: bytes, desired_key_len: int, encapsulated_key: bytes) -> bytes: shared_key_len = self.shared_key_length(desired_key_len) return _call_fn_returning_vec( @@ -1794,15 +1883,432 @@ def _load_buf_or_file(filename, buf, file_fn, buf_fn): # # X.509 certificates # -class X509Cert: # pylint: disable=invalid-name - def __init__(self, filename=None, buf=None): +class X509KeyConstraints(IntEnum): + NO_CONSTRAINTS = 0 + DIGITAL_SIGNATURE = 1 << 15 + NON_REPUDIATION = 1 << 14 + KEY_ENCIPHERMENT = 1 << 13 + DATA_ENCIPHERMENT = 1 << 12 + KEY_AGREEMENT = 1 << 11 + KEY_CERT_SIGN = 1 << 10 + CRL_SIGN = 1 << 9 + ENCIPHER_ONLY = 1 << 8 + DECIPHER_ONLY = 1 << 7 + + @classmethod + def to_bits(cls, constraints: List[X509KeyConstraints]) -> int: + con = 0 + for constraint in constraints: + con |= constraint.value + return con + + @classmethod + def from_bits(cls, bits: int) -> List[X509KeyConstraints]: + if bits == 0: + return X509KeyConstraints.NO_CONSTRAINTS + all_constraints = [ + X509KeyConstraints.DIGITAL_SIGNATURE, + X509KeyConstraints.NON_REPUDIATION, + X509KeyConstraints.KEY_ENCIPHERMENT, + X509KeyConstraints.DATA_ENCIPHERMENT, + X509KeyConstraints.KEY_AGREEMENT, + X509KeyConstraints.KEY_CERT_SIGN, + X509KeyConstraints.CRL_SIGN, + X509KeyConstraints.ENCIPHER_ONLY, + X509KeyConstraints.DECIPHER_ONLY, + ] + + constraints = [] + for constraint in all_constraints: + if bits & constraint.value != 0: + constraints.append(constraint) + return constraints + + # TODO deprecate this in a future version + @classmethod + def from_string(cls, constraint: str) -> X509KeyConstraints: + try: + if isinstance(constraint, X509KeyConstraints): + return constraint + return cls[constraint] + except KeyError as exc: + raise BotanException("Not a valid key constraint") from exc + + @classmethod + def to_string(cls, constraint: X509KeyConstraints) -> str: + return constraint.name + + +class X509CRLReason(IntEnum): + UNSPECIFIED = 0 + KEY_COMPROMISE = 1 + CA_COMPROMISE = 2 + AFFILIATION_CHANGED = 3 + SUPERSEDED = 4 + CESSATION_OF_OPERATION = 5 + CERTIFICATE_HOLD = 6 + REMOVE_FROM_CRL = 8 + PRIVILEGE_WITHDRAWN = 9 + AA_COMPROMISE = 10 + + @classmethod + def to_bits(cls, reason: X509CRLReason) -> int: + return reason.value + + @classmethod + def from_bits(cls, reason: int) -> X509CRLReason: + return cls(reason) + + +class X509CertificateBuilder: + def __init__(self): + self.__obj = c_void_p(0) + _DLL.botan_x509_cert_params_builder_create(byref(self.__obj)) + + def __del__(self): + _DLL.botan_x509_cert_params_builder_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def add_common_name(self, name: str): + _DLL.botan_x509_cert_params_builder_add_common_name(self.__obj, _ctype_str(name)) + + def add_country(self, country: str): + _DLL.botan_x509_cert_params_builder_add_country(self.__obj, _ctype_str(country)) + + def add_state(self, state: str): + _DLL.botan_x509_cert_params_builder_add_state(self.__obj, _ctype_str(state)) + + def add_locality(self, locality: str): + _DLL.botan_x509_cert_params_builder_add_locality(self.__obj, _ctype_str(locality)) + + def add_serial_number(self, serial_number: str): + _DLL.botan_x509_cert_params_builder_add_serial_number(self.__obj, _ctype_str(serial_number)) + + def add_organization(self, organization: str): + _DLL.botan_x509_cert_params_builder_add_organization(self.__obj, _ctype_str(organization)) + + def add_organizational_unit(self, org_unit: str): + _DLL.botan_x509_cert_params_builder_add_organizational_unit(self.__obj, _ctype_str(org_unit)) + + def add_email(self, email: str): + _DLL.botan_x509_cert_params_builder_add_email(self.__obj, _ctype_str(email)) + + def add_dns(self, dns: str): + _DLL.botan_x509_cert_params_builder_add_dns(self.__obj, _ctype_str(dns)) + + def add_uri(self, uri: str): + _DLL.botan_x509_cert_params_builder_add_uri(self.__obj, _ctype_str(uri)) + + def add_xmpp(self, xmpp: str): + _DLL.botan_x509_cert_params_builder_add_xmpp(self.__obj, _ctype_str(xmpp)) + + def add_ipv4(self, ipv4: int): + _DLL.botan_x509_cert_params_builder_add_ipv4(self.__obj, ipv4) + + def add_allowed_usage(self, usage_list: List[X509KeyConstraints]): + usage = X509KeyConstraints.to_bits(usage_list) + _DLL.botan_x509_cert_params_builder_add_allowed_usage(self.__obj, c_uint32(usage)) + + def add_allowed_extended_usage(self, oid: OID): + _DLL.botan_x509_cert_params_builder_add_allowed_extended_usage(self.__obj, oid.handle_()) + + def set_as_ca_certificate(self, limit: int | None = None): + _DLL.botan_x509_cert_params_builder_set_as_ca_certificate(self.__obj, c_size_t(limit) if limit is not None else None) + + def add_ext_ip_addr_blocks(self, ip_addr_blocks: X509ExtIPAddrBlocks, is_critical: bool): + _DLL.botan_x509_cert_params_builder_add_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_(), 1 if is_critical else 0) + + def add_ext_as_blocks(self, as_blocks: X509ExtASBlocks, is_critical: bool): + _DLL.botan_x509_cert_params_builder_add_ext_as_blocks(self.__obj, as_blocks.handle_(), 1 if is_critical else 0) + + def into_self_signed( + self, + key: PrivateKey, + rng: RandomNumberGenerator, + not_before: int, + not_after: int, + serial_number: MPI | None = None, + hash_fn: str | None = None, + padding: str | None = None + ) -> X509Cert: + cert = X509Cert() + serial_no = byref(serial_number.handle_()) if serial_number is not None else None + _DLL.botan_x509_cert_params_builder_into_self_signed( + byref(cert.handle_()), + key.handle_(), + self.__obj, + rng.handle_(), + not_before, + not_after, + serial_no, + _ctype_str(hash_fn), + _ctype_str(padding), + ) + return cert + + def into_request( + self, + key: PrivateKey, + rng: RandomNumberGenerator, + hash_fn: str | None = None, + padding: str | None = None, + challenge_password: str | None = None + ) -> PKCS10Req: + req = PKCS10Req() + _DLL.botan_x509_cert_params_builder_into_pkcs10_req( + byref(req.handle_()), + key.handle_(), + self.__obj, + rng.handle_(), + _ctype_str(hash_fn), + _ctype_str(padding), + _ctype_str(challenge_password) + ) + return req + + +class X509ExtIPAddrBlocks: + def __init__(self, cert: X509Cert | None = None): self.__obj = c_void_p(0) - self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_cert_load_file, _DLL.botan_x509_cert_load) + if cert: + _DLL.botan_x509_ext_ip_addr_blocks_create_from_cert(byref(self.__obj), cert.handle_()) + else: + _DLL.botan_x509_ext_ip_addr_blocks_create(byref(self.__obj)) + + def __del__(self): + _DLL.botan_x509_ext_ip_addr_blocks_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def add_addr(self, ip: List[int], safi: int | None = None): + self.add_range(ip, ip, safi) + + def add_range(self, min_: List[int], max_: List[int], safi: int | None = None): + min_len = len(min_) + if min_len not in (4, 16) or len(max_) != min_len: + raise BotanException("Address must be 4 or 16 bytes long") + + ipv6 = 1 if min_len == 16 else 0 + safi = byref(c_uint8(safi)) if safi is not None else None + _DLL.botan_x509_ext_ip_addr_blocks_add_ip_addr(self.__obj, bytes(min_), bytes(max_), c_int(ipv6), safi) + + def restrict(self, ipv6: bool, safi: int | None = None): + ipv6 = 1 if ipv6 else 0 + safi = byref(c_uint8(safi)) if safi is not None else None + _DLL.botan_x509_ext_ip_addr_blocks_restrict(self.__obj, c_int(ipv6), safi) + + def inherit(self, ipv6: bool, safi: int | None = None): + ipv6 = 1 if ipv6 else 0 + safi = byref(c_uint8(safi)) if safi is not None else None + _DLL.botan_x509_ext_ip_addr_blocks_inherit(self.__obj, c_int(ipv6), safi) + + def addresses(self) -> Tuple[ + List[Tuple[int | None, List[Tuple[List[int], List[int]]]]], + List[Tuple[int | None, List[Tuple[List[int], List[int]]]]] + ]: + v4 = [] + v6 = [] + + v4_count = c_size_t(0) + v6_count = c_size_t(0) + + _DLL.botan_x509_ext_ip_addr_blocks_get_counts(self.__obj, byref(v4_count), byref(v6_count)) + + v4_count = v4_count.value + v6_count = v6_count.value + + for (ipv6, start, stop) in ((0, 0, v4_count), (1, v4_count, v4_count + v6_count)): + for i in range(start, stop): + size = 16 if ipv6 else 4 + + has_safi = c_int(0) + safi = c_uint8(0) + present = c_int(0) + count = c_size_t(0) + _DLL.botan_x509_ext_ip_addr_blocks_get_family(self.__obj, c_int(ipv6), c_size_t(i), byref(has_safi), byref(safi), byref(present), byref(count)) + ranges = None + if present.value == 1: + ranges = [] + for entry in range(count.value): + min_, max_ = _call_fn_returning_vec_pair( + size, size, lambda mi, _, ma, l, ipv6=ipv6, i=i, entry=entry: _DLL.botan_x509_ext_ip_addr_blocks_get_address( + self.__obj, + c_int(ipv6), + c_size_t(i), + c_size_t(entry), + mi, + ma, + l)) + ranges.append((tuple(min_), tuple(max_))) + + safi = safi.value if has_safi.value == 1 else None + ranges = ranges if ranges is not None else None + if ipv6 == 0: + v4.append((safi, ranges)) + else: + v6.append((safi, ranges)) + + return (v4, v6) + + +class X509ExtASBlocks: + def __init__(self, cert: X509Cert | None = None): + self.__obj = c_void_p(0) + if cert: + _DLL.botan_x509_ext_as_blocks_create_from_cert(byref(self.__obj), cert.handle_()) + else: + _DLL.botan_x509_ext_as_blocks_create(byref(self.__obj)) + + def __del__(self): + _DLL.botan_x509_ext_as_blocks_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def add_asnum(self, asnum: int): + self.add_asnum_range(asnum, asnum) + + def add_asnum_range(self, min_: int, max_: int): + _DLL.botan_x509_ext_as_blocks_add_asnum(self.__obj, c_uint32(min_), c_uint32(max_)) + + def restrict_asnum(self): + _DLL.botan_x509_ext_as_blocks_restrict_asnum(self.__obj) + + def inherit_asnum(self): + _DLL.botan_x509_ext_as_blocks_inherit_asnum(self.__obj) + + def add_rdi(self, rdi: int): + self.add_rdi_range(rdi, rdi) + + def add_rdi_range(self, min_: int, max_: int): + _DLL.botan_x509_ext_as_blocks_add_rdi(self.__obj, c_uint32(min_), c_uint32(max_)) + + def restrict_rdi(self): + _DLL.botan_x509_ext_as_blocks_restrict_rdi(self.__obj) + + def inherit_rdi(self): + _DLL.botan_x509_ext_as_blocks_inherit_rdi(self.__obj) + + def asnum(self) -> List[Tuple[int, int]]: + present = c_int(0) + count = c_size_t(0) + _DLL.botan_x509_ext_as_blocks_get_asnum(self.__obj, byref(present), byref(count)) + + # asnum is 'inherit' + if present.value == 0: + return None + + asnums = [] + for i in range(count.value): + min_ = c_uint32(0) + max_ = c_uint32(0) + _DLL.botan_x509_ext_as_blocks_get_asnum_at(self.__obj, c_size_t(i), byref(min_), byref(max_)) + asnums.append((min_.value, max_.value)) + return asnums + + def rdi(self) -> List[Tuple[int, int]]: + present = c_int(0) + count = c_size_t(0) + _DLL.botan_x509_ext_as_blocks_get_rdi(self.__obj, byref(present), byref(count)) + + # rdi is 'inherit' + if present.value == 0: + return None + + rdis = [] + for i in range(count.value): + min_ = c_uint32(0) + max_ = c_uint32(0) + _DLL.botan_x509_ext_as_blocks_get_rdi_at(self.__obj, c_size_t(i), byref(min_), byref(max_)) + rdis.append((min_.value, max_.value)) + return rdis + + +class PKCS10Req: + def __init__(self, filename: str | None = None, buf: bytes | None = None): + if not filename and not buf: + self.__obj = c_void_p(0) + else: + self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_pkcs10_req_load_file, _DLL.botan_x509_pkcs10_req_load) + + def __del__(self): + _DLL.botan_x509_pkcs10_req_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def public_key(self) -> PublicKey: + pub = c_void_p(0) + _DLL.botan_x509_pkcs10_req_get_public_key(self.__obj, byref(pub)) + return PublicKey(pub) + + def key_constraints(self) -> List[X509KeyConstraints]: + usage = c_uint32(0) + _DLL.botan_x509_pkcs10_req_get_allowed_usage(self.__obj, byref(usage)) + return X509KeyConstraints.from_bits(usage.value) + + def is_ca(self) -> Tuple[bool, int]: + is_ca = c_int(0) + limit = c_size_t(0) + _DLL.botan_x509_pkcs10_req_is_ca(self.__obj, byref(is_ca), byref(limit)) + if is_ca.value == 0: + return (False, 0) + else: + return (True, limit.value) + + def verify(self, key: PublicKey) -> bool: + ret = c_int(0) + _DLL.botan_x509_pkcs10_req_verify_signature(self.__obj, key.handle_(), byref(ret)) + return ret.value == 1 + + def sign( + self, + issuing_cert: X509Cert, + issuing_key: PrivateKey, + rng: RandomNumberGenerator, + not_before: int, + not_after: int, + serial_number: MPI | None = None, + hash_fn: str | None = None, + padding: str | None = None + ) -> X509Cert: + cert = X509Cert() + serial_no = byref(serial_number.handle_()) if serial_number is not None else None + _DLL.botan_x509_pkcs10_req_sign( + byref(cert.handle_()), + self.__obj, + issuing_cert.handle_(), + issuing_key.handle_(), + rng.handle_(), + not_before, + not_after, + serial_no, + _ctype_str(hash_fn), + _ctype_str(padding) + ) + return cert + + def to_pem(self) -> str: + return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_x509_pkcs10_req_view_pem(self.__obj, vc, vfn)) + + def to_der(self) -> bytes: + return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_x509_pkcs10_req_view_der(self.__obj, vc, vfn)) + + +class X509Cert: # pylint: disable=invalid-name + def __init__(self, filename: str | None = None, buf: bytes | None = None): + if not filename and not buf: + self.__obj = c_void_p(0) + else: + self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_cert_load_file, _DLL.botan_x509_cert_load) def __del__(self): _DLL.botan_x509_cert_destroy(self.__obj) - def time_starts(self): + def time_starts(self) -> datetime: starts = _call_fn_returning_str( 16, lambda b, bl: _DLL.botan_x509_cert_get_time_starts(self.__obj, b, bl)) if len(starts) == 13: @@ -1816,7 +2322,7 @@ def time_starts(self): return datetime.fromtimestamp(mktime(struct_time)) - def time_expires(self): + def time_expires(self) -> datetime: expires = _call_fn_returning_str( 16, lambda b, bl: _DLL.botan_x509_cert_get_time_expires(self.__obj, b, bl)) if len(expires) == 13: @@ -1830,89 +2336,120 @@ def time_expires(self): return datetime.fromtimestamp(mktime(struct_time)) - def to_string(self): + def to_string(self) -> str: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_x509_cert_view_as_string(self.__obj, vc, vfn)) - def fingerprint(self, hash_algo='SHA-256'): + def to_pem(self) -> str: + return _call_fn_viewing_str( + lambda vc, vfn: _DLL.botan_x509_cert_view_pem(self.__obj, vc, vfn)) + + def fingerprint(self, hash_algo: str = 'SHA-256') -> str: n = HashFunction(hash_algo).output_length() * 3 return _call_fn_returning_str( n, lambda b, bl: _DLL.botan_x509_cert_get_fingerprint(self.__obj, _ctype_str(hash_algo), b, bl)) - def serial_number(self): + def serial_number(self) -> bytes: return _call_fn_returning_vec( 32, lambda b, bl: _DLL.botan_x509_cert_get_serial_number(self.__obj, b, bl)) - def authority_key_id(self): + def authority_key_id(self) -> bytes: return _call_fn_returning_vec( 32, lambda b, bl: _DLL.botan_x509_cert_get_authority_key_id(self.__obj, b, bl)) - def subject_key_id(self): + def subject_key_id(self) -> bytes: return _call_fn_returning_vec( 32, lambda b, bl: _DLL.botan_x509_cert_get_subject_key_id(self.__obj, b, bl)) - def subject_public_key_bits(self): + def subject_public_key_bits(self) -> bytes: return _call_fn_viewing_vec( lambda vc, vfn: _DLL.botan_x509_cert_view_public_key_bits(self.__obj, vc, vfn)) - def subject_public_key(self): + def subject_public_key(self) -> PublicKey: pub = c_void_p(0) _DLL.botan_x509_cert_get_public_key(self.__obj, byref(pub)) return PublicKey(pub) - def subject_dn(self, key, index): + def subject_dn(self, key: str, index: int) -> str: return _call_fn_returning_str( 0, lambda b, bl: _DLL.botan_x509_cert_get_subject_dn(self.__obj, _ctype_str(key), index, b, bl)) - def issuer_dn(self, key, index): + def subject_name(self) -> str: + return _call_fn_viewing_str( + lambda vc, vfn: _DLL.botan_x509_cert_get_subject_name(self.__obj, vc, vfn) + ) + + def issuer_dn(self, key: str, index: int) -> str: return _call_fn_returning_str( 0, lambda b, bl: _DLL.botan_x509_cert_get_issuer_dn(self.__obj, _ctype_str(key), index, b, bl)) - def hostname_match(self, hostname): + def issuer_name(self) -> str: + return _call_fn_viewing_str( + lambda vc, vfn: _DLL.botan_x509_cert_get_issuer_name(self.__obj, vc, vfn) + ) + + def hostname_match(self, hostname: str) -> bool: rc = _DLL.botan_x509_cert_hostname_match(self.__obj, _ctype_str(hostname)) return rc == 0 - def not_before(self): + def not_before(self) -> int: time = c_uint64(0) _DLL.botan_x509_cert_not_before(self.__obj, byref(time)) return time.value - def not_after(self): + def not_after(self) -> int: time = c_uint64(0) _DLL.botan_x509_cert_not_after(self.__obj, byref(time)) return time.value - def allowed_usage(self, usage_list): - usage_values = {"NO_CONSTRAINTS": 0, - "DIGITAL_SIGNATURE": 32768, - "NON_REPUDIATION": 16384, - "KEY_ENCIPHERMENT": 8192, - "DATA_ENCIPHERMENT": 4096, - "KEY_AGREEMENT": 2048, - "KEY_CERT_SIGN": 1024, - "CRL_SIGN": 512, - "ENCIPHER_ONLY": 256, - "DECIPHER_ONLY": 128} - usage = 0 - for u in usage_list: - if u not in usage_values: - return False - usage += usage_values[u] - + def allowed_usage(self, usage_list: List[str]) -> bool: + usage = X509KeyConstraints.to_bits([X509KeyConstraints.from_string(x) for x in usage_list]) rc = _DLL.botan_x509_cert_allowed_usage(self.__obj, c_uint(usage)) return rc == 0 + def allowed_usages(self) -> List[X509KeyConstraints]: + usage = c_uint32(0) + _DLL.botan_x509_cert_get_allowed_usage(self.__obj, byref(usage)) + return X509KeyConstraints.from_bits(usage.value) + + def is_ca(self) -> Tuple[bool, int]: + is_ca = c_int(0) + limit = c_size_t(0) + _DLL.botan_x509_cert_is_ca(self.__obj, byref(is_ca), byref(limit)) + if is_ca.value == 0: + return (False, 0) + else: + return (True, limit.value) + + def ocsp_responder(self) -> str: + return _call_fn_viewing_str( + lambda vc, vfn: _DLL.botan_x509_cert_get_ocsp_responder(self.__obj, vc, vfn)) + + def is_self_signed(self) -> bool: + self_signed = c_int(0) + _DLL.botan_x509_cert_is_self_signed(self.__obj, byref(self_signed)) + if self_signed.value == 0: + return False + else: + return True + + def ext_ip_addr_blocks(self) -> X509ExtIPAddrBlocks: + return X509ExtIPAddrBlocks(self) + + def ext_as_blocks(self) -> X509ExtASBlocks: + return X509ExtASBlocks(self) + def handle_(self): return self.__obj def verify(self, - intermediates=None, - trusted=None, - trusted_path=None, - required_strength=0, - hostname=None, - reference_time=0, - crls=None): + intermediates: List[X509Cert] | None = None, + trusted: List[X509Cert] | None = None, + trusted_path: str | None = None, + required_strength: int = 0, + hostname: str | None = None, + reference_time: int = 0, + crls: List[X509CRL] | None = None) -> int: if intermediates is not None: c_intermediates = len(intermediates) * c_void_p @@ -1962,10 +2499,10 @@ def verify(self, return error_code.value @classmethod - def validation_status(cls, error_code): + def validation_status(cls, error_code: int) -> str: return _ctype_to_str(_DLL.botan_x509_cert_validation_status(c_int(error_code))) - def is_revoked(self, crl): + def is_revoked(self, crl: X509CRL) -> bool: rc = _DLL.botan_x509_is_revoked(crl.handle_(), self.__obj) return rc == 0 @@ -1973,10 +2510,19 @@ def is_revoked(self, crl): # # X.509 Certificate revocation lists # +class X509CRLEntry: + def __init__(self, serial_number: MPI, expire_time: int, reason: X509CRLReason): + self.serial_number = serial_number + self.expire_time = expire_time + self.reason = reason + + class X509CRL: - def __init__(self, filename=None, buf=None): - self.__obj = c_void_p(0) - self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_crl_load_file, _DLL.botan_x509_crl_load) + def __init__(self, filename: str | None = None, buf: bytes | None = None): + if not filename and not buf: + self.__obj = c_void_p(0) + else: + self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_crl_load_file, _DLL.botan_x509_crl_load) def __del__(self): _DLL.botan_x509_crl_destroy(self.__obj) @@ -1984,10 +2530,92 @@ def __del__(self): def handle_(self): return self.__obj + @classmethod + def create( + cls, + rng: RandomNumberGenerator, + ca_cert: X509Cert, + ca_key: PrivateKey, + issue_time: int, + next_update: int, + hash_fn: str | None = None, + padding: str | None = None + ) -> X509CRL: + crl = X509CRL() + _DLL.botan_x509_crl_create( + byref(crl.handle_()), + rng.handle_(), + ca_cert.handle_(), + ca_key.handle_(), + issue_time, + next_update, + _ctype_str(hash_fn), + _ctype_str(padding) + ) + return crl + + def revoke( + self, + rng: RandomNumberGenerator, + ca_cert: X509Cert, + ca_key: PrivateKey, + issue_time: int, + next_update: int, + revoked: List[X509Cert], + reason: X509CRLReason, + hash_fn: str | None = None, + padding: str | None = None + ) -> X509CRL: + crl = X509CRL() + c_revoked = len(revoked) * c_void_p + arr_revoked = c_revoked() + for i, cert in enumerate(revoked): + arr_revoked[i] = cert.handle_() + revoked_len = c_size_t(len(revoked)) + + _DLL.botan_x509_crl_update( + byref(crl.handle_()), + self.__obj, + rng.handle_(), + ca_cert.handle_(), + ca_key.handle_(), + issue_time, + next_update, + arr_revoked, + revoked_len, + X509CRLReason.to_bits(reason), + _ctype_str(hash_fn), + _ctype_str(padding) + ) + return crl + + def revoked(self) -> List[X509CRLEntry]: + count = c_size_t(0) + _DLL.botan_x509_crl_get_count(self.__obj, byref(count)) + revoked = [] + for i in range(count.value): + expire_time = c_uint64(0) + reason = c_uint8(0) + serial = MPI() + _DLL.botan_x509_crl_get_entry(self.__obj, c_size_t(i), serial.handle_(), byref(expire_time), byref(reason)) + revoked.append(X509CRLEntry(serial, expire_time.value, X509CRLReason.from_bits(reason.value))) + return revoked + + def verify(self, key: PublicKey) -> bool: + ret = c_int(0) + _DLL.botan_x509_crl_verify_signature(self.__obj, key.handle_(), byref(ret)) + return ret.value == 1 + + def to_pem(self) -> str: + return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_x509_crl_view_pem(self.__obj, vc, vfn)) + + def to_der(self) -> bytes: + return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_x509_crl_view_der(self.__obj, vc, vfn)) + class MPI: - def __init__(self, initial_value=None, radix=None): + def __init__(self, initial_value: _MPIArg = None, radix: int | None = None): self.__obj = c_void_p(0) _DLL.botan_mp_init(byref(self.__obj)) @@ -2005,17 +2633,24 @@ def __init__(self, initial_value=None, radix=None): _DLL.botan_mp_set_from_str(self.__obj, _ctype_str(str(initial_value))) @classmethod - def random(cls, rng_obj, bits): + def random(cls, rng_obj: RandomNumberGenerator, bits: int) -> MPI: bn = MPI() _DLL.botan_mp_rand_bits(bn.handle_(), rng_obj.handle_(), c_size_t(bits)) return bn @classmethod - def random_range(cls, rng_obj, lower, upper): + def random_range(cls, rng_obj: RandomNumberGenerator, lower: MPI, upper: MPI): bn = MPI() _DLL.botan_mp_rand_range(bn.handle_(), rng_obj.handle_(), lower.handle_(), upper.handle_()) return bn + @classmethod + def from_bytes(cls, buf: bytes) -> MPI: + bn = MPI() + out_len = c_size_t(len(buf)) + _DLL.botan_mp_from_bin(bn.handle_(), buf, out_len) + return bn + def __del__(self): _DLL.botan_mp_destroy(self.__obj) @@ -2039,7 +2674,7 @@ def __repr__(self): out = out.raw[0:int(out_len.value - 1)] return _ctype_to_str(out) - def to_bytes(self): + def to_bytes(self) -> Array[c_char]: byte_count = self.byte_count() out_len = c_size_t(byte_count) out = create_string_buffer(out_len.value) @@ -2047,28 +2682,28 @@ def to_bytes(self): assert out_len.value == byte_count return out - def is_negative(self): + def is_negative(self) -> bool: rc = _DLL.botan_mp_is_negative(self.__obj) return rc == 1 - def is_positive(self): + def is_positive(self) -> bool: rc = _DLL.botan_mp_is_positive(self.__obj) return rc == 1 - def is_zero(self): + def is_zero(self) -> bool: rc = _DLL.botan_mp_is_zero(self.__obj) return rc == 1 - def is_odd(self): + def is_odd(self) -> bool: return self.get_bit(0) == 1 - def is_even(self): + def is_even(self) -> bool: return self.get_bit(0) == 0 def flip_sign(self): _DLL.botan_mp_flip_sign(self.__obj) - def cmp(self, other): + def cmp(self, other: MPI) -> int: r = c_int(0) _DLL.botan_mp_cmp(byref(r), self.__obj, other.handle_()) return r.value @@ -2076,130 +2711,144 @@ def cmp(self, other): def __hash__(self): return hash(self.to_bytes()) - def __eq__(self, other): - return self.cmp(other) == 0 + def __eq__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) == 0 + else: + return False - def __ne__(self, other): - return self.cmp(other) != 0 + def __ne__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) != 0 + else: + return False - def __lt__(self, other): - return self.cmp(other) < 0 + def __lt__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) < 0 + else: + return False - def __le__(self, other): - return self.cmp(other) <= 0 + def __le__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) <= 0 + else: + return False - def __gt__(self, other): - return self.cmp(other) > 0 + def __gt__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) > 0 + else: + return False - def __ge__(self, other): - return self.cmp(other) >= 0 + def __ge__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) >= 0 + else: + return False - def __add__(self, other): + def __add__(self, other: MPI): r = MPI() _DLL.botan_mp_add(r.handle_(), self.__obj, other.handle_()) return r - def __iadd__(self, other): + def __iadd__(self, other: MPI): _DLL.botan_mp_add(self.__obj, self.__obj, other.handle_()) return self - def __sub__(self, other): + def __sub__(self, other: MPI): r = MPI() _DLL.botan_mp_sub(r.handle_(), self.__obj, other.handle_()) return r - def __isub__(self, other): + def __isub__(self, other: MPI): _DLL.botan_mp_sub(self.__obj, self.__obj, other.handle_()) return self - def __mul__(self, other): + def __mul__(self, other: MPI): r = MPI() _DLL.botan_mp_mul(r.handle_(), self.__obj, other.handle_()) return r - def __imul__(self, other): + def __imul__(self, other: MPI): _DLL.botan_mp_mul(self.__obj, self.__obj, other.handle_()) return self - def __divmod__(self, other): + def __divmod__(self, other: MPI): d = MPI() q = MPI() _DLL.botan_mp_div(d.handle_(), q.handle_(), self.__obj, other.handle_()) return (d, q) - def __mod__(self, other): + def __mod__(self, other: MPI): d = MPI() q = MPI() _DLL.botan_mp_div(d.handle_(), q.handle_(), self.__obj, other.handle_()) return q - def __lshift__(self, shift): - shift = c_size_t(shift) + def __lshift__(self, shift: int): r = MPI() - _DLL.botan_mp_lshift(r.handle_(), self.__obj, shift) + _DLL.botan_mp_lshift(r.handle_(), self.__obj, c_size_t(shift)) return r - def __ilshift__(self, shift): - shift = c_size_t(shift) - _DLL.botan_mp_lshift(self.__obj, self.__obj, shift) + def __ilshift__(self, shift: int): + _DLL.botan_mp_lshift(self.__obj, self.__obj, c_size_t(shift)) return self - def __rshift__(self, shift): - shift = c_size_t(shift) + def __rshift__(self, shift: int): r = MPI() - _DLL.botan_mp_rshift(r.handle_(), self.__obj, shift) + _DLL.botan_mp_rshift(r.handle_(), self.__obj, c_size_t(shift)) return r - def __irshift__(self, shift): - shift = c_size_t(shift) - _DLL.botan_mp_rshift(self.__obj, self.__obj, shift) + def __irshift__(self, shift: int): + _DLL.botan_mp_rshift(self.__obj, self.__obj, c_size_t(shift)) return self - def mod_mul(self, other, modulus): + def mod_mul(self, other: MPI, modulus: MPI) -> MPI: r = MPI() _DLL.botan_mp_mod_mul(r.handle_(), self.__obj, other.handle_(), modulus.handle_()) return r - def gcd(self, other): + def gcd(self, other: MPI) -> MPI: r = MPI() _DLL.botan_mp_gcd(r.handle_(), self.__obj, other.handle_()) return r - def pow_mod(self, exponent, modulus): + def pow_mod(self, exponent: MPI, modulus: MPI) -> MPI: r = MPI() _DLL.botan_mp_powmod(r.handle_(), self.__obj, exponent.handle_(), modulus.handle_()) return r - def is_prime(self, rng_obj, prob=128): + def is_prime(self, rng_obj: RandomNumberGenerator, prob: int = 128) -> bool: return _DLL.botan_mp_is_prime(self.__obj, rng_obj.handle_(), c_size_t(prob)) == 1 - def inverse_mod(self, modulus): + def inverse_mod(self, modulus: MPI) -> MPI: r = MPI() _DLL.botan_mp_mod_inverse(r.handle_(), self.__obj, modulus.handle_()) return r - def bit_count(self): + def bit_count(self) -> int: b = c_size_t(0) _DLL.botan_mp_num_bits(self.__obj, byref(b)) return b.value - def byte_count(self): + def byte_count(self) -> int: b = c_size_t(0) _DLL.botan_mp_num_bytes(self.__obj, byref(b)) return b.value - def get_bit(self, bit): + def get_bit(self, bit: int) -> bool: return _DLL.botan_mp_get_bit(self.__obj, c_size_t(bit)) == 1 - def clear_bit(self, bit): + def clear_bit(self, bit: int): _DLL.botan_mp_clear_bit(self.__obj, c_size_t(bit)) - def set_bit(self, bit): + def set_bit(self, bit: int): _DLL.botan_mp_set_bit(self.__obj, c_size_t(bit)) class OID: - def __init__(self, obj=None): + def __init__(self, obj: c_void_p | None = None): if not obj: obj = c_void_p(0) self.__obj = obj @@ -2211,46 +2860,64 @@ def handle_(self): return self.__obj @classmethod - def from_string(cls, value): + def from_string(cls, value: str) -> OID: oid = OID() _DLL.botan_oid_from_string(byref(oid.handle_()), _ctype_str(value)) return oid - def to_string(self): + def to_string(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_oid_view_string(self.__obj, vc, vfn)) - def to_name(self): + def to_name(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_oid_view_name(self.__obj, vc, vfn)) - def register(self, name): + def register(self, name: str): _DLL.botan_oid_register(self.__obj, _ctype_str(name)) - def cmp(self, other): + def cmp(self, other: OID) -> int: r = c_int(0) _DLL.botan_oid_cmp(byref(r), self.__obj, other.handle_()) return r.value - def __eq__(self, other): - return self.cmp(other) == 0 + def __eq__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) == 0 + else: + return False - def __ne__(self, other): - return self.cmp(other) != 0 + def __ne__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) != 0 + else: + return False - def __lt__(self, other): - return self.cmp(other) < 0 + def __lt__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) < 0 + else: + return False - def __le__(self, other): - return self.cmp(other) <= 0 + def __le__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) <= 0 + else: + return False - def __gt__(self, other): - return self.cmp(other) > 0 + def __gt__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) > 0 + else: + return False - def __ge__(self, other): - return self.cmp(other) >= 0 + def __ge__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) >= 0 + else: + return False class ECGroup: - def __init__(self, obj=None): + def __init__(self, obj: c_void_p | None = None): if not obj: obj = c_void_p(0) self.__obj = obj @@ -2262,7 +2929,7 @@ def __del__(self): _DLL.botan_ec_group_destroy(self.__obj) @classmethod - def supports_application_specific_group(cls): + def supports_application_specific_group(cls) -> bool: r = c_int(0) _DLL.botan_ec_group_supports_application_specific_group(byref(r)) if r.value == 0: @@ -2270,7 +2937,7 @@ def supports_application_specific_group(cls): return True @classmethod - def supports_named_group(cls, name): + def supports_named_group(cls, name: str) -> bool: r = c_int(0) _DLL.botan_ec_group_supports_named_group(_ctype_str(name), byref(r)) if r.value == 0: @@ -2278,7 +2945,7 @@ def supports_named_group(cls, name): return True @classmethod - def from_params(cls, oid, p, a, b, base_x, base_y, order): + def from_params(cls, oid: OID, p: MPI, a: MPI, b: MPI, base_x: MPI, base_y: MPI, order: MPI) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_params( byref(ec_group.handle_()), @@ -2293,81 +2960,83 @@ def from_params(cls, oid, p, a, b, base_x, base_y, order): return ec_group @classmethod - def from_ber(cls, ber): + def from_ber(cls, ber: bytes) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_ber(byref(ec_group.handle_()), ber, len(ber)) return ec_group @classmethod - def from_pem(cls, pem): + def from_pem(cls, pem: str) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_pem(byref(ec_group.handle_()), _ctype_str(pem)) return ec_group @classmethod - def from_oid(cls, oid): + def from_oid(cls, oid: OID) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_oid(byref(ec_group.handle_()), oid.handle_()) return ec_group @classmethod - def from_name(cls, name): + def from_name(cls, name: str) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_name(byref(ec_group.handle_()), _ctype_str(name)) return ec_group - def to_der(self): + def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_group_view_der(self.__obj, vc, vfn)) - def to_pem(self): + def to_pem(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_ec_group_view_pem(self.__obj, vc, vfn)) - def get_curve_oid(self): + def get_curve_oid(self) -> OID: oid = OID() _DLL.botan_ec_group_get_curve_oid(byref(oid.handle_()), self.__obj) return oid - def get_p(self): + def get_p(self) -> MPI: p = MPI() _DLL.botan_ec_group_get_p(byref(p.handle_()), self.__obj) return p - def get_a(self): + def get_a(self) -> MPI: a = MPI() _DLL.botan_ec_group_get_a(byref(a.handle_()), self.__obj) return a - def get_b(self): + def get_b(self) -> MPI: b = MPI() _DLL.botan_ec_group_get_b(byref(b.handle_()), self.__obj) return b - def get_g_x(self): + def get_g_x(self) -> MPI: g_x = MPI() _DLL.botan_ec_group_get_g_x(byref(g_x.handle_()), self.__obj) return g_x - def get_g_y(self): + def get_g_y(self) -> MPI: g_y = MPI() _DLL.botan_ec_group_get_g_y(byref(g_y.handle_()), self.__obj) return g_y - def get_order(self): + def get_order(self) -> MPI: order = MPI() _DLL.botan_ec_group_get_order(byref(order.handle_()), self.__obj) return order - def __eq__(self, other): - rc = _DLL.botan_ec_group_equal(self.__obj, other.handle_()) - return rc == 1 + def __eq__(self, other: ECGroup | object) -> bool: + if isinstance(other, ECGroup): + return _DLL.botan_ec_group_equal(self.__obj, other.handle_()) == 1 + else: + return False - def __ne__(self, other): + def __ne__(self, other: ECGroup | object) -> bool: return not self == other class FormatPreservingEncryptionFE1: - def __init__(self, modulus, key, rounds=5, compat_mode=False): + def __init__(self, modulus: MPI, key: bytes, rounds: int = 5, compat_mode: bool = False): flags = c_uint32(1 if compat_mode else 0) self.__obj = c_void_p(0) _DLL.botan_fpe_fe1_init(byref(self.__obj), modulus.handle_(), key, len(key), c_size_t(rounds), flags) @@ -2375,32 +3044,32 @@ def __init__(self, modulus, key, rounds=5, compat_mode=False): def __del__(self): _DLL.botan_fpe_destroy(self.__obj) - def encrypt(self, msg, tweak): + def encrypt(self, msg: _MPIArg, tweak: str | bytes) -> MPI: r = MPI(msg) bits = _ctype_bits(tweak) _DLL.botan_fpe_encrypt(self.__obj, r.handle_(), bits, len(bits)) return r - def decrypt(self, msg, tweak): + def decrypt(self, msg: _MPIArg, tweak: str | bytes) -> MPI: r = MPI(msg) bits = _ctype_bits(tweak) _DLL.botan_fpe_decrypt(self.__obj, r.handle_(), bits, len(bits)) return r class HOTP: - def __init__(self, key, digest="SHA-1", digits=6): + def __init__(self, key: bytes, digest: str = "SHA-1", digits: int = 6): self.__obj = c_void_p(0) _DLL.botan_hotp_init(byref(self.__obj), key, len(key), _ctype_str(digest), digits) def __del__(self): _DLL.botan_hotp_destroy(self.__obj) - def generate(self, counter): + def generate(self, counter: int) -> int: code = c_uint32(0) _DLL.botan_hotp_generate(self.__obj, byref(code), counter) return code.value - def check(self, code, counter, resync_range=0): + def check(self, code: int, counter: int, resync_range: int = 0) -> tuple[bool, int]: next_ctr = c_uint64(0) rc = _DLL.botan_hotp_check(self.__obj, byref(next_ctr), code, counter, resync_range) if rc == 0: @@ -2409,21 +3078,21 @@ def check(self, code, counter, resync_range=0): return (False, counter) class TOTP: - def __init__(self, key, digest="SHA-1", digits=6, timestep=30): + def __init__(self, key: bytes, digest: str = "SHA-1", digits: int = 6, timestep: int = 30): self.__obj = c_void_p(0) _DLL.botan_totp_init(byref(self.__obj), key, len(key), _ctype_str(digest), digits, timestep) def __del__(self): _DLL.botan_totp_destroy(self.__obj) - def generate(self, timestamp=None): + def generate(self, timestamp: int | None = None) -> int: if timestamp is None: timestamp = int(system_time()) code = c_uint32(0) _DLL.botan_totp_generate(self.__obj, byref(code), timestamp) return code.value - def check(self, code, timestamp=None, acceptable_drift=0): + def check(self, code: int, timestamp: int | None = None, acceptable_drift: int = 0) -> bool: if timestamp is None: timestamp = int(system_time()) rc = _DLL.botan_totp_check(self.__obj, code, timestamp, acceptable_drift) @@ -2431,7 +3100,7 @@ def check(self, code, timestamp=None, acceptable_drift=0): return True return False -def nist_key_wrap(kek, key, cipher=None): +def nist_key_wrap(kek: bytes, key: bytes, cipher: str | None = None) -> bytes: cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher padding = 0 output = create_string_buffer(len(key) + 8) @@ -2440,9 +3109,9 @@ def nist_key_wrap(kek, key, cipher=None): key, len(key), kek, len(kek), output, byref(out_len)) - return output[0:int(out_len.value)] + return bytes(output[0:int(out_len.value)]) -def nist_key_unwrap(kek, wrapped, cipher=None): +def nist_key_unwrap(kek: bytes, wrapped: bytes, cipher: str | None = None) -> bytes: cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher padding = 0 output = create_string_buffer(len(wrapped)) @@ -2451,21 +3120,21 @@ def nist_key_unwrap(kek, wrapped, cipher=None): wrapped, len(wrapped), kek, len(kek), output, byref(out_len)) - return output[0:int(out_len.value)] + return bytes(output[0:int(out_len.value)]) class Srp6ServerSession: __obj = c_void_p(0) - def __init__(self, group): + def __init__(self, group: str): _DLL.botan_srp6_server_session_init(byref(self.__obj)) self.__group = group self.__group_size = _call_fn_returning_sz( - lambda l: _DLL.botan_srp6_group_size(_ctype_str(group), l)) + lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len)) def __del__(self): _DLL.botan_srp6_server_session_destroy(self.__obj) - def step1(self, verifier, hsh, rng): + def step1(self, verifier: bytes, hsh: str, rng: RandomNumberGenerator) -> bytes: return _call_fn_returning_vec(self.__group_size, lambda b, bl: _DLL.botan_srp6_server_session_step1(self.__obj, @@ -2475,14 +3144,14 @@ def step1(self, verifier, hsh, rng): rng.handle_(), b, bl)) - def step2(self, a): + def step2(self, a: bytes): return _call_fn_returning_vec(self.__group_size, lambda k, kl: _DLL.botan_srp6_server_session_step2(self.__obj, a, len(a), k, kl)) -def srp6_generate_verifier(identifier, password, salt, group, hsh): - sz = _call_fn_returning_sz(lambda l: _DLL.botan_srp6_group_size(_ctype_str(group), l)) +def srp6_generate_verifier(identifier: str, password: str, salt: bytes, group: str, hsh: str) -> bytes: + sz = _call_fn_returning_sz(lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len)) return _call_fn_returning_vec(sz, lambda v, vl: _DLL.botan_srp6_generate_verifier(_ctype_str(identifier), @@ -2492,8 +3161,8 @@ def srp6_generate_verifier(identifier, password, salt, group, hsh): _ctype_str(hsh), v, vl)) -def srp6_client_agree(username, password, group, hsh, salt, b, rng): - sz = _call_fn_returning_sz(lambda l: _DLL.botan_srp6_group_size(_ctype_str(group), l)) +def srp6_client_agree(username: str, password: str, group: str, hsh: str, salt: bytes, b: bytes, rng: RandomNumberGenerator) -> tuple[bytes, bytes]: + sz = _call_fn_returning_sz(lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len)) return _call_fn_returning_vec_pair(sz, sz, lambda a, al, k, kl: _DLL.botan_srp6_client_agree(_ctype_str(username), @@ -2506,7 +3175,7 @@ def srp6_client_agree(username, password, group, hsh, salt, b, rng): a, al, k, kl)) -def zfec_encode(k, n, input_bytes): +def zfec_encode(k: int, n: int, input_bytes: bytes) -> List[bytes]: """ ZFEC-encode an input message according to the given parameters @@ -2539,7 +3208,7 @@ def zfec_encode(k, n, input_bytes): return [output.raw for output in outputs] -def zfec_decode(k, n, indexes, inputs): +def zfec_decode(k: int, n: int, indexes: List[int], inputs: List[bytes]) -> List[bytes]: """ ZFEC decode diff --git a/src/scripts/bench.py b/src/scripts/bench.py index 250f0ec83be..c461b6a8244 100755 --- a/src/scripts/bench.py +++ b/src/scripts/bench.py @@ -120,18 +120,18 @@ def run_openssl_bench(openssl, algo): result = {} - for l in output.splitlines(): - if ignored.match(l): + for line in output.splitlines(): + if ignored.match(line): continue if not result: - match = buf_header.match(l) + match = buf_header.match(line) if match is None: - logging.error("Unexpected output from OpenSSL %s", l) + logging.error("Unexpected output from OpenSSL %s", line) result = {'algo': algo, 'buf_size': int(match.group(3))} else: - match = res_header.match(l) + match = res_header.match(line) result['bytes'] = int(match.group(1)) * result['buf_size'] result['runtime'] = float(match.group(2)) @@ -145,18 +145,18 @@ def run_openssl_bench(openssl, algo): result = {} - for l in output.splitlines(): - if ignored.match(l): + for line in output.splitlines(): + if ignored.match(line): continue - if match := signature_ops.match(l): + if match := signature_ops.match(line): results.append({ 'algo': algo, 'key_size': int(match.group(3)), 'op': 'sign', 'ops': int(match.group(2)), 'runtime': float(match.group(4))}) - elif match := verify_ops.match(l): + elif match := verify_ops.match(line): results.append({ 'algo': algo, 'key_size': int(match.group(3)), @@ -165,7 +165,7 @@ def run_openssl_bench(openssl, algo): 'runtime': float(match.group(4)) }) else: - logging.error("Unexpected output from OpenSSL %s", l) + logging.error("Unexpected output from OpenSSL %s", line) elif algo in KEY_AGREEMENT_EVP_MAP: res_header = re.compile(r'\+(R7|R9|R12|R14):([0-9]+):([0-9]+):([0-9]+\.[0-9]+)$') @@ -173,18 +173,18 @@ def run_openssl_bench(openssl, algo): result = {} - for l in output.splitlines(): - if ignored.match(l): + for line in output.splitlines(): + if ignored.match(line): continue - if match := res_header.match(l): + if match := res_header.match(line): results.append({ 'algo': algo, 'key_size': int(match.group(3)), 'ops': int(match.group(2)), 'runtime': float(match.group(4))}) else: - logging.error("Unexpected output from OpenSSL %s", l) + logging.error("Unexpected output from OpenSSL %s", line) return results @@ -236,13 +236,13 @@ def run_botan_key_agreement_bench(botan, runtime, algo): output = json.loads(output) results = [] - for l in output: - if l['op'] == 'key agreements': + for res in output: + if res['op'] == 'key agreements': results.append({ 'algo': algo, - 'key_size': int(re.search(r'[A-Z]+-[a-z]*([0-9]+).*', l['algo']).group(1)), - 'ops': l['events'], - 'runtime': l['nanos'] / 1000 / 1000 / 1000, + 'key_size': int(re.search(r'[A-Z]+-[a-z]*([0-9]+).*', res['algo']).group(1)), + 'ops': res['events'], + 'runtime': res['nanos'] / 1000 / 1000 / 1000, }) return results diff --git a/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py b/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py index edb851445af..ad756546dc9 100755 --- a/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py +++ b/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py @@ -48,11 +48,11 @@ def __map_file_offset(self, offset : int) -> tuple[int, int]: with open(self.file, encoding="utf-8") as srcfile: readoffset = 0 lineoffset = 0 - for l in srcfile.readlines(): - readoffset += len(l) + for line in srcfile.readlines(): + readoffset += len(line) lineoffset += 1 if readoffset >= offset: - coloffset = offset - readoffset + len(l) + coloffset = offset - readoffset + len(line) return (lineoffset, coloffset) raise RuntimeError(f"FileOffset {offset} out of range for {self.file}") diff --git a/src/scripts/ci_report_sizes.py b/src/scripts/ci_report_sizes.py index 7665a84d3b5..54e85f4e5e8 100755 --- a/src/scripts/ci_report_sizes.py +++ b/src/scripts/ci_report_sizes.py @@ -22,7 +22,7 @@ def format_size(bytes): return "%d bytes" % (bytes) def report_size(fsname): - if os.access(fsname, os.R_OK) == False: + if not os.access(fsname, os.R_OK): print("ERROR: Could not find %s" % (fsname)) return bytes = os.stat(fsname).st_size diff --git a/src/scripts/dev_tools/analyze_timing_results.py b/src/scripts/dev_tools/analyze_timing_results.py index 690928dcfff..ef3b9972578 100755 --- a/src/scripts/dev_tools/analyze_timing_results.py +++ b/src/scripts/dev_tools/analyze_timing_results.py @@ -41,7 +41,7 @@ def main(args = None): if match is None: print("Failed to match on '%s'" % (line)) - cnt = int(match.group(1)) + #cnt = int(match.group(1)) id = int(match.group(2)) time = int(match.group(3)) diff --git a/src/scripts/dev_tools/file_size_check.py b/src/scripts/dev_tools/file_size_check.py index b9678172389..50ada693489 100755 --- a/src/scripts/dev_tools/file_size_check.py +++ b/src/scripts/dev_tools/file_size_check.py @@ -7,11 +7,11 @@ def lines_in(f): lines = 0 - for l in f.decode('utf8').splitlines(): - if l == '': + for line in f.decode('utf8').splitlines(): + if line == '': continue - if l.startswith('#'): + if line.startswith('#'): continue lines += 1 return lines diff --git a/src/scripts/dev_tools/gen_dilithium_kat.py b/src/scripts/dev_tools/gen_dilithium_kat.py index 177e5cc054e..6c4e830ca77 100755 --- a/src/scripts/dev_tools/gen_dilithium_kat.py +++ b/src/scripts/dev_tools/gen_dilithium_kat.py @@ -41,7 +41,7 @@ def read_kats(self): while True: key, val = self.next_value() - if key == None: + if key is None: return # eof if key not in ['count', 'seed', 'mlen', 'msg', 'pk', 'sk', 'smlen', 'sm']: diff --git a/src/scripts/dev_tools/gen_ec_groups.py b/src/scripts/dev_tools/gen_ec_groups.py index 196a115cd61..c0873be0cb7 100755 --- a/src/scripts/dev_tools/gen_ec_groups.py +++ b/src/scripts/dev_tools/gen_ec_groups.py @@ -115,7 +115,7 @@ class OmitFirstLine: def __init__(self): self.first_line = True - def __call__(self, l): + def __call__(self, line): r = not self.first_line self.first_line = False return r diff --git a/src/scripts/dev_tools/gen_frodo_kat.py b/src/scripts/dev_tools/gen_frodo_kat.py index a891f67dbfc..861aebedda1 100644 --- a/src/scripts/dev_tools/gen_frodo_kat.py +++ b/src/scripts/dev_tools/gen_frodo_kat.py @@ -44,7 +44,7 @@ def read_kats(self): while True: key, val = self.next_value() - if key == None: + if key is None: return # eof if key not in ['count', 'seed', 'pk', 'sk', 'ct', 'ss']: @@ -66,8 +66,7 @@ def shake_256_16(v): return h.hexdigest(16) def compress_kat(kat): - first = kat['count'] == 0 - del kat['count'] + del kat['count'] # not needed # rename keys kat['Seed'] = kat.pop('seed') diff --git a/src/scripts/dev_tools/gen_kyber_kat.py b/src/scripts/dev_tools/gen_kyber_kat.py index a05a92f6c72..eed0592ec40 100755 --- a/src/scripts/dev_tools/gen_kyber_kat.py +++ b/src/scripts/dev_tools/gen_kyber_kat.py @@ -57,7 +57,7 @@ def read_kats(self): while True: key, val = self.next_value() - if key == None: + if key is None: return # eof if key in ['msg']: @@ -88,8 +88,7 @@ def sha256_16(v): return h.hexdigest()[:32] def compress_kat(kat, mode): - first = kat['count'] == 0 - del kat['count'] + del kat['count'] # Not needed hash_fn = sha256_16 if '90s' in mode else shake_256_16 diff --git a/src/scripts/dev_tools/gen_mp_monty.py b/src/scripts/dev_tools/gen_mp_monty.py index 601dc81c72c..75c0745ad1f 100755 --- a/src/scripts/dev_tools/gen_mp_monty.py +++ b/src/scripts/dev_tools/gen_mp_monty.py @@ -15,9 +15,9 @@ def monty_redc_code(n, p_dash1=False): fn_name = "bigint_monty_redc_pdash1" if p_dash1 else "bigint_monty_redc" if p_dash1: - hdr = "void bigint_monty_redc_pdash1_%d(word r[%d], const word z[%d], const word p[%d], word ws[%d]) {\n" % (n, n, 2*n, n, n) + hdr = "void %s_%d(word r[%d], const word z[%d], const word p[%d], word ws[%d]) {\n" % (fn_name, n, n, 2*n, n, n) else: - hdr = "void bigint_monty_redc_%d(word r[%d], const word z[%d], const word p[%d], word p_dash, word ws[%d]) {\n" % (n, n, 2*n, n, n) + hdr = "void %s_%d(word r[%d], const word z[%d], const word p[%d], word p_dash, word ws[%d]) {\n" % (fn_name, n, n, 2*n, n, n) ftr = "\n}\n" @@ -52,7 +52,7 @@ def monty_redc_code(n, p_dash1=False): lines.append("bigint_monty_maybe_sub<%d>(r, w1, ws, p);" % (n)) - return hdr + "\n".join([" %s" % (l) for l in lines]) + ftr + return hdr + "\n".join([" %s" % (line) for line in lines]) + ftr def main(args = None): if args is None: diff --git a/src/scripts/dev_tools/gen_os_features.py b/src/scripts/dev_tools/gen_os_features.py index 625f759c7ab..7a447b06429 100755 --- a/src/scripts/dev_tools/gen_os_features.py +++ b/src/scripts/dev_tools/gen_os_features.py @@ -22,7 +22,7 @@ # locale sys.path.append(botan_root) -from configure import OsInfo +from configure import OsInfo # noqa: E402 parser = argparse.ArgumentParser(description="") parser.add_argument('--verbose', dest='verbose', action='store_const', diff --git a/src/scripts/dev_tools/gen_pqc_dsa_kats.py b/src/scripts/dev_tools/gen_pqc_dsa_kats.py index d9d24c52e28..498e8789106 100644 --- a/src/scripts/dev_tools/gen_pqc_dsa_kats.py +++ b/src/scripts/dev_tools/gen_pqc_dsa_kats.py @@ -112,7 +112,7 @@ def main(args = None): hash_fn = sha3_256 def mldsa_sign_internal(m, sk, rnd): # For some reason the interfaces vary between FIPS 204 and FIPS 205... - if rnd == None: + if rnd is None: rnd = bytes([0]*32) return alg.sign_internal(sk, m, rnd) diff --git a/src/scripts/dev_tools/show_dependencies.py b/src/scripts/dev_tools/show_dependencies.py index a8f0164b40f..28d3ed60ec9 100755 --- a/src/scripts/dev_tools/show_dependencies.py +++ b/src/scripts/dev_tools/show_dependencies.py @@ -25,7 +25,7 @@ # locale sys.path.append(botan_root) -from configure import ModuleInfo +from configure import ModuleInfo # noqa: E402 parser = argparse.ArgumentParser(description= 'Show Botan module dependencies. ' diff --git a/src/scripts/run_limbo_tests.py b/src/scripts/run_limbo_tests.py index 276b5943a6e..bb29034e0bf 100755 --- a/src/scripts/run_limbo_tests.py +++ b/src/scripts/run_limbo_tests.py @@ -224,7 +224,7 @@ def main(args = None): validation_time = int(parser.parse(test['validation_time']).timestamp()) hostname = None - if test['expected_peer_name'] != None: + if test['expected_peer_name'] is not None: if test['expected_peer_name']['kind'] in ['DNS', 'IP']: hostname = test['expected_peer_name']['value'] else: diff --git a/src/scripts/run_tls_attacker.py b/src/scripts/run_tls_attacker.py index c2f226ed4ec..4d69749f7cc 100755 --- a/src/scripts/run_tls_attacker.py +++ b/src/scripts/run_tls_attacker.py @@ -54,11 +54,11 @@ def main(args=None): print("Unknown --type %s" % (options.test_type)) return 1 - if os.access(cli_exe, os.X_OK) != True: + if not os.access(cli_exe, os.X_OK): print("Unable to find CLI tool at %s" % (cli_exe)) return 1 - if os.access(src_dir, os.X_OK) != True: + if not os.access(src_dir, os.X_OK): print("Unable to find src dir at %s" % (src_dir)) return 1 @@ -72,7 +72,7 @@ def main(args=None): tls_attacker_testsuites = os.path.join(tls_attacker_dir, 'resources/testsuite') tls_fuzzer_workflows = os.path.join(tls_attacker_dir, 'resources/fuzzing/workflows') - if os.access(tls_attacker_jar, os.R_OK) != True: + if not os.access(tls_attacker_jar, os.R_OK): print("Unable to find TLS-Attacker jar at %s" % (tls_attacker_jar)) return 1 diff --git a/src/scripts/run_tls_fuzzer.py b/src/scripts/run_tls_fuzzer.py index d5221e09178..a5ca0535a52 100755 --- a/src/scripts/run_tls_fuzzer.py +++ b/src/scripts/run_tls_fuzzer.py @@ -86,7 +86,7 @@ def main(args = None): if script in results: continue - if proc.poll() != None: + if proc.poll() is not None: rv = proc.returncode results[script] = rv if rv == 0: diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py index d7770cf84c7..91f61c85be4 100755 --- a/src/scripts/test_cli.py +++ b/src/scripts/test_cli.py @@ -922,8 +922,8 @@ def cli_dl_group_info_tests(_tmp_dir): if len(lines) != 2: logging.error('Unexpected output from dl_group_info') - for l in lines: - if not dl_output.match(l): + for line in lines: + if not dl_output.match(line): logging.error('Unexpected output from dl_group_info') diff --git a/src/scripts/test_fuzzers.py b/src/scripts/test_fuzzers.py index c245d643950..b1f32bec487 100755 --- a/src/scripts/test_fuzzers.py +++ b/src/scripts/test_fuzzers.py @@ -120,7 +120,7 @@ def main(args=None): for fuzzer in sorted(list(fuzzers_with_corpus)): fuzzer_bin = os.path.join(fuzzer_dir, fuzzer) corpus_subdir = os.path.join(corpus_dir, fuzzer) - corpus_files = [os.path.join(corpus_subdir, l) for l in sorted(list(os.listdir(corpus_subdir)))] + corpus_files = [os.path.join(corpus_subdir, fsname) for fsname in sorted(list(os.listdir(corpus_subdir)))] # We have to do this hack because multiprocessing's Pool.map doesn't support # passing any initial arguments, just the single iterable @@ -170,7 +170,7 @@ def main(args=None): else: corpus_subdir = random_corpus_dir - corpus_files = [os.path.join(corpus_subdir, l) for l in sorted(list(os.listdir(corpus_subdir)))] + corpus_files = [os.path.join(corpus_subdir, fsname) for fsname in sorted(list(os.listdir(corpus_subdir)))] if fuzzer in slow_fuzzers: corpus_files = corpus_files[:random_corpus_size_for_slow_fuzzers] diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 52ebdd96e7d..941b7c92935 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -12,6 +12,7 @@ import platform import argparse import sys +import time from itertools import permutations # Starting with Python 3.8 DLL search locations are more restricted on Windows. @@ -777,6 +778,7 @@ def test_certs(self): self.assertTrue(cert.allowed_usage(["CRL_SIGN", "KEY_CERT_SIGN"])) self.assertTrue(cert.allowed_usage(["KEY_CERT_SIGN"])) + self.assertTrue(cert.allowed_usage([botan.X509KeyConstraints.CRL_SIGN, botan.X509KeyConstraints.KEY_CERT_SIGN])) self.assertFalse(cert.allowed_usage(["DIGITAL_SIGNATURE"])) self.assertFalse(cert.allowed_usage(["DIGITAL_SIGNATURE", "CRL_SIGN"])) @@ -820,6 +822,193 @@ def test_certs(self): self.assertFalse(int04_1.is_revoked(rootcrl)) self.assertTrue(end21.is_revoked(int21crl)) + def test_cert_creation(self): + group = "secp256r1" + now = int(time.time()) + not_before = now - 180 + not_after = now + 86400 + + rng = botan.RandomNumberGenerator() + ca_key = botan.PrivateKey.create("ECDSA", group, rng) + ca_builder = botan.X509CertificateBuilder() + ca_builder.add_common_name("Test CA") + ca_builder.add_country("US") + ca_builder.add_organization("Botan Project") + ca_builder.add_organizational_unit("Testing") + ca_builder.set_as_ca_certificate(1) + ca_cert = ca_builder.into_self_signed(ca_key, rng, not_before, not_after) + self.assertEqual(ca_cert.subject_name(), 'CN="Test CA",C="US",O="Botan Project",OU="Testing"') + constraints = ca_cert.allowed_usages() + self.assertEqual(len(constraints), 3) + for item in [ + botan.X509KeyConstraints.DIGITAL_SIGNATURE, + botan.X509KeyConstraints.KEY_CERT_SIGN, + botan.X509KeyConstraints.CRL_SIGN + ]: + self.assertTrue(item in constraints) + self.assertTrue(ca_cert.is_self_signed()) + + cert_key = botan.PrivateKey.create("ECDSA", group, rng) + req_builder = botan.X509CertificateBuilder() + req_builder.add_allowed_usage([botan.X509KeyConstraints.DIGITAL_SIGNATURE]) + req_builder.add_uri("https://botan.randombit.net") + for item in ["imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"]: + req_builder.add_dns(item) + req = req_builder.into_request(cert_key, rng) + self.assertTrue(req.verify(cert_key.get_public_key())) + + cert = req.sign(ca_cert, ca_key, rng, not_before, not_after) + self.assertEqual(ca_cert.issuer_name(), 'CN="Test CA",C="US",O="Botan Project",OU="Testing"') + self.assertFalse(cert.is_self_signed()) + self.assertEqual(cert.allowed_usages(), [botan.X509KeyConstraints.DIGITAL_SIGNATURE]) + + with self.assertRaisesRegex(botan.BotanException, r".*No value available.*"): + _ = cert.ext_ip_addr_blocks() + + with self.assertRaisesRegex(botan.BotanException, r".*No value available.*"): + _ = cert.ext_as_blocks() + + self.assertEqual(cert.verify(None, [ca_cert]), 0) + + def test_cert_revocation(self): + group = "secp256r1" + now = int(time.time()) + not_before = now - 180 + not_after = now + 86400 + + rng = botan.RandomNumberGenerator() + ca_key = botan.PrivateKey.create("ECDSA", group, rng) + ca_builder = botan.X509CertificateBuilder() + ca_builder.set_as_ca_certificate(1) + ca_cert = ca_builder.into_self_signed(ca_key, rng, not_before, not_after, botan.MPI("0")) + + cert_key_1 = botan.PrivateKey.create("ECDSA", group, rng) + req_builder_1 = botan.X509CertificateBuilder() + req_1 = req_builder_1.into_request(cert_key_1, rng) + cert_1 = req_1.sign(ca_cert, ca_key, rng, not_before, not_after, botan.MPI("1")) + + cert_key_2 = botan.PrivateKey.create("ECDSA", group, rng) + req_builder_2 = botan.X509CertificateBuilder() + req_2 = req_builder_2.into_request(cert_key_2, rng) + cert_2 = req_2.sign(ca_cert, ca_key, rng, not_before, not_after, botan.MPI("2")) + + crl = botan.X509CRL.create(rng, ca_cert, ca_key, now, now + 600) + self.assertTrue(crl.verify(ca_cert.subject_public_key())) + self.assertEqual(cert_1.verify(None, [ca_cert], crls=[crl]), 0) + self.assertEqual(cert_2.verify(None, [ca_cert], crls=[crl]), 0) + + crl = crl.revoke(rng, ca_cert, ca_key, now, now + 86400, [cert_1], botan.X509CRLReason.KEY_COMPROMISE) + self.assertTrue(crl.verify(ca_cert.subject_public_key())) + self.assertEqual(cert_1.verify(None, [ca_cert], crls=[crl]), 5000) + self.assertEqual(cert_2.verify(None, [ca_cert], crls=[crl]), 0) + self.assertEqual(len(crl.revoked()), 1) + revoked_entry: botan.X509CRLEntry = crl.revoked()[0] + self.assertEqual(revoked_entry.reason, botan.X509CRLReason.KEY_COMPROMISE) + self.assertEqual(revoked_entry.serial_number, botan.MPI("1")) + self.assertTrue(now - 5 <= revoked_entry.expire_time <= now + 5) + + pem = crl.to_pem() + crl_from_pem = botan.X509CRL(buf=pem) + self.assertEqual(len(crl_from_pem.revoked()), 1) + + def test_x509_rpki(self): + group = "secp256r1" + now = int(time.time()) + not_before = now - 180 + not_after = now + 86400 + + rng = botan.RandomNumberGenerator() + ca_key = botan.PrivateKey.create("ECDSA", group, rng) + ca_builder = botan.X509CertificateBuilder() + ca_builder.set_as_ca_certificate() + + ca_ip_addr_blocks = botan.X509ExtIPAddrBlocks() + + ca_ip_addr_blocks.add_addr([192, 168, 2, 1]) + ca_ip_addr_blocks.add_range([10, 0, 0, 1], [10, 0, 255, 255]) + ca_ip_addr_blocks.restrict(False, 42) + + ca_ip_addr_blocks.add_addr([0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) + ca_ip_addr_blocks.restrict(True, 234) + + ca_as_blocks = botan.X509ExtASBlocks() + ca_as_blocks.add_asnum(30) + ca_as_blocks.add_asnum_range(3000, 4999) + ca_as_blocks.restrict_rdi() + + ca_builder.add_ext_ip_addr_blocks(ca_ip_addr_blocks, True) + ca_builder.add_ext_as_blocks(ca_as_blocks, True) + + with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): + ca_builder.add_ext_ip_addr_blocks(ca_ip_addr_blocks, True) + + with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): + ca_builder.add_ext_as_blocks(ca_as_blocks, True) + + ca_cert = ca_builder.into_self_signed(ca_key, rng, not_before, not_after) + + ca_ip_addr_blocks = ca_cert.ext_ip_addr_blocks() + self.assertEqual(ca_ip_addr_blocks.addresses(), ( + [ + (None, [((10, 0, 0, 1), (10, 0, 255, 255)), ((192, 168, 2, 1), (192, 168, 2, 1))]), + (42, []) + ], + [ + (None, [( + (0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), + (0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01) + )], ), + (234, []) + ] + )) + + with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): + ca_ip_addr_blocks.add_addr([123, 0, 0, 1]) + + ca_as_blocks = ca_cert.ext_as_blocks() + self.assertEqual(ca_as_blocks.asnum(), [(30, 30), (3000, 4999)]) + self.assertEqual(ca_as_blocks.rdi(), []) + + with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): + ca_as_blocks.add_asnum(999) + + cert_key = botan.PrivateKey.create("ECDSA", group, rng) + req_builder = botan.X509CertificateBuilder() + + req_ip_addr_blocks = botan.X509ExtIPAddrBlocks() + req_ip_addr_blocks.add_addr([192, 168, 2, 1]) + req_ip_addr_blocks.add_range([10, 0, 5, 5], [10, 0, 7, 7]) + req_ip_addr_blocks.restrict(False, 42) + req_ip_addr_blocks.inherit(True) + req_ip_addr_blocks.inherit(True, 234) + + req_as_blocks = botan.X509ExtASBlocks() + req_as_blocks.add_asnum_range(3100, 4000) + req_as_blocks.inherit_rdi() + + req_builder.add_ext_ip_addr_blocks(req_ip_addr_blocks, True) + req_builder.add_ext_as_blocks(req_as_blocks, True) + req = req_builder.into_request(cert_key, rng) + + cert = req.sign(ca_cert, ca_key, rng, not_before, not_after) + + req_ip_addr_blocks = cert.ext_ip_addr_blocks() + self.assertEqual(req_ip_addr_blocks.addresses(), ( + [ + (None, [((10, 0, 5, 5), (10, 0, 7, 7)), ((192, 168, 2, 1), (192, 168, 2, 1))]), + (42, []) + ], + [ + (None, None), + (234, None) + ] + )) + + req_as_blocks = cert.ext_as_blocks() + self.assertEqual(req_as_blocks.asnum(), [(3100, 4000)]) + self.assertEqual(req_as_blocks.rdi(), None) + + self.assertEqual(cert.verify(None, [ca_cert]), 0) def test_mpi(self): z = botan.MPI() @@ -918,16 +1107,16 @@ def test_mpi(self): def test_mpi_random(self): rng = botan.RandomNumberGenerator() - u = botan.MPI.random(rng, 512) - self.assertEqual(u.bit_count(), 512) + upper = botan.MPI.random(rng, 512) + self.assertEqual(upper.bit_count(), 512) - l = u >> 32 - self.assertEqual(l.bit_count(), 512-32) + lower = upper >> 32 + self.assertEqual(lower.bit_count(), 512-32) for _i in range(10): - x = botan.MPI.random_range(rng, l, u) - self.assertLess(x, u) - self.assertGreater(x, l) + x = botan.MPI.random_range(rng, lower, upper) + self.assertLess(x, upper) + self.assertGreater(x, lower) def test_fpe(self): diff --git a/src/scripts/tls_scanner/tls_scanner.py b/src/scripts/tls_scanner/tls_scanner.py index e7e89fda9a1..1004ffeec28 100755 --- a/src/scripts/tls_scanner/tls_scanner.py +++ b/src/scripts/tls_scanner/tls_scanner.py @@ -45,12 +45,12 @@ def scanner(args = None): for i in range(timeout): scanners[url].poll() - if scanners[url].returncode != None: + if scanners[url].returncode is not None: break #print("Waiting %d more seconds for %s" % (timeout-i, url)) time.sleep(1) - if scanners[url].returncode != None: + if scanners[url].returncode is not None: output = scanners[url].stdout.read() + scanners[url].stderr.read() report[url] = format_report(output.decode("utf-8")) diff --git a/src/tests/test_certstor.cpp b/src/tests/test_certstor.cpp index bd5beb6e0ad..546b93818dc 100644 --- a/src/tests/test_certstor.cpp +++ b/src/tests/test_certstor.cpp @@ -267,8 +267,8 @@ Test::Result test_certstor_sqlite3_find_all_certs_test(const std::vector& certsandkeys) { - Test::Result result("Certificate Store - Find by subject hash"); +Test::Result test_certstor_all_finders(const std::vector& certsandkeys) { + Test::Result result("Certificate Store - Test all finders"); try { Botan::Certificate_Store_In_Memory store; @@ -279,15 +279,33 @@ Test::Result test_certstor_find_hash_subject(const std::vectorraw_subject_dn_sha256()); } - result.test_eq("Got wrong certificate", hash, found->raw_subject_dn_sha256()); + // find by issuer dn and serial number + { + const auto& issuer_dn = cert.issuer_dn(); + const auto& serial_number = cert.serial_number(); + + const auto found = store.find_cert_by_issuer_dn_and_serial_number(issuer_dn, serial_number); + if(!found) { + result.test_failure("Can't retrieve certificate " + cert.fingerprint("SHA-1")); + return result; + } + + result.test_eq("Got wrong certificate", serial_number, found->serial_number()); + } } const auto found = store.find_cert_by_raw_subject_dn_sha256(std::vector(32, 0)); @@ -363,7 +381,7 @@ class Certstor_Tests final : public Test { std::vector results; - results.push_back(test_certstor_find_hash_subject(certsandkeys)); + results.push_back(test_certstor_all_finders(certsandkeys)); results.push_back(test_certstor_load_allcert()); #if defined(BOTAN_HAS_CERTSTOR_SQLITE3) results.push_back(test_certstor_sqlite3_insert_find_remove_test(certsandkeys)); diff --git a/src/tests/test_certstor_flatfile.cpp b/src/tests/test_certstor_flatfile.cpp index f5499834447..cc1294066a0 100644 --- a/src/tests/test_certstor_flatfile.cpp +++ b/src/tests/test_certstor_flatfile.cpp @@ -228,6 +228,28 @@ Test::Result certstore_contains_user_certificate() { return result; } +Test::Result find_cert_by_issuer_dn_and_serial_number() { + Test::Result result("Flatfile Certificate Store - Find Certificate by issuer DN and serial number"); + + try { + result.start_timer(); + Botan::Flatfile_Certificate_Store certstore(get_valid_ca_bundle_path()); + auto cert = certstore.find_cert_by_issuer_dn_and_serial_number(get_dn(), get_serial_number()); + result.end_timer(); + + if(result.test_not_nullopt("found certificate", cert)) { + auto cns = cert->subject_dn().get_attribute("CN"); + result.test_int_eq("exactly one CN", cns.size(), 1); + result.test_eq("CN", cns.front(), get_subject_cn()); + result.test_eq("serial number", cert->serial_number(), get_serial_number()); + } + } catch(std::exception& e) { + result.test_failure(e.what()); + } + + return result; +} + class Certstor_Flatfile_Tests final : public Test { public: std::vector run() override { @@ -242,6 +264,7 @@ class Certstor_Flatfile_Tests final : public Test { results.push_back(find_all_subjects()); results.push_back(no_certificate_matches()); results.push_back(certstore_contains_user_certificate()); + results.push_back(find_cert_by_issuer_dn_and_serial_number()); return results; } diff --git a/src/tests/test_certstor_system.cpp b/src/tests/test_certstor_system.cpp index e7c9ed3825f..14a29d0b480 100644 --- a/src/tests/test_certstor_system.cpp +++ b/src/tests/test_certstor_system.cpp @@ -227,6 +227,27 @@ Test::Result find_all_subjects(Botan::Certificate_Store& certstore) { return result; } +Test::Result find_cert_by_issuer_dn_and_serial_number(Botan::Certificate_Store& certstore) { + Test::Result result("System Certificate Store - Find Certificate by issuer DN and serial number"); + + try { + result.start_timer(); + auto cert = certstore.find_cert_by_issuer_dn_and_serial_number(get_dn(), get_serial_number()); + result.end_timer(); + + if(result.test_not_nullopt("found certificate", cert)) { + auto cns = cert->subject_dn().get_attribute("CN"); + result.test_is_eq("exactly one CN", cns.size(), size_t(1)); + result.test_eq("CN", cns.front(), get_subject_cn()); + result.test_eq("serial number", cert->serial_number(), get_serial_number()); + } + } catch(std::exception& e) { + result.test_failure(e.what()); + } + + return result; +} + Test::Result no_certificate_matches(Botan::Certificate_Store& certstore) { Test::Result result("System Certificate Store - can deal with no matches (regression test)"); @@ -312,6 +333,7 @@ class Certstor_System_Tests final : public Test { results.push_back(find_all_subjects(*system)); results.push_back(no_certificate_matches(*system)); results.push_back(find_cert_by_utf8_subject_dn(*system)); + results.push_back(find_cert_by_issuer_dn_and_serial_number(*system)); #if defined(BOTAN_HAS_CERTSTOR_MACOS) results.push_back(certificate_matching_with_dn_normalization(*system)); #endif diff --git a/src/tests/test_certstor_utils.cpp b/src/tests/test_certstor_utils.cpp index dbd52acb297..18daf9ede11 100644 --- a/src/tests/test_certstor_utils.cpp +++ b/src/tests/test_certstor_utils.cpp @@ -88,6 +88,11 @@ std::string get_subject_cn() { return "ISRG Root X1"; } +std::vector get_serial_number() { + // serial number of "ISRG Root X1" + return Botan::hex_decode("8210CFB0D240E3594463E0BB63828B00"); +} + std::vector get_pubkey_sha1_of_cert_with_different_key_id() { // see https://github.com/randombit/botan/issues/2779 for details // diff --git a/src/tests/test_certstor_utils.h b/src/tests/test_certstor_utils.h index 4c87f44cc2b..b03cd9aed93 100644 --- a/src/tests/test_certstor_utils.h +++ b/src/tests/test_certstor_utils.h @@ -28,6 +28,7 @@ std::vector> get_utf8_dn_alternatives(); std::vector get_key_id(); std::string get_subject_cn(); +std::vector get_serial_number(); std::vector get_pubkey_sha1_of_cert_with_different_key_id(); Botan::X509_DN get_dn_of_cert_with_different_key_id(); diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index a19d8204e87..db848ca09f2 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -793,6 +793,449 @@ class FFI_Cert_Validation_Test final : public FFI_Test { } }; +class FFI_Cert_Creation_Test final : public FFI_Test { + public: + std::string name() const override { return "FFI Cert Creation"; } + + void ffi_test(Test::Result& result, botan_rng_t rng) override { + const std::string hash_fn{"SHA-256"}; + const std::string group{"secp256r1"}; + + botan_privkey_t ca_key; + botan_pubkey_t ca_key_pub; + botan_privkey_t cert_key; + botan_pubkey_t cert_key_pub; + + if(TEST_FFI_INIT(botan_privkey_create, (&ca_key, "ECDSA", group.c_str(), rng))) { + TEST_FFI_OK(botan_privkey_create, (&cert_key, "ECDSA", group.c_str(), rng)); + + TEST_FFI_OK(botan_privkey_export_pubkey, (&ca_key_pub, ca_key)); + TEST_FFI_OK(botan_privkey_export_pubkey, (&cert_key_pub, cert_key)); + + uint64_t now = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + uint64_t not_before = now - 180; + uint64_t not_after = now + 86400; + + botan_x509_cert_params_builder_t ca_builder; + if(TEST_FFI_INIT(botan_x509_cert_params_builder_create, (&ca_builder))) { + TEST_FFI_OK(botan_x509_cert_params_builder_add_common_name, (ca_builder, "Test CA")); + TEST_FFI_OK(botan_x509_cert_params_builder_add_country, (ca_builder, "US")); + TEST_FFI_OK(botan_x509_cert_params_builder_add_organization, (ca_builder, "Botan Project")); + TEST_FFI_OK(botan_x509_cert_params_builder_add_organizational_unit, (ca_builder, "Testing")); + TEST_FFI_OK(botan_x509_cert_params_builder_set_as_ca_certificate, (ca_builder, nullptr)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_uri, (ca_builder, "https://botan.randombit.net")); + for(const auto* dns : {"imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"}) { + TEST_FFI_OK(botan_x509_cert_params_builder_add_dns, (ca_builder, dns)); + } + + botan_x509_cert_t ca_cert; + TEST_FFI_OK(botan_x509_cert_params_builder_into_self_signed, + (&ca_cert, ca_key, ca_builder, rng, not_before, not_after, nullptr, hash_fn.c_str(), "")); + + botan_x509_cert_params_builder_t req_builder; + TEST_FFI_OK(botan_x509_cert_params_builder_create, (&req_builder)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_allowed_usage, (req_builder, 1 << 15)); + + botan_x509_pkcs10_req_t req; + TEST_FFI_OK(botan_x509_cert_params_builder_into_pkcs10_req, + (&req, cert_key, req_builder, rng, hash_fn.c_str(), nullptr, nullptr)); + + int res; + TEST_FFI_OK(botan_x509_pkcs10_req_verify_signature, (req, ca_key_pub, &res)); + result.confirm("signature is not valid", res == 0); + TEST_FFI_OK(botan_x509_pkcs10_req_verify_signature, (req, cert_key_pub, &res)); + result.confirm("signature is valid", res == 1); + + uint32_t usage; + TEST_FFI_OK(botan_x509_pkcs10_req_get_allowed_usage, (req, &usage)); + result.confirm("usage is correct", usage == 1 << 15); + + int is_ca; + size_t limit; + TEST_FFI_OK(botan_x509_pkcs10_req_is_ca, (req, &is_ca, &limit)); + result.confirm("cert is not a CA", is_ca == 0); + + botan_pubkey_t pubkey_from_req; + TEST_FFI_OK(botan_x509_pkcs10_req_get_public_key, (req, &pubkey_from_req)); + TEST_FFI_OK(botan_x509_pkcs10_req_verify_signature, (req, pubkey_from_req, &res)); + result.confirm("signature is valid", res == 1); + + ViewStringSink req_pem; + TEST_FFI_OK(botan_x509_pkcs10_req_view_pem, (req, req_pem.delegate(), req_pem.callback())); + std::string pem = {req_pem.get().begin(), req_pem.get().end()}; + + botan_x509_pkcs10_req_t req_from_pem; + TEST_FFI_OK(botan_x509_pkcs10_req_load, + (&req_from_pem, reinterpret_cast(pem.c_str()), pem.size())); + + TEST_FFI_OK(botan_x509_pkcs10_req_verify_signature, (req_from_pem, cert_key_pub, &res)); + result.confirm("signature is valid", res == 1); + + botan_mp_t serial; + TEST_FFI_OK(botan_mp_init, (&serial)); + TEST_FFI_OK(botan_mp_set_from_str, (serial, "12345")); + + botan_x509_cert_t cert; + TEST_FFI_OK(botan_x509_pkcs10_req_sign, + (&cert, req, ca_cert, ca_key, rng, not_before, not_after, &serial, hash_fn.c_str(), "")); + + std::vector serial_out(5); + size_t out_len = 5; + + botan_mp_t serial_from_cert; + TEST_FFI_OK(botan_mp_init, (&serial_from_cert)); + + TEST_FFI_OK(botan_x509_cert_get_serial_number, (cert, serial_out.data(), &out_len)); + TEST_FFI_OK(botan_mp_from_bin, (serial_from_cert, serial_out.data(), out_len)); + TEST_FFI_RC(1, botan_mp_equal, (serial, serial_from_cert)); + + int rc; + TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); + result.confirm("validation succeeds", rc == 0); + + botan_x509_crl_t crl; + TEST_FFI_OK(botan_x509_crl_create, + (&crl, rng, ca_cert, ca_key, not_before, 86400, hash_fn.c_str(), nullptr)); + + TEST_FFI_RC(0, + botan_x509_cert_verify_with_crl, + (&rc, cert, nullptr, 0, &ca_cert, 1, &crl, 1, nullptr, 0, nullptr, 0)); + botan_x509_crl_t new_crl; + botan_x509_cert_t certs[1] = {cert}; + TEST_FFI_OK( + botan_x509_crl_update, + (&new_crl, crl, rng, ca_cert, ca_key, not_before, 86400, certs, 1, 1, hash_fn.c_str(), nullptr)); + + TEST_FFI_RC(1, + botan_x509_cert_verify_with_crl, + (&rc, cert, nullptr, 0, &ca_cert, 1, &new_crl, 1, nullptr, 0, nullptr, 0)); + result.confirm("validation fails because cert is revoked", rc == 5000); + + size_t count; + TEST_FFI_OK(botan_x509_crl_get_count, (new_crl, &count)); + result.confirm("crl has correct number of entires", count == 1); + + uint64_t expire_time; + uint8_t reason; + botan_mp_t serial_from_crl; + TEST_FFI_OK(botan_mp_init, (&serial_from_crl)); + TEST_FFI_OK(botan_x509_crl_get_entry, (new_crl, 0, serial_from_crl, &expire_time, &reason)); + TEST_FFI_RC(1, botan_mp_equal, (serial, serial_from_crl)); + result.confirm("expire time is correct", now - 5 <= expire_time && expire_time <= now + 5); + result.confirm("reason is correct", reason == 1); + + TEST_FFI_OK(botan_mp_destroy, (serial)); + TEST_FFI_OK(botan_mp_destroy, (serial_from_cert)); + TEST_FFI_OK(botan_mp_destroy, (serial_from_crl)); + TEST_FFI_OK(botan_x509_crl_destroy, (crl)); + TEST_FFI_OK(botan_x509_crl_destroy, (new_crl)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (ca_builder)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (req_builder)); + TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req)); + TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req_from_pem)); + TEST_FFI_OK(botan_x509_cert_destroy, (ca_cert)); + TEST_FFI_OK(botan_x509_cert_destroy, (cert)); + TEST_FFI_OK(botan_pubkey_destroy, (ca_key_pub)); + TEST_FFI_OK(botan_pubkey_destroy, (cert_key_pub)); + TEST_FFI_OK(botan_pubkey_destroy, (pubkey_from_req)); + TEST_FFI_OK(botan_privkey_destroy, (ca_key)); + TEST_FFI_OK(botan_privkey_destroy, (cert_key)); + } + } + } +}; + +class FFI_X509_RPKI_Test final : public FFI_Test { + public: + std::string name() const override { return "FFI X509 RPKI"; } + + void ffi_test(Test::Result& result, botan_rng_t rng) override { + const std::string hash_fn{"SHA-256"}; + const std::string group{"secp256r1"}; + + botan_privkey_t ca_key; + botan_privkey_t cert_key; + + if(TEST_FFI_INIT(botan_privkey_create, (&ca_key, "ECDSA", group.c_str(), rng))) { + TEST_FFI_OK(botan_privkey_create, (&cert_key, "ECDSA", group.c_str(), rng)); + + uint64_t now = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + uint64_t not_before = now - 180; + uint64_t not_after = now + 86400; + + botan_x509_cert_params_builder_t ca_builder; + if(TEST_FFI_INIT(botan_x509_cert_params_builder_create, (&ca_builder))) { + size_t limit = 1; + TEST_FFI_OK(botan_x509_cert_params_builder_set_as_ca_certificate, (ca_builder, &limit)); + + botan_x509_ext_ip_addr_blocks_t ca_ip_addr_blocks; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_create, (&ca_ip_addr_blocks)); + std::vector ip_addr = {192, 168, 2, 1}; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_add_ip_addr, + (ca_ip_addr_blocks, ip_addr.data(), ip_addr.data(), 0, nullptr)); + + std::vector min = {10, 0, 0, 1}; + std::vector max = {10, 255, 255, 255}; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_add_ip_addr, + (ca_ip_addr_blocks, min.data(), max.data(), 0, nullptr)); + + std::vector ipv6_addr = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + uint8_t ipv6_safi = 1; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_add_ip_addr, + (ca_ip_addr_blocks, ipv6_addr.data(), ipv6_addr.data(), 1, &ipv6_safi)); + + ipv6_safi = 2; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_restrict, (ca_ip_addr_blocks, 1, &ipv6_safi)); + + size_t v4_count; + size_t v6_count; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_counts, (ca_ip_addr_blocks, &v4_count, &v6_count)); + + result.test_eq("ext has v4 addresses", v4_count, 1); + result.test_eq("ext has v6 addresses", v6_count, 2); + + int has_safi; + uint8_t safi; + int present; + size_t count; + + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_ip_addr_blocks_get_family, + (ca_ip_addr_blocks, 0, 1, &has_safi, &safi, &present, &count)); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (ca_ip_addr_blocks, 0, 0, &has_safi, &safi, &present, &count)); + + result.confirm("index 0 does not have a safi", has_safi == 0); + result.confirm("index 0 has a value", present == 1); + result.confirm("index 0 has entries", count == 2); + + std::vector min_out(4); + std::vector max_out(4); + size_t out_len = 4; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 0, 0, 0, min_out.data(), max_out.data(), &out_len)); + + result.confirm("data has correct size", out_len == 4); + result.test_eq("index 0 entry 0 has correct min", min_out, min); + result.test_eq("index 0 entry 0 has correct min", max_out, max); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 0, 0, 1, min_out.data(), max_out.data(), &out_len)); + + result.test_eq("index 0 entry 1 has correct min", min_out, ip_addr); + result.test_eq("index 0 entry 1 has correct min", max_out, ip_addr); + + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 0, 0, 2, min_out.data(), max_out.data(), &out_len)); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 0, 1, 0, min_out.data(), max_out.data(), &out_len)); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 1, 0, 0, min_out.data(), max_out.data(), &out_len)); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (ca_ip_addr_blocks, 1, 1, &has_safi, &safi, &present, &count)); + + result.confirm("index 1 does have a safi", has_safi == 1); + result.confirm("index 1 safi is correct", safi == 1); + result.confirm("index 1 has a value", present == 1); + result.confirm("index 1 has entries", count == 1); + + min_out.resize(16); + max_out.resize(16); + out_len = 16; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 1, 1, 0, min_out.data(), max_out.data(), &out_len)); + + result.confirm("data has correct size", out_len == 16); + result.test_eq("index 1 entry 0 has correct min", min_out, ipv6_addr); + result.test_eq("index 1 entry 0 has correct min", max_out, ipv6_addr); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (ca_ip_addr_blocks, 1, 2, &has_safi, &safi, &present, &count)); + + result.confirm("index 1 does have a safi", has_safi == 1); + result.confirm("index 1 safi is correct", safi == 2); + result.confirm("index 1 has a value", present == 1); + result.confirm("index 1 has entries", count == 0); + + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (ca_builder, ca_ip_addr_blocks, 1)); + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_cert_params_builder_add_ext_ip_addr_blocks, + (ca_builder, ca_ip_addr_blocks, 1)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (ca_ip_addr_blocks)); + + botan_x509_ext_as_blocks_t ca_as_blocks; + TEST_FFI_OK(botan_x509_ext_as_blocks_create, (&ca_as_blocks)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_add_asnum, (ca_as_blocks, 0, 3000)); + TEST_FFI_OK(botan_x509_ext_as_blocks_add_asnum, (ca_as_blocks, 4000, 5000)); + TEST_FFI_OK(botan_x509_ext_as_blocks_restrict_rdi, (ca_as_blocks)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum, (ca_as_blocks, &present, &count)); + result.confirm("ext has asnum", present == 1); + result.confirm("ext has 2 asnums", count == 2); + + uint32_t min_out_ = 0; + uint32_t max_out_ = 0; + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum_at, (ca_as_blocks, 0, &min_out_, &max_out_)); + result.confirm("asnum 0 min is correct", min_out_ == 0); + result.confirm("asnum 0 max is correct", max_out_ == 3000); + TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum_at, (ca_as_blocks, 1, &min_out_, &max_out_)); + result.confirm("asnum 1 min is correct", min_out_ == 4000); + result.confirm("asnum 1 max is correct", max_out_ == 5000); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_as_blocks_get_asnum_at, + (ca_as_blocks, 2, &min_out_, &max_out_)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_rdi, (ca_as_blocks, &present, &count)); + result.confirm("ext has rdi", present == 1); + result.confirm("ext has 0 rdis", count == 0); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_as_blocks_get_rdi_at, + (ca_as_blocks, 0, &min_out_, &max_out_)); + + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (ca_builder, ca_as_blocks, 1)); + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_cert_params_builder_add_ext_as_blocks, + (ca_builder, ca_as_blocks, 1)); + TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (ca_as_blocks)); + + botan_x509_cert_t ca_cert; + TEST_FFI_OK( + botan_x509_cert_params_builder_into_self_signed, + (&ca_cert, ca_key, ca_builder, rng, not_before, not_after, nullptr, hash_fn.c_str(), nullptr)); + + botan_x509_cert_params_builder_t req_builder; + TEST_FFI_OK(botan_x509_cert_params_builder_create, (&req_builder)); + + botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_create, (&req_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_inherit, (req_ip_addr_blocks, 0, nullptr)); + + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (req_builder, req_ip_addr_blocks, 1)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks)); + + botan_x509_ext_as_blocks_t req_as_blocks; + TEST_FFI_OK(botan_x509_ext_as_blocks_create, (&req_as_blocks)); + TEST_FFI_OK(botan_x509_ext_as_blocks_inherit_asnum, (req_as_blocks)); + + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (req_builder, req_as_blocks, 1)); + TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (req_as_blocks)); + + botan_x509_pkcs10_req_t req; + TEST_FFI_OK(botan_x509_cert_params_builder_into_pkcs10_req, + (&req, cert_key, req_builder, rng, hash_fn.c_str(), nullptr, nullptr)); + + botan_x509_cert_t cert; + TEST_FFI_OK( + botan_x509_pkcs10_req_sign, + (&cert, req, ca_cert, ca_key, rng, not_before, not_after, nullptr, hash_fn.c_str(), nullptr)); + + uint64_t test = 0; + TEST_FFI_OK(botan_x509_cert_not_after, (cert, &test)); + + botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks_from_cert; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_create_from_cert, (&req_ip_addr_blocks_from_cert, cert)); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_counts, + (req_ip_addr_blocks_from_cert, &v4_count, &v6_count)); + + result.test_eq("ext has v4 addresses", v4_count, 1); + result.test_eq("ext has no v6 addresses", v6_count, 0); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (req_ip_addr_blocks_from_cert, 0, 0, &has_safi, &safi, &present, &count)); + + result.confirm("index 0 does not have a safi", has_safi == 0); + result.confirm("index 0 has a value", present == 0); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_ip_addr_blocks_add_ip_addr, + (req_ip_addr_blocks_from_cert, min.data(), min.data(), 0, &safi)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_ip_addr_blocks_restrict, + (req_ip_addr_blocks_from_cert, 0, &safi)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_ip_addr_blocks_inherit, + (req_ip_addr_blocks_from_cert, 0, &safi)); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks_from_cert)); + + botan_x509_ext_as_blocks_t req_as_blocks_from_cert; + TEST_FFI_OK(botan_x509_ext_as_blocks_create_from_cert, (&req_as_blocks_from_cert, cert)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum, (req_as_blocks_from_cert, &present, &count)); + result.confirm("ext inherits asnum", present == 0); + + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_ext_as_blocks_get_asnum_at, + (req_as_blocks_from_cert, 0, &min_out_, &max_out_)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_ext_as_blocks_get_rdi, + (req_as_blocks_from_cert, &present, &count)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_ext_as_blocks_get_rdi_at, + (req_as_blocks_from_cert, 0, &min_out_, &max_out_)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_add_asnum, + (req_as_blocks_from_cert, 0, 1)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_restrict_asnum, + (req_as_blocks_from_cert)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_inherit_asnum, + (req_as_blocks_from_cert)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_add_rdi, + (req_as_blocks_from_cert, 0, 1)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_restrict_rdi, + (req_as_blocks_from_cert)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_inherit_rdi, + (req_as_blocks_from_cert)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (req_as_blocks_from_cert)); + + int rc; + TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (ca_builder)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (req_builder)); + TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req)); + TEST_FFI_OK(botan_x509_cert_destroy, (ca_cert)); + TEST_FFI_OK(botan_x509_cert_destroy, (cert)); + } + + TEST_FFI_OK(botan_privkey_destroy, (ca_key)); + TEST_FFI_OK(botan_privkey_destroy, (cert_key)); + } + } +}; + class FFI_ECDSA_Certificate_Test final : public FFI_Test { public: std::string name() const override { return "FFI ECDSA cert"; } @@ -4606,6 +5049,8 @@ BOTAN_REGISTER_TEST("ffi", "ffi_rsa_cert", FFI_RSA_Cert_Test); BOTAN_REGISTER_TEST("ffi", "ffi_zfec", FFI_ZFEC_Test); BOTAN_REGISTER_TEST("ffi", "ffi_crl", FFI_CRL_Test); BOTAN_REGISTER_TEST("ffi", "ffi_cert_validation", FFI_Cert_Validation_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_cert_creation", FFI_Cert_Creation_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_x509_rpki", FFI_X509_RPKI_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ecdsa_certificate", FFI_ECDSA_Certificate_Test); BOTAN_REGISTER_TEST("ffi", "ffi_pkcs_hashid", FFI_PKCS_Hashid_Test); BOTAN_REGISTER_TEST("ffi", "ffi_cbc_cipher", FFI_CBC_Cipher_Test); diff --git a/src/tests/test_x509_rpki.cpp b/src/tests/test_x509_rpki.cpp index 23954ecc9e8..b4d2adfd97d 100644 --- a/src/tests/test_x509_rpki.cpp +++ b/src/tests/test_x509_rpki.cpp @@ -11,10 +11,10 @@ #include #include #include + #include #include #include #include - #include #include #endif @@ -57,99 +57,96 @@ std::unique_ptr generate_key(const std::string& algo, Botan: } else if(algo == "Ed25519") { params = ""; } else if(algo == "RSA") { - params = "1536"; + params = "1024"; } return Botan::create_private_key(algo, rng, params); } -Botan::X509_Cert_Options ca_opts(const std::string& sig_padding = "") { - Botan::X509_Cert_Options opts("Test CA/US/Botan Project/Testing"); +Botan::CertificateParametersBuilder ca_params() { + Botan::CertificateParametersBuilder builder; - opts.uri = "https://botan.randombit.net"; - opts.dns = "botan.randombit.net"; - opts.email = "testing@randombit.net"; - opts.set_padding_scheme(sig_padding); + builder.add_common_name("RPKI Test CA") + .add_country("US") + .add_organization("Botan") + .add_dns("botan.randombit.net") + .set_as_ca_certificate(1); - opts.CA_key(1); - - return opts; + return builder; } -Botan::X509_Cert_Options req_opts(const std::string& algo, const std::string& sig_padding = "") { - Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing"); - - opts.uri = "https://botan.randombit.net"; - opts.dns = "botan.randombit.net"; - opts.email = "testing@randombit.net"; - opts.set_padding_scheme(sig_padding); - - opts.not_before("160101200000Z"); - opts.not_after("300101200000Z"); - - opts.challenge = "zoom"; +Botan::CertificateParametersBuilder user_params() { + Botan::CertificateParametersBuilder builder; - if(algo == "RSA") { - opts.constraints = Botan::Key_Constraints::KeyEncipherment; - } else if(algo == "DSA" || algo == "ECDSA" || algo == "ECGDSA" || algo == "ECKCDSA") { - opts.constraints = Botan::Key_Constraints::DigitalSignature; - } + builder.add_common_name("RPKI Test User") + .add_country("US") + .add_organization("Botan") + .add_allowed_usage(Botan::Key_Constraints::DigitalSignature); - return opts; + return builder; } -std::tuple get_sig_algo_padding() { +std::pair get_sig_algo() { #if defined(BOTAN_HAS_ECDSA) const std::string sig_algo{"ECDSA"}; - const std::string padding_method; const std::string hash_fn{"SHA-256"}; #elif defined(BOTAN_HAS_ED25519) const std::string sig_algo{"Ed25519"}; - const std::string padding_method; const std::string hash_fn{"SHA-512"}; #elif defined(BOTAN_HAS_RSA) const std::string sig_algo{"RSA"}; - const std::string padding_method{"PKCS1v15(SHA-256)"}; const std::string hash_fn{"SHA-256"}; #endif - return std::make_tuple(sig_algo, padding_method, hash_fn); + return std::make_pair(sig_algo, hash_fn); } -Botan::X509_Certificate make_self_signed(std::unique_ptr& rng, - const Botan::X509_Cert_Options& opts = std::move(ca_opts())) { - auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding(); - auto key = generate_key(sig_algo, *rng); - const auto cert = Botan::X509::create_self_signed_cert(opts, *key, hash_fn, *rng); +Botan::X509_Certificate make_self_signed(Botan::RandomNumberGenerator& rng, + const Botan::CertificateParametersBuilder& params) { + auto [sig_algo, hash_fn] = get_sig_algo(); + auto key = generate_key(sig_algo, rng); + + const auto now = std::chrono::system_clock::now(); + const auto not_before = now - std::chrono::seconds(180); + const auto not_after = now + std::chrono::seconds(86400); - return cert; + return params.into_self_signed_cert(not_before, not_after, *key, rng, hash_fn); } -CA_Creation_Result make_ca(std::unique_ptr& rng, - const Botan::X509_Cert_Options& opts = std::move(ca_opts())) { - auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding(); - auto ca_key = generate_key(sig_algo, *rng); - const auto ca_cert = Botan::X509::create_self_signed_cert(opts, *ca_key, hash_fn, *rng); - Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng); - auto sub_key = generate_key(sig_algo, *rng); +CA_Creation_Result make_ca(Botan::RandomNumberGenerator& rng, const Botan::CertificateParametersBuilder& params) { + auto [sig_algo, hash_fn] = get_sig_algo(); + auto ca_key = generate_key(sig_algo, rng); + + const auto now = std::chrono::system_clock::now(); + const auto not_before = now - std::chrono::seconds(180); + const auto not_after = now + std::chrono::seconds(86400); + + const auto ca_cert = params.into_self_signed_cert(not_before, not_after, *ca_key, rng, hash_fn); + + Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, rng); + auto sub_key = generate_key(sig_algo, rng); return CA_Creation_Result{ca_cert, std::move(ca), std::move(sub_key), sig_algo, hash_fn}; } -std::pair make_and_sign_ca( - std::unique_ptr ext, - Botan::X509_CA& parent_ca, - std::unique_ptr& rng) { - auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding(); +CA_Creation_Result make_ca(Botan::RandomNumberGenerator& rng) { + return make_ca(rng, ca_params()); +} + +std::pair make_and_sign_ca(std::unique_ptr ext, + Botan::X509_CA& parent_ca, + Botan::RandomNumberGenerator& rng) { + auto [sig_algo, hash_fn] = get_sig_algo(); + + auto params = ca_params(); + params.add_extension(std::move(ext)); - Botan::X509_Cert_Options opts = ca_opts(); - opts.extensions.add(std::move(ext)); + std::unique_ptr key = generate_key(sig_algo, rng); - std::unique_ptr key = generate_key(sig_algo, *rng); + Botan::PKCS10_Request req = params.into_pkcs10_request(*key, rng, hash_fn); - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, hash_fn, *rng); - Botan::X509_Certificate cert = parent_ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); - Botan::X509_CA ca(cert, *key, hash_fn, padding_method, *rng); + Botan::X509_Certificate cert = parent_ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + Botan::X509_CA ca(cert, *key, hash_fn, rng); return std::make_pair(std::move(cert), std::move(ca)); } @@ -394,10 +391,7 @@ Test::Result test_x509_ip_addr_blocks_rfc3779_example() { blocks_1->add_address({10, 3, 0, 0}, {10, 3, 255, 255}, 1); blocks_1->inherit(); - Botan::X509_Cert_Options opts_1 = ca_opts(); - opts_1.extensions.add(std::move(blocks_1)); - - auto cert_1 = make_self_signed(rng, opts_1); + auto cert_1 = make_self_signed(*rng, ca_params().add_extension(std::move(blocks_1))); auto bits_1 = cert_1.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); @@ -441,10 +435,7 @@ Test::Result test_x509_ip_addr_blocks_rfc3779_example() { blocks_2->add_address({172, 16, 0, 0}, {172, 31, 255, 255}, 1); blocks_2->inherit(2); - Botan::X509_Cert_Options opts_2 = ca_opts(); - opts_2.extensions.add(std::move(blocks_2)); - - auto cert_2 = make_self_signed(rng, opts_2); + auto cert_2 = make_self_signed(*rng, ca_params().add_extension(std::move(blocks_2))); auto bits_2 = cert_2.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); @@ -508,10 +499,7 @@ Test::Result test_x509_ip_addr_blocks_encode_builder() { blocks->add_address({123, 123, 2, 1}); - Botan::X509_Cert_Options opts = ca_opts(); - opts.extensions.add(std::move(blocks)); - - auto cert = make_self_signed(rng, opts); + auto cert = make_self_signed(*rng, ca_params().add_extension(std::move(blocks))); auto bits = cert.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); // hand validated with https://lapo.it/asn1js/ @@ -545,7 +533,7 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_ctor() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); for(size_t i = 0; i < 64; i++) { bool push_ipv4_ranges = bit_set<0>(i); @@ -555,8 +543,6 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_ctor() { bool push_ipv4_family = bit_set<4>(i); bool push_ipv6_family = bit_set<5>(i); - Botan::X509_Cert_Options opts = req_opts(sig_algo); - std::vector a = {123, 123, 2, 1}; auto ipv4_1 = IPAddressBlocks::IPAddress(a); a = {255, 255, 255, 255}; @@ -641,9 +627,7 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_ctor() { std::unique_ptr blocks = std::make_unique(addr_blocks); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); @@ -722,7 +706,7 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_edge_cases_ctor() { std::vector edge_values = {0, 2, 4, 8, 16, 32, 64, 128, 1, 3, 7, 15, 31, 63, 127, 255, 12, 46, 123, 160, 234}; - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); for(size_t i = 0; i < edge_values.size(); i++) { for(size_t j = 0; j < 4; j++) { @@ -736,8 +720,6 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_edge_cases_ctor() { break; } - Botan::X509_Cert_Options opts = req_opts(sig_algo); - std::vector min_bytes(16, 0x00); std::vector max_bytes(16, 0xFF); @@ -765,9 +747,7 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_edge_cases_ctor() { std::unique_ptr blocks = std::make_unique(addr_blocks); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); @@ -797,8 +777,7 @@ Test::Result test_x509_ip_addr_blocks_range_merge() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); - Botan::X509_Cert_Options opts = req_opts(sig_algo); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); std::vector>> addresses = { {{11, 0, 0, 0}, {{11, 0, 0, 0}}}, @@ -828,9 +807,7 @@ Test::Result test_x509_ip_addr_blocks_range_merge() { std::unique_ptr blocks = std::make_unique(addr_blocks); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); @@ -860,8 +837,7 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); - Botan::X509_Cert_Options opts = req_opts(sig_algo); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); std::vector addr_blocks; @@ -916,9 +892,7 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { std::unique_ptr blocks = std::make_unique(addr_blocks); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); @@ -1084,12 +1058,9 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success_builder() { {0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x0D, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, *rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); @@ -1115,11 +1086,11 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success_builder() { dyn_blocks->inherit(); } - auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, *rng); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); - Botan::X509_Certificate sub_cert = - dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + const auto sub_req = user_params().add_extension(sub_blocks->copy()).into_pkcs10_request(*sub_key, *rng, hash_fn); + + const auto sub_cert = dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); const Botan::Path_Validation_Restrictions restrictions(false, 80); std::vector certs = {sub_cert, dyn_cert, inherit_cert}; @@ -1249,12 +1220,9 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success_ctor() { auto sub_addr_blocks = {sub_ipv4_family, sub_ipv6_family}; std::unique_ptr sub_blocks = std::make_unique(sub_addr_blocks); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, *rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); @@ -1274,11 +1242,10 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success_ctor() { auto dyn_addr_blocks = {dyn_ipv4_family, dyn_ipv6_family}; std::unique_ptr dyn_blocks = std::make_unique(dyn_addr_blocks); - auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, *rng); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); - Botan::X509_Certificate sub_cert = - dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + const auto sub_req = user_params().add_extension(sub_blocks->copy()).into_pkcs10_request(*sub_key, *rng, hash_fn); + const auto sub_cert = dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); const Botan::Path_Validation_Restrictions restrictions(false, 80); std::vector certs = {sub_cert, dyn_cert, inherit_cert}; @@ -1315,11 +1282,11 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_builder() { root_blocks->inherit(42); } - Botan::X509_Cert_Options root_opts = ca_opts(); + auto root_params = ca_params(); if(!nullptr_extensions) { - root_opts.extensions.add(std::move(root_blocks)); + root_params.add_extension(std::move(root_blocks)); } - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(*rng, root_params); // Issuer Cert std::unique_ptr iss_blocks = std::make_unique(); @@ -1333,7 +1300,7 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_builder() { iss_blocks->inherit(42); } - auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, rng); + auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, *rng); // Subject cert std::unique_ptr sub_blocks = std::make_unique(); @@ -1354,10 +1321,7 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_builder() { sub_blocks->inherit(safi); } - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + auto sub_req = user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = iss_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -1405,11 +1369,11 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_ctor() { auto root_addr_blocks = {root_family}; std::unique_ptr root_blocks = std::make_unique(root_addr_blocks); - Botan::X509_Cert_Options root_opts = ca_opts(); + auto root_params = ca_params(); if(!nullptr_extensions) { - root_opts.extensions.add(std::move(root_blocks)); + root_params.add_extension(std::move(root_blocks)); } - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(*rng, root_params); // Issuer Cert a = {122, 0, 0, 255}; @@ -1428,7 +1392,7 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_ctor() { auto iss_family = IPAddressBlocks::IPAddressFamily(iss_choice, 42); auto iss_addr_blocks = {iss_family}; std::unique_ptr iss_blocks = std::make_unique(iss_addr_blocks); - auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, rng); + auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, *rng); // Subject cert if(too_small_subrange) { @@ -1457,10 +1421,7 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_ctor() { auto sub_addr_blocks = {sub_family}; std::unique_ptr sub_blocks = std::make_unique(sub_addr_blocks); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + auto sub_req = user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = iss_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -1492,10 +1453,7 @@ Test::Result test_x509_as_blocks_rfc3779_example() { blocks->add_asnum(5001); blocks->inherit_rdi(); - Botan::X509_Cert_Options opts = ca_opts(); - opts.extensions.add(std::move(blocks)); - - auto cert = make_self_signed(rng, opts); + auto cert = make_self_signed(*rng, ca_params().add_extension(std::move(blocks))); auto bits = cert.v3_extensions().get_extension_bits(ASBlocks::static_oid()); result.test_eq( @@ -1529,10 +1487,7 @@ Test::Result test_x509_as_blocks_encode_builder() { // this overwrites the previous two blocks->restrict_asnum(); - Botan::X509_Cert_Options opts = ca_opts(); - opts.extensions.add(std::move(blocks)); - - auto cert = make_self_signed(rng, opts); + auto cert = make_self_signed(*rng, ca_params().add_extension(std::move(blocks))); auto bits = cert.v3_extensions().get_extension_bits(ASBlocks::static_oid()); result.test_eq("extension is encoded as specified", bits, "3011A0023000A10B300930070201090202012D"); @@ -1549,7 +1504,7 @@ Test::Result test_x509_as_blocks_extension_encode_ctor() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); for(size_t i = 0; i < 16; i++) { bool push_asnum = bit_set<0>(i); @@ -1590,10 +1545,7 @@ Test::Result test_x509_as_blocks_extension_encode_ctor() { std::unique_ptr blocks = std::make_unique(ident); - Botan::X509_Cert_Options opts = req_opts(sig_algo); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { @@ -1648,8 +1600,7 @@ Test::Result test_x509_as_blocks_range_merge() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); - Botan::X509_Cert_Options opts = req_opts(sig_algo); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); std::vector> ranges = { {2005, 37005}, @@ -1673,9 +1624,7 @@ Test::Result test_x509_as_blocks_range_merge() { std::unique_ptr blocks = std::make_unique(ident); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + const auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* as_blocks = cert.v3_extensions().get_extension_object_as(); @@ -1741,12 +1690,9 @@ Test::Result test_x509_as_blocks_path_validation_success_builder() { sub_blocks->add_rdi(1567); sub_blocks->add_rdi(33100, 40000); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, *rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); @@ -1771,9 +1717,9 @@ Test::Result test_x509_as_blocks_path_validation_success_builder() { dyn_blocks->inherit_rdi(); } - auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, *rng); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + const auto sub_req = user_params().add_extension(sub_blocks->copy()).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -1880,12 +1826,9 @@ Test::Result test_x509_as_blocks_path_validation_success_ctor() { ASBlocks::ASIdentifiers sub_ident = ASBlocks::ASIdentifiers(sub_asnum, sub_rdi); std::unique_ptr sub_blocks = std::make_unique(sub_ident); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, *rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); @@ -1901,9 +1844,9 @@ Test::Result test_x509_as_blocks_path_validation_success_ctor() { ASBlocks::ASIdentifiers dyn_ident = ASBlocks::ASIdentifiers(dyn_asnum, dyn_rdi); std::unique_ptr dyn_blocks = std::make_unique(dyn_ident); - auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, *rng); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + const auto sub_req = user_params().add_extension(sub_blocks->copy()).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -1938,11 +1881,8 @@ Test::Result test_x509_as_blocks_path_validation_extension_not_present_builder() sub_blocks->add_rdi(33100, 40000); // create a root ca that does not have any extension - Botan::X509_Cert_Options root_opts = ca_opts(); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(*rng, ca_params()); + auto sub_req = user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = root_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); Botan::Certificate_Store_In_Memory trusted; @@ -1996,11 +1936,8 @@ Test::Result test_x509_as_blocks_path_validation_extension_not_present_ctor() { std::unique_ptr sub_blocks = std::make_unique(sub_ident); // create a root ca that does not have any extension - Botan::X509_Cert_Options root_opts = ca_opts(); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(*rng, ca_params()); + const auto sub_req = user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = root_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); Botan::Certificate_Store_In_Memory trusted; @@ -2193,14 +2130,12 @@ Test::Result test_x509_as_blocks_path_validation_failure_builder() { sub_blocks->inherit_rdi(); } - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, *rng); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + const auto sub_req = + user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = issu_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -2388,14 +2323,12 @@ Test::Result test_x509_as_blocks_path_validation_failure_ctor() { ASBlocks::ASIdentifiers sub_ident = ASBlocks::ASIdentifiers(sub_asnum, sub_rdi); std::unique_ptr sub_blocks = std::make_unique(sub_ident); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, *rng); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + const auto sub_req = + user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = issu_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));