diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index f5a3e7f3e70..84b04a2d13d 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 @@ -1684,6 +1689,52 @@ 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_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, \ + 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_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 @@ -1984,6 +2035,52 @@ 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_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, \ + 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_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``. @@ -2022,6 +2119,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..83662355759 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,38 @@ 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); +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. + * + * 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); +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); 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 +2552,44 @@ 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); +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. + * + * 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); +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 689f50c0b32..d980a2325ac 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) @@ -107,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 @@ -175,6 +210,219 @@ 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, + 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) { + 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, + 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) { + 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, index, 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_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, + 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, index, 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_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) @@ -867,6 +1115,117 @@ 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, index, 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_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) + 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, index, 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_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 76ea0a44539..a8b5dbc8ff0 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -724,6 +724,19 @@ 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())); + 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())); @@ -785,6 +798,22 @@ 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())); + 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); + 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 +832,67 @@ 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())); + + 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. + // + // 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 +943,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 +1032,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 +1056,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 +1073,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, @@ -1022,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)); @@ -1425,6 +1547,73 @@ 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; + + 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; + } + + 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 +5567,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