From 6cf5f35f677cc05be912de7b8188ca794a21b67d Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Mon, 5 Jan 2026 15:54:19 +0100 Subject: [PATCH 1/3] Doc: explain BOTAN_FFI_ERROR_NO_VALUE --- doc/api_ref/ffi.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index f5a3e7f3e70..3820c7100e8 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -103,6 +103,11 @@ 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 + + Given the context of the invocation no semantically reasonable value could + be produced, any provided out-parameters must be ignored. + .. cpp:enumerator:: BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE = -10 Functions which write a variable amount of space return this if the indicated From d2a0e4fbdd6baf4b4f9e9d562867e55248a44b65 Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Mon, 22 Dec 2025 15:14:22 +0100 Subject: [PATCH 2/3] Add generic value getters for X.509 objects in FFI Namely: * botan_x509_cert_view_binary_values * botan_x509_cert_view_string_values * botan_x509_crl_view_binary_values * botan_x509_crl_view_string_values This allows fetching standard information from X.509 objects such as the serial number of both a certificate and a CRL but also more specific information like OCSP responder URLs. Some getter types may have more than one value. For such cases, the getters have an index parameter to enumerate all elements. --- doc/api_ref/ffi.rst | 137 ++++++++++++++++++++ src/lib/ffi/ffi.h | 94 ++++++++++++++ src/lib/ffi/ffi_cert.cpp | 274 ++++++++++++++++++++++++++++++++++++++- src/tests/test_ffi.cpp | 164 +++++++++++++++++++++++ 4 files changed, 668 insertions(+), 1 deletion(-) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index 3820c7100e8..08bd3bdc38e 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -1689,6 +1689,38 @@ X.509 Certificates const char* common_name, \ const char* org_name) +.. cpp:function:: int botan_x509_cert_view_binary_values(botan_x509_cert_t cert, \ + botan_x509_value_type value_type, \ + size_t index, \ + botan_view_ctx ctx, \ + botan_view_bin_fn view_fn) + + Access various binary fields of information contained in the certificate. + + Some of those may be multi-value fields, the `index` parameter may be used + to enumerate such values until :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` + is returned. For singular values, an `index` of 0 must be used. + + See :ref:`x509_getter_function` for further information about the available + values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` + is returned. + +.. cpp:function:: int botan_x509_cert_view_string_values(botan_x509_cert_t cert, \ + botan_x509_value_type value_type, \ + size_t index, \ + botan_view_ctx ctx, \ + botan_view_str_fn view_fn) + + Access various string fields of information contained in the certificate. + + Some of those may be multi-value fields, the `index` parameter may be used + to enumerate such values until :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` + is returned. For singular values, an `index` of 0 must be used. + + See :ref:`x509_getter_function` for further information about the available + values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` + is returned. + .. 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 @@ -1989,6 +2021,38 @@ X.509 Certificate Revocation Lists is technically optional in CRLs, if the CRL does not specify a "next update" timestamp, :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` is returned. +.. cpp:function:: int botan_x509_crl_view_binary_values(botan_x509_crl_t crl, \ + botan_x509_value_type value_type, \ + size_t index, \ + botan_view_ctx ctx, \ + botan_view_bin_fn view_fn) + + Access various binary fields of information contained in the CRL. + + Some of those may be multi-value fields, the `index` parameter may be used + to enumerate such values until :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` + is returned. For singular values, an `index` of 0 must be used. + + See :ref:`x509_getter_function` for further information about the available + values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` + is returned. + +.. cpp:function:: int botan_x509_crl_view_string_values(botan_x509_crl_t crl, \ + botan_x509_value_type value_type, \ + size_t index, \ + botan_view_ctx ctx, \ + botan_view_str_fn view_fn) + + Access various string fields of information contained in the CRL. + + Some of those may be multi-value fields, the `index` parameter may be used + to enumerate such values until :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` + is returned. For singular values, an `index` of 0 must be used. + + See :ref:`x509_getter_function` for further information about the available + values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` + is returned. + .. cpp:function:: int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert) Check whether a given ``crl`` contains a given ``cert``. @@ -2027,6 +2091,79 @@ X.509 Certificate Revocation Lists Destroy the CRL entry object. +.. _x509_getter_function: + +X.509 Available Generic Getter Values +---------------------------------------- + +Most X.509 objects may contain various data fields that may be of interest for +using applications. Many of those values can be queried through a generic API +that is extensible without introducing ABI incompatibilities. + +All available value types of the generic X.509 object getters are: + +.. cpp:enumerator:: BOTAN_X509_SERIAL_NUMBER + + The binary big-endian encoded serial number of a certificate or CRL. + +.. cpp:enumerator:: BOTAN_X509_SUBJECT_DN_BITS + + The DER encoded subject distinguished name of the certificate. + +.. cpp:enumerator:: BOTAN_X509_ISSUER_DN_BITS + + The DER encoded issuer distinguished name of a certificate or CRL. + +.. cpp:enumerator:: BOTAN_X509_SUBJECT_KEY_IDENTIFIER + + The subject key identifier (usually a hash of the certificate's public key) + in binary format. + +.. cpp:enumerator:: BOTAN_X509_AUTHORITY_KEY_IDENTIFIER + + The issuer's key identifier (usually a hash of the issuer's public key) in + binary format. + +.. cpp:enumerator:: BOTAN_X509_PUBLIC_KEY_PKCS8_BITS + + The certificate's public key in PKCS#8 format (DER encoding). + +.. cpp:enumerator:: BOTAN_X509_TBS_DATA_BITS + + The "To-Be-Signed" data of a certificate or CRL (DER encoding). + +.. cpp:enumerator:: BOTAN_X509_SIGNATURE_SCHEME_BITS + + The signature scheme descriptor of a certificate or CRL (DER encoding). + +.. cpp:enumerator:: BOTAN_X509_SIGNATURE_BITS + + The raw signature data of a certificate or CRL. The encoding depends on the + signature algorithm but is always in binary format. + +.. cpp:enumerator:: BOTAN_X509_DER_ENCODING + + The binary DER encoding of the entire certificate or CRL object. + +.. cpp:enumerator:: BOTAN_X509_PEM_ENCODING + + The string-based PEM encoding of the entire certificate or CRL object. + +.. cpp:enumerator:: BOTAN_X509_CRL_DISTRIBUTION_URLS + + The CRL distribution points (URLs) noted in the certificate as a character + array. There might be more than one such URL defined in a certificate. + +.. cpp:enumerator:: BOTAN_X509_OCSP_RESPONDER_URLS + + The OCSP responders (URLs) noted in the certificate as a character array. + There might be more than one such URL defined in a certificate. + +.. cpp:enumerator:: BOTAN_X509_CA_ISSUERS_URLS + + The URLs of the issuing CA certificate of a certificate as a character array. + There might be more than one such URL defined in a certificate. + ZFEC (Forward Error Correction) ---------------------------------------- diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 788b9345578..c8a715e051c 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2228,6 +2228,39 @@ int botan_mceies_decrypt(botan_privkey_t mce_key, typedef struct botan_x509_cert_struct* botan_x509_cert_t; +/** + * Generic values that may be retrieved from X.509 certificates or CRLs via + * the generic getter functions. + * + * When extending this list the existing entries must stay backward-compatible + * to remain ABI compatible across versions. Therefore, new values must be added + * to the end of this list. + * + * See: + * * botan_x509_cert_view_binary_values() + * * botan_x509_crl_view_binary_values() + * * botan_x509_cert_view_string_values() + */ +typedef enum /* NOLINT(*-enum-size) */ { + BOTAN_X509_SERIAL_NUMBER = 0, /** singleton binary big-endian encoding */ + BOTAN_X509_SUBJECT_DN_BITS = 1, /** singleton binary DER encoding of the subject distinguished name */ + BOTAN_X509_ISSUER_DN_BITS = 2, /** singleton binary DER encoding of the issuer distinguished name */ + BOTAN_X509_SUBJECT_KEY_IDENTIFIER = 3, /** singleton binary encoding */ + BOTAN_X509_AUTHORITY_KEY_IDENTIFIER = 4, /** singleton binary encoding */ + + BOTAN_X509_PUBLIC_KEY_PKCS8_BITS = 200, /** singleton binary DER encoding of the PKCS#8 public key */ + BOTAN_X509_TBS_DATA_BITS = 201, /** singleton binary DER encoding */ + BOTAN_X509_SIGNATURE_SCHEME_BITS = 202, /** singleton binary DER encoding of the algorithm identifier */ + BOTAN_X509_SIGNATURE_BITS = 203, /** singleton binary signature bits */ + + BOTAN_X509_DER_ENCODING = 300, /** singleton binary DER encoding of the whole object */ + BOTAN_X509_PEM_ENCODING = 301, /** singleton string value PEM encoding of the whole object */ + + BOTAN_X509_CRL_DISTRIBUTION_URLS = 400, /** multi-value string of the CRL distribution points */ + BOTAN_X509_OCSP_RESPONDER_URLS = 401, /** multi-value string of the OCSP responder URLs */ + BOTAN_X509_CA_ISSUERS_URLS = 402, /** multi-value string of the CA issuer URLs */ +} botan_x509_value_type; + BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert[], size_t cert_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* filename); @@ -2238,6 +2271,34 @@ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_destroy(botan_x509_cert_t cert); BOTAN_FFI_EXPORT(2, 8) int botan_x509_cert_dup(botan_x509_cert_t* new_cert, botan_x509_cert_t cert); +/** + * Retrieve a specific binary value from an X.509 certificate. + * + * For multi-values @p index allows enumerating the available entries, until + * BOTAN_FFI_ERROR_OUT_OF_RANGE is returned. For singleton values, an @p index + * of value "0" is expected. + * + * @returns BOTAN_FFI_ERROR_NO_VALUE if the provided @p cert does not provide + * the requested @p value_type at all or not in binary format. + */ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_view_binary_values( + botan_x509_cert_t cert, botan_x509_value_type value_type, size_t index, botan_view_ctx ctx, botan_view_bin_fn view); + +/** + * Retrieve a specific string value from an X.509 certificate. + * + * For multi-values @p index allows enumerating the available entries, until + * BOTAN_FFI_ERROR_OUT_OF_RANGE is returned. For singleton values, an @p index + * of value "0" is expected. + * + * @returns BOTAN_FFI_ERROR_NO_VALUE if the provided @p cert does not provide + * the requested @p value_type at all or not in string format. + */ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_view_string_values( + botan_x509_cert_t cert, botan_x509_value_type value_type, size_t index, botan_view_ctx ctx, botan_view_str_fn view); + /* Prefer botan_x509_cert_not_before and botan_x509_cert_not_after */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_time_expires(botan_x509_cert_t cert, char out[], size_t* out_len); @@ -2487,6 +2548,39 @@ BOTAN_FFI_EXPORT(3, 11) int botan_x509_crl_next_update(botan_x509_crl_t crl, uin BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_destroy(botan_x509_crl_t crl); +/** + * Retrieve a specific binary value from an X.509 certificate revocation list. + * + * For multi-values @p index allows enumerating the available entries, until + * BOTAN_FFI_ERROR_OUT_OF_RANGE is returned. For singleton values, an @p index + * of value "0" is expected. + * + * @returns BOTAN_FFI_ERROR_NO_VALUE if the provided @p crl_obj does not provide + * the requested @p value_type at all or not in binary format. + */ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_view_binary_values(botan_x509_crl_t crl_obj, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_bin_fn view); + +/** + * Retrieve a specific string value from an X.509 certificate revocation list. + * + * For multi-values @p index allows enumerating the available entries, until + * BOTAN_FFI_ERROR_OUT_OF_RANGE is returned. For singleton values, an @p index + * of value "0" is expected. + * + * @returns BOTAN_FFI_ERROR_NO_VALUE if the provided @p crl_obj does not provide + * the requested @p value_type at all or not in string format. + */ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_view_string_values(botan_x509_crl_t crl_obj, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_str_fn view); /** * Given a CRL and a certificate, * check if the certificate is revoked on that particular CRL diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 689f50c0b32..e6f2e6adaf9 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -6,18 +6,21 @@ #include +#include #include #include #include #if defined(BOTAN_HAS_X509_CERTIFICATES) - #include #include #include + #include #include #include #include #include + #include + #include #endif #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -175,6 +178,183 @@ int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert_bits[], return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } +} + +namespace { + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +int botan_x509_object_view_value(const Botan::X509_Object& object, + botan_x509_value_type value_type, + botan_view_ctx ctx, + botan_view_str_fn view_fn) { + auto view = [=](const std::string& value) { return invoke_view_callback(view_fn, ctx, value); }; + + switch(value_type) { + case BOTAN_X509_PEM_ENCODING: + return view(object.PEM_encode()); + default: + BOTAN_ASSERT_UNREACHABLE(); /* called with unexpected (non-generic) value_type */ + } +} + +int botan_x509_object_view_value(const Botan::X509_Object& object, + botan_x509_value_type value_type, + botan_view_ctx ctx, + botan_view_bin_fn view_fn) { + auto view = [=](std::span value) { return invoke_view_callback(view_fn, ctx, value); }; + + switch(value_type) { + case BOTAN_X509_TBS_DATA_BITS: + return view(object.tbs_data()); + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + return view(object.signature_algorithm().BER_encode()); + case BOTAN_X509_SIGNATURE_BITS: + return view(object.signature()); + case BOTAN_X509_DER_ENCODING: + return view(object.BER_encode()); + default: + BOTAN_ASSERT_UNREACHABLE(); /* called with unexpected (non-generic) value_type */ + } +} + +#endif + +} // namespace + +extern "C" { + +int botan_x509_cert_view_binary_values(botan_x509_cert_t cert, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_bin_fn view_fn) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(index != 0) { + // As of now there are no multi-value binary entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + auto view = [=](std::span value) -> int { + if(value.empty()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } else { + return invoke_view_callback(view_fn, ctx, value); + } + }; + + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) -> int { + switch(value_type) { + case BOTAN_X509_SERIAL_NUMBER: + return view(c.serial_number()); + case BOTAN_X509_SUBJECT_DN_BITS: + return view(c.raw_subject_dn()); + case BOTAN_X509_ISSUER_DN_BITS: + return view(c.raw_issuer_dn()); + case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: + return view(c.subject_key_id()); + case BOTAN_X509_AUTHORITY_KEY_IDENTIFIER: + return view(c.authority_key_id()); + case BOTAN_X509_PUBLIC_KEY_PKCS8_BITS: + return view(c.subject_public_key_info()); + + case BOTAN_X509_TBS_DATA_BITS: + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + case BOTAN_X509_SIGNATURE_BITS: + case BOTAN_X509_DER_ENCODING: + return botan_x509_object_view_value(c, value_type, ctx, view_fn); + + case BOTAN_X509_PEM_ENCODING: + case BOTAN_X509_CRL_DISTRIBUTION_URLS: + case BOTAN_X509_OCSP_RESPONDER_URLS: + case BOTAN_X509_CA_ISSUERS_URLS: + return BOTAN_FFI_ERROR_NO_VALUE; + } + + return BOTAN_FFI_ERROR_BAD_PARAMETER; + }); +#else + BOTAN_UNUSED(cert, value_type, index, ctx, view_fn); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_view_string_values(botan_x509_cert_t cert, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_str_fn view_fn) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + auto enumerate = [view_fn, ctx](auto values, size_t idx) -> int { + if(idx >= values.size()) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } else { + return invoke_view_callback(view_fn, ctx, values[idx]); + } + }; + + auto enumerate_crl_distribution_points = [view_fn, ctx](const Botan::X509_Certificate& c, size_t idx) -> int { + const auto* crl_dp_ext = + c.v3_extensions().get_extension_object_as(); + if(crl_dp_ext == nullptr) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; // essentially an empty list + } + + const auto& dps = crl_dp_ext->distribution_points(); + for(size_t i = idx; const auto& dp : dps) { + const auto& uris = dp.point().uris(); + if(i >= uris.size()) { + i -= uris.size(); + continue; + } + + auto itr = uris.begin(); + std::advance(itr, i); + return invoke_view_callback(view_fn, ctx, *itr); + } + + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + }; + + auto wrap_or_empty = [](std::string str) -> std::vector { + if(str.empty()) { + return {}; + } else { + return {std::move(str)}; + } + }; + + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) -> int { + switch(value_type) { + case BOTAN_X509_CRL_DISTRIBUTION_URLS: + return enumerate_crl_distribution_points(c, index); + case BOTAN_X509_OCSP_RESPONDER_URLS: + return enumerate(wrap_or_empty(c.ocsp_responder()), index); + case BOTAN_X509_CA_ISSUERS_URLS: + return enumerate(c.ca_issuers(), index); + case BOTAN_X509_PEM_ENCODING: + return botan_x509_object_view_value(c, value_type, ctx, view_fn); + + case BOTAN_X509_SERIAL_NUMBER: + case BOTAN_X509_SUBJECT_DN_BITS: + case BOTAN_X509_ISSUER_DN_BITS: + case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: + case BOTAN_X509_AUTHORITY_KEY_IDENTIFIER: + case BOTAN_X509_PUBLIC_KEY_PKCS8_BITS: + case BOTAN_X509_TBS_DATA_BITS: + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + case BOTAN_X509_SIGNATURE_BITS: + case BOTAN_X509_DER_ENCODING: + return BOTAN_FFI_ERROR_NO_VALUE; + } + + return BOTAN_FFI_ERROR_BAD_PARAMETER; + }); +#else + BOTAN_UNUSED(cert, value_type, index, ctx, view_fn); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} int botan_x509_cert_is_ca(botan_x509_cert_t cert) { #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -867,6 +1047,98 @@ int botan_x509_crl_destroy(botan_x509_crl_t crl) { #endif } +int botan_x509_crl_view_binary_values(botan_x509_crl_t crl_obj, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_bin_fn view_fn) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(index != 0) { + // As of now there are no multi-value binary entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + auto view = [=](std::span value) -> int { + if(value.empty()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } else { + return invoke_view_callback(view_fn, ctx, value); + } + }; + + return BOTAN_FFI_VISIT(crl_obj, [=](const Botan::X509_CRL& crl) -> int { + switch(value_type) { + case BOTAN_X509_SERIAL_NUMBER: + return view(Botan::store_be(crl.crl_number())); + case BOTAN_X509_ISSUER_DN_BITS: + return view(Botan::ASN1::put_in_sequence(crl.issuer_dn().get_bits())); + case BOTAN_X509_AUTHORITY_KEY_IDENTIFIER: + return view(crl.authority_key_id()); + + case BOTAN_X509_TBS_DATA_BITS: + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + case BOTAN_X509_SIGNATURE_BITS: + case BOTAN_X509_DER_ENCODING: + return botan_x509_object_view_value(crl, value_type, ctx, view_fn); + + case BOTAN_X509_SUBJECT_DN_BITS: + case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: + case BOTAN_X509_PUBLIC_KEY_PKCS8_BITS: + case BOTAN_X509_PEM_ENCODING: + case BOTAN_X509_CRL_DISTRIBUTION_URLS: + case BOTAN_X509_OCSP_RESPONDER_URLS: + case BOTAN_X509_CA_ISSUERS_URLS: + return BOTAN_FFI_ERROR_NO_VALUE; + } + + return BOTAN_FFI_ERROR_BAD_PARAMETER; + }); +#else + BOTAN_UNUSED(crl_obj, value_type, index, ctx, view_fn); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_view_string_values(botan_x509_crl_t crl_obj, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(index != 0) { + // As of now there are no multi-value string entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + return BOTAN_FFI_VISIT(crl_obj, [=](const Botan::X509_CRL& crl) -> int { + switch(value_type) { + case BOTAN_X509_PEM_ENCODING: + return botan_x509_object_view_value(crl, value_type, ctx, view); + + case BOTAN_X509_SERIAL_NUMBER: + case BOTAN_X509_SUBJECT_DN_BITS: + case BOTAN_X509_ISSUER_DN_BITS: + case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: + case BOTAN_X509_AUTHORITY_KEY_IDENTIFIER: + case BOTAN_X509_PUBLIC_KEY_PKCS8_BITS: + case BOTAN_X509_TBS_DATA_BITS: + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + case BOTAN_X509_SIGNATURE_BITS: + case BOTAN_X509_DER_ENCODING: + case BOTAN_X509_CRL_DISTRIBUTION_URLS: + case BOTAN_X509_OCSP_RESPONDER_URLS: + case BOTAN_X509_CA_ISSUERS_URLS: + return BOTAN_FFI_ERROR_NO_VALUE; + } + + return BOTAN_FFI_ERROR_BAD_PARAMETER; + }); +#else + BOTAN_UNUSED(crl_obj, value_type, index, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(crl, [=](const auto& c) { return c.is_revoked(safe_get(cert)) ? 0 : -1; }); diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 76ea0a44539..d08e9d30972 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -724,6 +724,15 @@ class FFI_CRL_Test final : public FFI_Test { TEST_FFI_RC(BOTAN_FFI_ERROR_NULL_POINTER, botan_x509_crl_this_update, (bytecrl, nullptr)); TEST_FFI_RC(BOTAN_FFI_ERROR_NULL_POINTER, botan_x509_crl_next_update, (bytecrl, nullptr)); + ViewBytesSink akid; + TEST_FFI_OK(botan_x509_crl_view_binary_values, + (bytecrl, BOTAN_X509_AUTHORITY_KEY_IDENTIFIER, 0, akid.delegate(), akid.callback())); + result.test_bin_eq("authority key ID", akid.get(), "4ACF102F238FAB555A3F2732E2811CE7444C81F9"); + + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_crl_view_binary_values, + (bytecrl, BOTAN_X509_SUBJECT_KEY_IDENTIFIER, 0, akid.delegate(), akid.callback())); + botan_x509_crl_t crl; REQUIRE_FFI_OK(botan_x509_crl_load_file, (&crl, Test::data_file("x509/nist/root.crl").c_str())); @@ -785,6 +794,19 @@ class FFI_CRL_Test final : public FFI_Test { TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, botan_x509_crl_entries, (crl, 1, &entry)); TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, botan_x509_crl_entries, (bytecrl, 0, &entry)); + ViewStringSink crl_pem; + TEST_FFI_OK(botan_x509_crl_view_string_values, + (bytecrl, BOTAN_X509_PEM_ENCODING, 0, crl_pem.delegate(), crl_pem.callback())); + + auto remove_newlines = [](std::string_view str) { + auto out = std::string(str); + std::erase(out, '\n'); + std::erase(out, '\r'); + return out; + }; + + result.test_str_eq("CRL PEM", remove_newlines(crl_pem.get()), remove_newlines(crl_string)); + TEST_FFI_OK(botan_x509_crl_destroy, (crl)); TEST_FFI_OK(botan_x509_crl_destroy, (bytecrl)); TEST_FFI_OK(botan_x509_crl_destroy, (crl_without_next_update)); @@ -803,6 +825,57 @@ class FFI_Cert_Validation_Test final : public FFI_Test { #endif } + void verify_bare_pkcs1_rsa_signature(Test::Result& result, botan_x509_cert_t ee, botan_x509_cert_t ca) { + ViewBytesSink tbs_data; + ViewBytesSink sig_scheme; + ViewBytesSink signature; + ViewBytesSink public_key; + + TEST_FFI_OK(botan_x509_cert_view_binary_values, + (ee, BOTAN_X509_TBS_DATA_BITS, 0, tbs_data.delegate(), tbs_data.callback())); + TEST_FFI_OK(botan_x509_cert_view_binary_values, + (ee, BOTAN_X509_SIGNATURE_SCHEME_BITS, 0, sig_scheme.delegate(), sig_scheme.callback())); + TEST_FFI_OK(botan_x509_cert_view_binary_values, + (ee, BOTAN_X509_SIGNATURE_BITS, 0, signature.delegate(), signature.callback())); + TEST_FFI_OK(botan_x509_cert_view_binary_values, + (ca, BOTAN_X509_PUBLIC_KEY_PKCS8_BITS, 0, public_key.delegate(), public_key.callback())); + + // These values exist exactly once in a certificate + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_cert_view_binary_values, + (ee, BOTAN_X509_TBS_DATA_BITS, 1, tbs_data.delegate(), tbs_data.callback())); + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_cert_view_binary_values, + (ee, BOTAN_X509_SIGNATURE_SCHEME_BITS, 1, sig_scheme.delegate(), sig_scheme.callback())); + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_cert_view_binary_values, + (ee, BOTAN_X509_SIGNATURE_BITS, 1, signature.delegate(), signature.callback())); + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_cert_view_binary_values, + (ca, BOTAN_X509_PUBLIC_KEY_PKCS8_BITS, 1, public_key.delegate(), public_key.callback())); + + // At the moment there's no way to directly instantiate a signature + // verifier object with an encoded signature algorithm scheme. Hence, + // we just check that the hard-coded expectation is fulfilled. + // + // TODO: improve this if we ever have a pk_op_verify_t constructor that + // takes an encoded AlgorithmIdentifier. + const auto expected_sig_scheme = + Botan::AlgorithmIdentifier("RSA/PKCS1v15(SHA-1)", Botan::AlgorithmIdentifier::USE_NULL_PARAM).BER_encode(); + result.test_bin_eq("AlgorithmIdentifier", sig_scheme.get(), expected_sig_scheme); + + botan_pubkey_t pubkey; + TEST_FFI_INIT(botan_pubkey_load, (&pubkey, public_key.data(), public_key.size())); + + botan_pk_op_verify_t verifier; + TEST_FFI_INIT(botan_pk_op_verify_create, (&verifier, pubkey, "PKCS1v15(SHA-1)", 0)); + TEST_FFI_OK(botan_pk_op_verify_update, (verifier, tbs_data.data(), tbs_data.size())); + TEST_FFI_OK(botan_pk_op_verify_finish, (verifier, signature.data(), signature.size())); + + TEST_FFI_OK(botan_pk_op_verify_destroy, (verifier)); + TEST_FFI_OK(botan_pubkey_destroy, (pubkey)); + } + void ffi_test(Test::Result& result, botan_rng_t /*unused*/) override { botan_x509_cert_t root; int rc; @@ -853,6 +926,8 @@ class FFI_Cert_Validation_Test final : public FFI_Test { result.test_is_true("Validation test07 passed", rc == 0); result.test_str_eq("Validation test07 status string", botan_x509_cert_validation_status(rc), "Verified"); + verify_bare_pkcs1_rsa_signature(result, end7, sub7); + TEST_FFI_RC(1, botan_x509_cert_verify_with_crl, (&rc, end7, subs, 2, nullptr, 0, nullptr, 0, "x509/farce", 0, nullptr, 0)); @@ -940,6 +1015,11 @@ class FFI_ECDSA_Certificate_Test final : public FFI_Test { result.test_sz_eq("cert serial length", serial.size(), 16); result.test_bin_eq("cert serial", serial, "41D29DD172EAEEA780C12C6CE92F8752"); + ViewBytesSink serial_sink; + TEST_FFI_OK(botan_x509_cert_view_binary_values, + (cert, BOTAN_X509_SERIAL_NUMBER, 0, serial_sink.delegate(), serial_sink.callback())); + result.test_bin_eq("cert serial (2)", serial_sink.get(), "41D29DD172EAEEA780C12C6CE92F8752"); + size_t fingerprint_len = 0; TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_x509_cert_get_fingerprint, @@ -959,6 +1039,12 @@ class FFI_ECDSA_Certificate_Test final : public FFI_Test { result.test_sz_eq("No AKID", key_id_len, 0); + // "No AKID" is explicitly communicated with an error code + ViewBytesSink key_id_sink; + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_cert_view_binary_values, + (cert, BOTAN_X509_AUTHORITY_KEY_IDENTIFIER, 0, key_id_sink.delegate(), key_id_sink.callback())); + key_id_len = 0; TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_x509_cert_get_subject_key_id, @@ -970,6 +1056,10 @@ class FFI_ECDSA_Certificate_Test final : public FFI_Test { Botan::hex_encode(key_id.data(), key_id.size(), true), "7C4296AEDE4B483BFA92F89E8CCF6D8BA9723795"); + TEST_FFI_OK(botan_x509_cert_view_binary_values, + (cert, BOTAN_X509_SUBJECT_KEY_IDENTIFIER, 0, key_id_sink.delegate(), key_id_sink.callback())); + result.test_bin_eq("cert subject key id", key_id_sink.get(), "7C4296AEDE4B483BFA92F89E8CCF6D8BA9723795"); + size_t pubkey_len = 0; TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_x509_cert_get_public_key_bits, @@ -1425,6 +1515,79 @@ class FFI_Cert_NameConstraints_Test final : public FFI_Test { } }; +class FFI_Cert_AuthorityInformationAccess_Test final : public FFI_Test { + private: + static auto read_aia_string_list(Test::Result& result, botan_x509_cert_t cert, botan_x509_value_type value_type) { + std::vector out; + + for(size_t i = 0; true; ++i) { + const auto rc = botan_x509_cert_view_string_values( + cert, value_type, i, &out, [](botan_view_ctx ctx, const char* str, size_t) -> int { + static_cast*>(ctx)->emplace_back(str); + return BOTAN_FFI_SUCCESS; + }); + + if(rc == BOTAN_FFI_ERROR_OUT_OF_RANGE) { + break; + } else { + if(!result.test_sz_eq("error code from accessing AIA", rc, BOTAN_FFI_SUCCESS)) { + break; + } + } + } + + return out; + } + + public: + std::string name() const override { return "FFI X509 Authority Information Access"; } + + void ffi_test(Test::Result& result, botan_rng_t /*unused*/) override { + botan_x509_cert_t cert_with_aia; + botan_x509_cert_t cert_without_crl_dps; + if(!TEST_FFI_INIT( + botan_x509_cert_load_file, + (&cert_with_aia, + Test::data_file("x509/misc/contains_authority_info_access_with_two_ca_issuers.pem").c_str()))) { + return; + } + + if(!TEST_FFI_INIT(botan_x509_cert_load_file, + (&cert_without_crl_dps, Test::data_file("x509/misc/no_alternative_names.pem").c_str()))) { + return; + } + + TEST_FFI_RC( + BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_cert_view_string_values, + (cert_without_crl_dps, BOTAN_X509_CRL_DISTRIBUTION_URLS, 0, nullptr, [](auto, auto, auto) { return 0; })); + + const auto crl_dps = read_aia_string_list(result, cert_with_aia, BOTAN_X509_CRL_DISTRIBUTION_URLS); + result.test_sz_eq("has two CRL URI distribution points", crl_dps.size(), 2); + result.test_is_true("has expected CRL URI distribution point", + Botan::value_exists(crl_dps, "http://crl.d-trust.net/crl/bdrive_test_ca_1-2_2017.crl")); + + const auto dummy_callback = [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }; + + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_cert_view_string_values, + (cert_without_crl_dps, BOTAN_X509_OCSP_RESPONDER_URLS, 0, nullptr, dummy_callback)); + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_cert_view_string_values, + (cert_without_crl_dps, BOTAN_X509_CA_ISSUERS_URLS, 0, nullptr, dummy_callback)); + + const auto ocsps = read_aia_string_list(result, cert_with_aia, BOTAN_X509_OCSP_RESPONDER_URLS); + result.test_is_true("OCSP responder found", Botan::value_exists(ocsps, "http://staging.ocsp.d-trust.net")); + + const auto cas = read_aia_string_list(result, cert_with_aia, BOTAN_X509_CA_ISSUERS_URLS); + result.test_is_true("CA issuer found", + Botan::value_exists(cas, "http://www.d-trust.net/cgi-bin/Bdrive_Test_CA_1-2_2017.crt")); + + TEST_FFI_OK(botan_x509_cert_destroy, (cert_with_aia)); + TEST_FFI_OK(botan_x509_cert_destroy, (cert_without_crl_dps)); + } +}; + #endif class FFI_PKCS_Hashid_Test final : public FFI_Test { @@ -5378,6 +5541,7 @@ BOTAN_REGISTER_TEST("ffi", "ffi_srp6", FFI_SRP6_Test); #if defined(BOTAN_HAS_X509) BOTAN_REGISTER_TEST("ffi", "ffi_cert_alt_names", FFI_Cert_AlternativeNames_Test); BOTAN_REGISTER_TEST("ffi", "ffi_cert_name_constraints", FFI_Cert_NameConstraints_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_cert_aia", FFI_Cert_AuthorityInformationAccess_Test); #endif #endif From 525e34c303d877e0d74456ae82048e2c7fc6423c Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Mon, 12 Jan 2026 16:18:20 +0100 Subject: [PATCH 3/3] Add *_count functions for generic getters These functions are currently implemented by trivially counting the number of elements their associated enumerator functions are actually producing. Given that most values are anyway viewed straight from the C++ objects' memory, the overhead should be negligible. The main advantage of this approach is its minimal maintenance burden. --- doc/api_ref/ffi.rst | 28 +++++++++++ src/lib/ffi/ffi.h | 9 ++++ src/lib/ffi/ffi_cert.cpp | 105 +++++++++++++++++++++++++++++++++++---- src/tests/test_ffi.cpp | 54 ++++++++++++++------ 4 files changed, 173 insertions(+), 23 deletions(-) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index 08bd3bdc38e..84b04a2d13d 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -1705,6 +1705,13 @@ X.509 Certificates values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` is returned. +.. cpp:function:: int botan_x509_cert_view_binary_values_count(botan_x509_cert_t cert, \ + botan_x509_value_type value_type, \ + size_t* count) + + Get the number of entries for multi-value binary fields of information + contained in the certificate. + .. cpp:function:: int botan_x509_cert_view_string_values(botan_x509_cert_t cert, \ botan_x509_value_type value_type, \ size_t index, \ @@ -1721,6 +1728,13 @@ X.509 Certificates values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` is returned. +.. cpp:function:: int botan_x509_cert_view_string_values_count(botan_x509_cert_t cert, \ + botan_x509_value_type value_type, \ + size_t* count) + + Get the number of entries for multi-value string fields of information + contained in the certificate. + .. 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 @@ -2037,6 +2051,13 @@ X.509 Certificate Revocation Lists values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` is returned. +.. cpp:function:: int botan_x509_crl_view_binary_values_count(botan_x509_crl_t crl, \ + botan_x509_value_type value_type, \ + size_t* count) + + Get the number of entries for multi-value binary fields of information + contained in the CRL. + .. cpp:function:: int botan_x509_crl_view_string_values(botan_x509_crl_t crl, \ botan_x509_value_type value_type, \ size_t index, \ @@ -2053,6 +2074,13 @@ X.509 Certificate Revocation Lists values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` is returned. +.. cpp:function:: int botan_x509_crl_view_string_values_count(botan_x509_crl_t crl, \ + botan_x509_value_type value_type, \ + size_t* count) + + Get the number of entries for multi-value string fields of information + contained in the CRL. + .. cpp:function:: int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert) Check whether a given ``crl`` contains a given ``cert``. diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index c8a715e051c..83662355759 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2284,6 +2284,8 @@ BOTAN_FFI_EXPORT(2, 8) int botan_x509_cert_dup(botan_x509_cert_t* new_cert, bota BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_view_binary_values( botan_x509_cert_t cert, botan_x509_value_type value_type, size_t index, botan_view_ctx ctx, botan_view_bin_fn view); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_view_binary_values_count(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t* count); /** * Retrieve a specific string value from an X.509 certificate. @@ -2298,6 +2300,8 @@ int botan_x509_cert_view_binary_values( BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_view_string_values( botan_x509_cert_t cert, botan_x509_value_type value_type, size_t index, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_view_string_values_count(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t* count); /* Prefer botan_x509_cert_not_before and botan_x509_cert_not_after */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len); @@ -2564,6 +2568,8 @@ int botan_x509_crl_view_binary_values(botan_x509_crl_t crl_obj, size_t index, botan_view_ctx ctx, botan_view_bin_fn view); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_view_binary_values_count(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t* count); /** * Retrieve a specific string value from an X.509 certificate revocation list. @@ -2581,6 +2587,9 @@ int botan_x509_crl_view_string_values(botan_x509_crl_t crl_obj, size_t index, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_view_string_values_count(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t* count); + /** * Given a CRL and a certificate, * check if the certificate is revoked on that particular CRL diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index e6f2e6adaf9..d980a2325ac 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -110,6 +110,38 @@ std::optional to_botan_x509_general_name_types(Bo BOTAN_ASSERT_UNREACHABLE(); } +/** + * Given some enumerator-style function @p fn, count how many values it can + * produce before returning BOTAN_FFI_ERROR_OUT_OF_RANGE. If the first call to + * @p fn returns BOTAN_FFI_ERROR_NO_VALUE, zero is written to @p count. + * + * If this function returns BOTAN_FFI_SUCCESS, @p count contains the number of + * values that can be enumerated. Otherwise, the value of @p count is undefined. + */ +template EnumeratorT> +int enumerator_count_values(size_t* count, EnumeratorT fn) { + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *count = 0; + for(;; ++(*count)) { + const auto rc = fn(*count); + switch(rc) { + case BOTAN_FFI_ERROR_NO_VALUE: + case BOTAN_FFI_ERROR_OUT_OF_RANGE: + // hit the end of the enumeration + return BOTAN_FFI_SUCCESS; + case BOTAN_FFI_SUCCESS: + // got a value, continue counting + break; + default: + // unexpected error from enumerator function + return rc; + } + } +} + } // namespace } // namespace Botan_FFI @@ -186,8 +218,14 @@ namespace { int botan_x509_object_view_value(const Botan::X509_Object& object, botan_x509_value_type value_type, + size_t index, botan_view_ctx ctx, botan_view_str_fn view_fn) { + if(index != 0) { + // As of now there are no multi-value generic string entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + auto view = [=](const std::string& value) { return invoke_view_callback(view_fn, ctx, value); }; switch(value_type) { @@ -200,8 +238,14 @@ int botan_x509_object_view_value(const Botan::X509_Object& object, int botan_x509_object_view_value(const Botan::X509_Object& object, botan_x509_value_type value_type, + size_t index, botan_view_ctx ctx, botan_view_bin_fn view_fn) { + if(index != 0) { + // As of now there are no multi-value generic binary entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + auto view = [=](std::span value) { return invoke_view_callback(view_fn, ctx, value); }; switch(value_type) { @@ -262,7 +306,7 @@ int botan_x509_cert_view_binary_values(botan_x509_cert_t cert, case BOTAN_X509_SIGNATURE_SCHEME_BITS: case BOTAN_X509_SIGNATURE_BITS: case BOTAN_X509_DER_ENCODING: - return botan_x509_object_view_value(c, value_type, ctx, view_fn); + return botan_x509_object_view_value(c, value_type, index, ctx, view_fn); case BOTAN_X509_PEM_ENCODING: case BOTAN_X509_CRL_DISTRIBUTION_URLS: @@ -279,6 +323,18 @@ int botan_x509_cert_view_binary_values(botan_x509_cert_t cert, #endif } +int botan_x509_cert_view_binary_values_count(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return enumerator_count_values(count, [=](size_t index) { + return botan_x509_cert_view_binary_values( + cert, value_type, index, nullptr, [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }); + }); +#else + BOTAN_UNUSED(cert, value_type, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_view_string_values(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t index, @@ -333,7 +389,7 @@ int botan_x509_cert_view_string_values(botan_x509_cert_t cert, case BOTAN_X509_CA_ISSUERS_URLS: return enumerate(c.ca_issuers(), index); case BOTAN_X509_PEM_ENCODING: - return botan_x509_object_view_value(c, value_type, ctx, view_fn); + return botan_x509_object_view_value(c, value_type, index, ctx, view_fn); case BOTAN_X509_SERIAL_NUMBER: case BOTAN_X509_SUBJECT_DN_BITS: @@ -356,6 +412,18 @@ int botan_x509_cert_view_string_values(botan_x509_cert_t cert, #endif } +int botan_x509_cert_view_string_values_count(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return enumerator_count_values(count, [=](size_t index) { + return botan_x509_cert_view_string_values( + cert, value_type, index, nullptr, [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }); + }); +#else + BOTAN_UNUSED(cert, value_type, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_is_ca(botan_x509_cert_t cert) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return c.is_CA_cert() ? 1 : 0; }); @@ -1079,7 +1147,7 @@ int botan_x509_crl_view_binary_values(botan_x509_crl_t crl_obj, case BOTAN_X509_SIGNATURE_SCHEME_BITS: case BOTAN_X509_SIGNATURE_BITS: case BOTAN_X509_DER_ENCODING: - return botan_x509_object_view_value(crl, value_type, ctx, view_fn); + return botan_x509_object_view_value(crl, value_type, index, ctx, view_fn); case BOTAN_X509_SUBJECT_DN_BITS: case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: @@ -1099,21 +1167,28 @@ int botan_x509_crl_view_binary_values(botan_x509_crl_t crl_obj, #endif } +int botan_x509_crl_view_binary_values_count(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return enumerator_count_values(count, [=](size_t index) { + return botan_x509_crl_view_binary_values( + crl_obj, value_type, index, nullptr, [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }); + }); +#else + BOTAN_UNUSED(crl_obj, value_type, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_crl_view_string_values(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t index, botan_view_ctx ctx, botan_view_str_fn view) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - if(index != 0) { - // As of now there are no multi-value string entries. - return BOTAN_FFI_ERROR_OUT_OF_RANGE; - } - return BOTAN_FFI_VISIT(crl_obj, [=](const Botan::X509_CRL& crl) -> int { switch(value_type) { case BOTAN_X509_PEM_ENCODING: - return botan_x509_object_view_value(crl, value_type, ctx, view); + return botan_x509_object_view_value(crl, value_type, index, ctx, view); case BOTAN_X509_SERIAL_NUMBER: case BOTAN_X509_SUBJECT_DN_BITS: @@ -1139,6 +1214,18 @@ int botan_x509_crl_view_string_values(botan_x509_crl_t crl_obj, #endif } +int botan_x509_crl_view_string_values_count(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return enumerator_count_values(count, [=](size_t index) { + return botan_x509_crl_view_string_values( + crl_obj, value_type, index, nullptr, [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }); + }); +#else + BOTAN_UNUSED(crl_obj, value_type, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(crl, [=](const auto& c) { return c.is_revoked(safe_get(cert)) ? 0 : -1; }); diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index d08e9d30972..a8b5dbc8ff0 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -732,6 +732,10 @@ class FFI_CRL_Test final : public FFI_Test { TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, botan_x509_crl_view_binary_values, (bytecrl, BOTAN_X509_SUBJECT_KEY_IDENTIFIER, 0, akid.delegate(), akid.callback())); + size_t akid_count; + TEST_FFI_OK(botan_x509_crl_view_binary_values_count, + (bytecrl, BOTAN_X509_SUBJECT_KEY_IDENTIFIER, &akid_count)); + result.test_sz_eq("no subject key ID entries", akid_count, 0); botan_x509_crl_t crl; REQUIRE_FFI_OK(botan_x509_crl_load_file, (&crl, Test::data_file("x509/nist/root.crl").c_str())); @@ -797,6 +801,9 @@ class FFI_CRL_Test final : public FFI_Test { ViewStringSink crl_pem; TEST_FFI_OK(botan_x509_crl_view_string_values, (bytecrl, BOTAN_X509_PEM_ENCODING, 0, crl_pem.delegate(), crl_pem.callback())); + size_t pem_count; + TEST_FFI_OK(botan_x509_crl_view_string_values_count, (bytecrl, BOTAN_X509_PEM_ENCODING, &pem_count)); + result.test_sz_eq("one PEM encoding", pem_count, 1); auto remove_newlines = [](std::string_view str) { auto out = std::string(str); @@ -854,6 +861,16 @@ class FFI_Cert_Validation_Test final : public FFI_Test { botan_x509_cert_view_binary_values, (ca, BOTAN_X509_PUBLIC_KEY_PKCS8_BITS, 1, public_key.delegate(), public_key.callback())); + size_t count; + TEST_FFI_OK(botan_x509_cert_view_binary_values_count, (ee, BOTAN_X509_TBS_DATA_BITS, &count)); + result.test_sz_eq("TBS data count", count, 1); + TEST_FFI_OK(botan_x509_cert_view_binary_values_count, (ee, BOTAN_X509_SIGNATURE_SCHEME_BITS, &count)); + result.test_sz_eq("Signature scheme count", count, 1); + TEST_FFI_OK(botan_x509_cert_view_binary_values_count, (ee, BOTAN_X509_SIGNATURE_BITS, &count)); + result.test_sz_eq("Signature count", count, 1); + TEST_FFI_OK(botan_x509_cert_view_binary_values_count, (ca, BOTAN_X509_PUBLIC_KEY_PKCS8_BITS, &count)); + result.test_sz_eq("Public key count", count, 1); + // At the moment there's no way to directly instantiate a signature // verifier object with an encoded signature algorithm scheme. Hence, // we just check that the hard-coded expectation is fulfilled. @@ -1112,6 +1129,21 @@ class FFI_ECDSA_Certificate_Test final : public FFI_Test { std::string printable(printable_len - 1, '0'); TEST_FFI_OK(botan_x509_cert_to_string, (cert, printable.data(), &printable_len)); + size_t count; + TEST_FFI_OK(botan_x509_cert_view_string_values_count, (cert, BOTAN_X509_PEM_ENCODING, &count)); + result.test_sz_eq("one PEM encoding", count, 1); + TEST_FFI_OK(botan_x509_cert_view_binary_values_count, (cert, BOTAN_X509_DER_ENCODING, &count)); + result.test_sz_eq("one DER encoding", count, 1); + + ViewBytesSink der; + ViewStringSink pem; + TEST_FFI_OK(botan_x509_cert_view_binary_values, + (cert, BOTAN_X509_DER_ENCODING, 0, der.delegate(), der.callback())); + result.test_is_true("DER encoding produced something", !der.get().empty()); + TEST_FFI_OK(botan_x509_cert_view_string_values, + (cert, BOTAN_X509_PEM_ENCODING, 0, pem.delegate(), pem.callback())); + result.test_is_true("PEM encoding produced something", !pem.get().empty()); + TEST_FFI_RC(0, botan_x509_cert_allowed_usage, (cert, KEY_CERT_SIGN)); TEST_FFI_RC(0, botan_x509_cert_allowed_usage, (cert, CRL_SIGN)); TEST_FFI_RC(1, botan_x509_cert_allowed_usage, (cert, DIGITAL_SIGNATURE)); @@ -1520,20 +1552,14 @@ class FFI_Cert_AuthorityInformationAccess_Test final : public FFI_Test { static auto read_aia_string_list(Test::Result& result, botan_x509_cert_t cert, botan_x509_value_type value_type) { std::vector out; - for(size_t i = 0; true; ++i) { - const auto rc = botan_x509_cert_view_string_values( - cert, value_type, i, &out, [](botan_view_ctx ctx, const char* str, size_t) -> int { - static_cast*>(ctx)->emplace_back(str); - return BOTAN_FFI_SUCCESS; - }); - - if(rc == BOTAN_FFI_ERROR_OUT_OF_RANGE) { - break; - } else { - if(!result.test_sz_eq("error code from accessing AIA", rc, BOTAN_FFI_SUCCESS)) { - break; - } - } + size_t count = 0; + TEST_FFI_OK(botan_x509_cert_view_string_values_count, (cert, value_type, &count)); + for(size_t i = 0; i < count; ++i) { + TEST_FFI_OK(botan_x509_cert_view_string_values, + (cert, value_type, i, &out, [](botan_view_ctx ctx, const char* str, size_t) -> int { + static_cast*>(ctx)->emplace_back(str); + return BOTAN_FFI_SUCCESS; + })); } return out;