From a3355efe39f86392bef2df159386a3fac7155a5f Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:34:58 +0100 Subject: [PATCH] Add getters for RFC 3779 extensions to FFI --- doc/api_ref/ffi.rst | 73 ++++++++++ src/lib/ffi/ffi.h | 74 ++++++++++ src/lib/ffi/ffi_cert_ext.cpp | 255 +++++++++++++++++++++++++++++++++++ src/lib/x509/x509_ext.cpp | 7 + src/lib/x509/x509_ext.h | 8 ++ src/python/botan3.py | 106 +++++++++++++++ src/scripts/test_python.py | 34 +++++ src/tests/test_ffi.cpp | 112 +++++++++++++++ 8 files changed, 669 insertions(+) create mode 100644 src/lib/ffi/ffi_cert_ext.cpp diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index 0dcb941f07e..f72d6f8c82e 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -2125,6 +2125,79 @@ X.509 Certificates Return a (statically allocated) string associated with the verification result, or NULL if the code is not known. +.. cpp:function:: int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_cert_t cert, \ + size_t* v4_count, \ + size_t* v6_count) + + Get info about the IP Address Blocks extension from `RFC 3779 `_. + ``v4_count`` is set to the number of v4 families contained in the extension, + ``v6_count`` to the number of v6 families. If the extension is not present, :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` is returned. + + If the extension is not present or an error occurs, ``v4_count`` and ``v6_count`` are not modified. + +.. cpp:function:: int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_cert_t cert, \ + int ipv6, \ + size_t i, \ + int* has_safi, \ + uint8_t* safi, \ + int* present, \ + size_t* count) + + Get info about a specific family in the extension. + ``ipv6`` should be set to 0 for v4 families, 1 for v6 families. + ``i`` is the local index for each family type, the first v4 family is at (``i = 0``, ``ipv6 = 0``), the first v6 family is at (``i = 0``, ``ipv6 = 1``). + The number of v4 / v6 families corresponds to the ``v4_count`` / ``v6_count`` values obtained from :cpp:func:`botan_x509_ext_ip_addr_blocks_get_counts`. + ``has_safi`` is set to 1 if the family has an associated SAFI, else 0. + ``safi`` contains the SAFI if the family has one, otherwise its value is not modified. + ``present`` is set to 1 if the family has range values, 0 if it is marked as "inherit". + ``count`` is set to the number of ranges contained if any, otherwise its value is not modified. + + The output parameters ``has_safi``, ``safi``, ``present`` and ``count`` may be modified even if the extension is not present or some other error occurs. + In this event, the value of each output parameter after the call returns is undefined. + +.. cpp:function:: int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_cert_t cert, \ + int ipv6, \ + size_t i, \ + size_t entry, \ + uint8_t min_out[], \ + uint8_t max_out[], \ + size_t* out_len) + + Get info about a specific range in the extension. + ``ipv6`` and ``i`` behave as in :cpp:func:`botan_x509_ext_ip_addr_blocks_get_family`. + ``entry`` is the index to the range in the family, between 0 and (not including) ``count``. + ``min_out`` and ``max_out`` are set to the min and max addresses of the range respectively. + ``out_len`` should be set to 4 for v4 families, 16 for v6 families, the two arrays must also be that size. + + The output parameters ``min_out``, ``max_out`` and ``out_len`` may be modified even if the extension is not present or some other error occurs. + In this event, the value of each output parameter after the call returns is undefined. + +.. cpp:function:: int botan_x509_ext_as_blocks_get_info(botan_x509_cert_t cert, \ + int asnum, \ + int* present, \ + size_t* count) + + Get info about the AS Blocks extension from `RFC 3779 `_. + ``asnum`` should be set to 1 to get info about the ASNUM part of the extension, 0 for RDI. + ``present`` is set to 1 if a value is contained, 0 if that part of the extension is marked as "inherit". + If the part is not present at all, :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` will be returned. + ``count`` is set to the number of entries for that part if any, otherwise its value is not modified. + + If the extension is not present or an error occurs, ``present`` and ``count`` are not modified. + +.. cpp:function:: int botan_x509_ext_as_blocks_get_entry_at(botan_x509_cert_t cert, \ + int asnum, \ + size_t i, \ + uint32_t* min, \ + uint32_t* max) + + Get info about a specific entry from the extension. + ``asnum`` behaves as in :cpp:func:`botan_x509_ext_as_blocks_get_info`, ``i`` is the index for that part, + between 0 and (not including) ``count``. + ``min`` and ``max`` will be set to the minimum and maximum AS numbers of the range respectively. + + If the extension is not present or an error occurs, ``min`` and ``max`` are not modified. + X.509 Certificate Revocation Lists ---------------------------------------- diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 7e10fe26ba6..e985eb9b315 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2704,6 +2704,80 @@ 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 +*/ + +/** +* Get info about the IP Address Blocks extension from RFC 3779 +* @param v4_count is set to the number of v4 families contained in the extension +* @param v6_count is set to the number of v6 families +* @returns 0 on success, negative number on error +* +* If the extension is not present or an error occurs, `v4_count` and `v6_count` are not modified +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_cert_t cert, size_t* v4_count, size_t* v6_count); + +/** +* Get info about a specific family in the IP Address Blocks extension from RFC 3779 +* @param ipv6 must be set to 1 if the family is an IPv6 family, 0 for IPv4 families +* @param i is the (local) index for this family kind (the first v4 family is at i = 0, ipv6 = 0; the first v6 family is at i = 0, ipv6 = 1) +* @param has_safi will be set to 1 if the family has an associated SAFI +* @param safi will be set to the families' SAFI, if it has one, otherwise `safi` is not modified +* @param present is set to 1 if the family contains values (ranges), 0 if it is marked as "inherit" +* @param count is set to the number of values (ranges), if they were present, otherwise `count` is not modified +* @returns 0 on success, negative number on error +* +* The output parameters `has_safi`, `safi`, `present` and `count` may be modified even if the extension is not present or some other error occurs. +* In this event, the value of each output parameter after the call returns is undefined. +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_x509_ext_ip_addr_blocks_get_family( + botan_x509_cert_t cert, int ipv6, size_t i, int* has_safi, uint8_t* safi, int* present, size_t* count); + +/** +* Get info about a specific range in the IP Address Blocks extension from RFC 3779 +* @param ipv6 must be set to 1 if the family is an IPv6 family, 0 for IPv4 families +* @param i is the (local) index of the family, see `botan_x509_ext_ip_addr_blocks_get_family` +* @param entry is the index of the range +* @param min_out is set to the lower address of the range +* @param max_out is set to the upper address of the range +* @param out_len is set to the length of the addresses (4 for IPv4, 16 for IPv6) +* @returns 0 on success, negative number on error +* +* The output parameters `min_out`, `max_out` and `out_len` may be modified even if the extension is not present or some other error occurs. +* In this event, the value of each output parameter after the call returns is undefined. +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_x509_ext_ip_addr_blocks_get_address( + botan_x509_cert_t cert, int ipv6, size_t i, size_t entry, uint8_t min_out[], uint8_t max_out[], size_t* out_len); + +/** +* Get basic info about the AS Blocks extension from RFC 3779 +* @param asnum must be set to 1 to get info about AS numbers, 0 for RDIs (the type) +* @param present is set to 1 if the extension contains entries for the type, 0 if it is marked as "inherit" +* @param count is set to number of entries for this type, if it was present, otherwise `count` is not modified +* @returns 0 on success, negative number on error +* +* If the extension is not present or an error occurs, `present` and `count` are not modified +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_x509_ext_as_blocks_get_info(botan_x509_cert_t cert, int asnum, int* present, size_t* count); + +/** +* Get a specific entry in the AS Blocks extension from RFC 3779 +* @param asnum Set to 1 to get info about AS numbers, 0 for RDIs (the type) +* @param i The index of the entry to get +* @param min is set to the min value of the range +* @param max is set to the max value of the range +* @returns 0 on success, negative number on error +* +* If the extension is not present or an error occurs, `min` and `max` are not modified +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_x509_ext_as_blocks_get_entry_at(botan_x509_cert_t cert, int asnum, size_t i, uint32_t* min, uint32_t* max); + /* * X.509 CRL **************************/ diff --git a/src/lib/ffi/ffi_cert_ext.cpp b/src/lib/ffi/ffi_cert_ext.cpp new file mode 100644 index 00000000000..09df72e546c --- /dev/null +++ b/src/lib/ffi/ffi_cert_ext.cpp @@ -0,0 +1,255 @@ +/* +* (C) 2026 Jack Lloyd +* (C) 2026 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + #include +#endif + +namespace { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +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; + } + const auto& choice = std::get>(family.addr_choice()); + + if(!choice.ranges().has_value()) { + *present = 0; + } else { + *present = 1; + *count = choice.ranges().value().size(); + } + return BOTAN_FFI_SUCCESS; +} + +template +int ip_addr_blocks_get_address(const Botan::Cert_Extension::IPAddressBlocks::IPAddressFamily::AddrChoice& addr_choice, + size_t entry, + uint8_t min_out[], + uint8_t max_out[], + size_t* out_len) { + if(!std::holds_alternative>(addr_choice)) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + const auto& choice = std::get>(addr_choice); + + if(!choice.ranges().has_value()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + if(entry >= choice.ranges().value().size()) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + const auto& entry_ = choice.ranges().value().at(entry); + + const int 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()); +} +#endif +} // namespace + +extern "C" { + +using namespace Botan_FFI; + +// ip addr blocks ext +int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_cert_t cert, size_t* v4_count, size_t* v6_count) { + if(Botan::any_null_pointers(v4_count, v6_count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& ext = + safe_get(cert).v3_extensions().get_extension_object_as(); + if(Botan::any_null_pointers(ext)) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + *v4_count = ext->v4_count(); + *v6_count = ext->v6_count(); + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_get_family( + botan_x509_cert_t cert, int ipv6, size_t i, int* has_safi, uint8_t* safi, int* present, size_t* count) { + if(Botan::any_null_pointers(has_safi, safi, present, count)) { + 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& ext = + safe_get(cert).v3_extensions().get_extension_object_as(); + if(Botan::any_null_pointers(ext)) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const size_t limit = ipv6 == 0 ? ext->v4_count() : ext->v6_count(); + if(i >= limit) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + const size_t index = ipv6 == 0 ? i : ext->v4_count() + i; + const auto& addr_blocks = ext->addr_blocks(); + const auto& family = addr_blocks.at(index); + 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(cert, ipv6, i); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_get_address( + botan_x509_cert_t cert, 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& ext = + safe_get(cert).v3_extensions().get_extension_object_as(); + if(Botan::any_null_pointers(ext)) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const size_t limit = ipv6 == 0 ? ext->v4_count() : ext->v6_count(); + if(i >= limit) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + const size_t index = ipv6 == 0 ? i : ext->v4_count() + i; + const auto& addr_blocks = ext->addr_blocks(); + const auto& addr_choice = addr_blocks.at(index).addr_choice(); + + if(ipv6 == 0) { + return ip_addr_blocks_get_address( + addr_choice, entry, min_out, max_out, out_len); + } else { + return ip_addr_blocks_get_address( + addr_choice, entry, min_out, max_out, out_len); + } + }); +#else + BOTAN_UNUSED(cert, ipv6, i, entry, min_out, max_out); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +// as blocks ext +int botan_x509_ext_as_blocks_get_info(botan_x509_cert_t cert, int asnum, int* present, size_t* count) { + if(Botan::any_null_pointers(present, count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + if(asnum != 0 && asnum != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + const auto& ext = safe_get(cert).v3_extensions().get_extension_object_as(); + if(Botan::any_null_pointers(ext)) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& asnum_or_rdi = asnum == 1 ? ext->as_identifiers().asnum() : ext->as_identifiers().rdi(); + + if(!asnum_or_rdi.has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& ranges = asnum_or_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(cert, asnum); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_entry_at(botan_x509_cert_t cert, int asnum, size_t i, uint32_t* min, uint32_t* max) { + if(Botan::any_null_pointers(min, max)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + if(asnum != 0 && asnum != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + const auto& ext = safe_get(cert).v3_extensions().get_extension_object_as(); + if(Botan::any_null_pointers(ext)) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& asnum_or_rdi = asnum == 1 ? ext->as_identifiers().asnum() : ext->as_identifiers().rdi(); + if(!asnum_or_rdi.has_value() || !asnum_or_rdi.value().ranges().has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& range = asnum_or_rdi.value().ranges().value(); + if(i >= range.size()) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + *min = range.at(i).min(); + *max = range.at(i).max(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert, asnum, i); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} +} diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp index 97e2b031cb4..5fc430ec6c2 100644 --- a/src/lib/x509/x509_ext.cpp +++ b/src/lib/x509/x509_ext.cpp @@ -1794,6 +1794,8 @@ void IPAddressBlocks::sort_and_merge() { } std::vector merged_blocks; + size_t v4_count = 0; + size_t v6_count = 0; for(auto& it : afam_map) { // fams consists of families with the same afi/safi combination std::vector& fams = it.second; @@ -1804,11 +1806,16 @@ void IPAddressBlocks::sort_and_merge() { // fams[0] has to have the same choice type as the fams in the same bucket if(std::holds_alternative>(fams[0].addr_choice())) { merged_blocks.push_back(merge(fams)); + v4_count++; } else { merged_blocks.push_back(merge(fams)); + v6_count++; } } + BOTAN_ASSERT_NOMSG(v4_count + v6_count == merged_blocks.size()); m_ip_addr_blocks = merged_blocks; + m_v4_count = v4_count; + m_v6_count = v6_count; } template diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h index 3719eb9735f..f109756f1d3 100644 --- a/src/lib/x509/x509_ext.h +++ b/src/lib/x509/x509_ext.h @@ -1105,6 +1105,12 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio const std::vector& addr_blocks() const { return m_ip_addr_blocks; } + /// The number of IPv4 families contained in the extension + size_t v4_count() const { return m_v4_count; } + + /// The number of IPv6 families contained in the extension + size_t v6_count() const { return m_v6_count; } + private: std::string oid_name() const override { return "PKIX.IpAddrBlocks"; } @@ -1116,6 +1122,8 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio void decode_inner(const std::vector& in) override; std::vector m_ip_addr_blocks; + size_t m_v4_count = 0; + size_t m_v6_count = 0; void sort_and_merge(); template diff --git a/src/python/botan3.py b/src/python/botan3.py index 47432105178..a8621bf5469 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -556,6 +556,16 @@ def ffi_api(fn, args, allowed_errors=None): dll.botan_x509_cert_validation_status.argtypes = [c_int] dll.botan_x509_cert_validation_status.restype = c_char_p + # X509 Extensions + 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_get_info, [c_void_p, c_int, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_as_blocks_get_entry_at, + [c_void_p, c_int, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) + # 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]) @@ -2395,6 +2405,102 @@ def allowed_usage(self, usage_list: list[str]) -> bool: def handle_(self): return self.__obj + def ext_ip_addr_blocks(self) -> tuple[ + list[tuple[int | None, list[tuple[tuple[int], tuple[int]]] | None]], + list[tuple[int | None, list[tuple[tuple[int], tuple[int]]] | None]] + ]: + """Get values from the IP Address Blocks extension. + If the extension is not present, an exception will be raised. + + Returns all values in the extension, in the form of (v4, v6), where both contain a list of tuples of + type (int | None, list[...]). The first element of each tuple is the SAFI, it may be `None` + to indicate no SAFI is present. The second element is a list of elements of type + tuple[tuple[int], tuple[int]], where each element is a single address range. Each element contains + two tuples of equal length, 4 for IPv4 families and 16 for IPv6 families. The values are the minimum + and maximum addresses of the range respectively. If the particular family is marked as "inherit", + the outer tuple will contain `None` as its second element instead of a list of ranges.""" + 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, stop) in ((0, v4_count), (1, v6_count)): + for i in range(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, out_len, 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, + out_len)) + ranges.append((tuple(min_), tuple(max_))) + + safi = safi.value if has_safi.value == 1 else None + if ipv6 == 0: + v4.append((safi, ranges)) + else: + v6.append((safi, ranges)) + return (v4, v6) + + def __get_as_blocks_values(self, asnum: bool) -> list[tuple[int, int]] | None: + present = c_int(0) + count = c_size_t(0) + asnum = c_int(1 if asnum else 0) + _DLL.botan_x509_ext_as_blocks_get_info(self.__obj, asnum, byref(present), byref(count)) + + # value is 'inherit' + if present.value == 0: + return None + + values = [] + for i in range(count.value): + min_ = c_uint32(0) + max_ = c_uint32(0) + _DLL.botan_x509_ext_as_blocks_get_entry_at(self.__obj, asnum, c_size_t(i), byref(min_), byref(max_)) + values.append((min_.value, max_.value)) + return values + + def ext_as_blocks_asnum(self) -> list[tuple[int, int]] | None: + """Get values from the AS Blocks extension. + If the extension is not present, an exception will be raised. + + Returns all AS numbers contained in the extension. + Returns a list of tuples, where each tuple is a range, and its inner elements are the + minimum and maximum values of the range respectively. + If AS numbers are marked as "inherit", `None` is returned instead. + If AS numbers are not present in the extension at all, this raises an exception.""" + return self.__get_as_blocks_values(True) + + def ext_as_blocks_rdi(self) -> list[tuple[int, int]] | None: + """Get values from the AS Blocks extension. + If the extension is not present, an exception will be raised. + + Get all RDIs contained in the extension. + Returns a list of tuples, where each tuple is a range, and its inner elements are the + minimum and maximum values of the range respectively. + If RDIs are marked as "inherit", `None` is returned instead. + If RDIs are not present in the extension at all, this raises an exception.""" + return self.__get_as_blocks_values(False) + def verify(self, intermediates: list[X509Cert] | None = None, trusted: list[X509Cert] | None = None, diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 7e85cdb087b..2670c482e75 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -1035,6 +1035,40 @@ def test_certs(self): self.assertFalse(int04_1.is_revoked(rootcrl)) self.assertTrue(end21.is_revoked(int21crl)) + def test_x509_extensions(self): + no_ext_cert = botan.X509Cert(filename=test_data("src/tests/data/x509/x509test/root.pem")) + + with self.assertRaisesRegex(botan.BotanException, r".*No value available.*"): + no_ext_cert.ext_as_blocks_asnum() + + with self.assertRaisesRegex(botan.BotanException, r".*No value available.*"): + no_ext_cert.ext_as_blocks_rdi() + + with self.assertRaisesRegex(botan.BotanException, r".*No value available.*"): + no_ext_cert.ext_ip_addr_blocks() + + ip_addr_blocks_cert = botan.X509Cert(filename=test_data("src/tests/data/x509/x509test/IPAddrBlocksUnsorted.pem")) + v4, v6 = ip_addr_blocks_cert.ext_ip_addr_blocks() + + self.assertEqual(v4, [ + (None, None), + (1, [((192, 168, 0, 0), (200, 0, 0, 0))]), + (2, None) + ]) + self.assertEqual(v6, [ + (None, [( + (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255), + (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255) + )]), + (1, None) + ]) + as_blocks_cert = botan.X509Cert(filename=test_data("src/tests/data/x509/x509test/ASNumberInherit.pem")) + asnum = as_blocks_cert.ext_as_blocks_asnum() + rdi = as_blocks_cert.ext_as_blocks_rdi() + + self.assertEqual(asnum, None) + self.assertEqual(rdi, [(0, 4294967295)]) + def test_crls(self): rng = botan.RandomNumberGenerator() now = int(time.time()) diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 458bc2ec63f..a25c0cb0f78 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -1713,6 +1713,117 @@ class FFI_Cert_AuthorityInformationAccess_Test final : public FFI_Test { } }; +class FFI_Cert_ExtRFC3779_Test final : public FFI_Test { + public: + std::string name() const override { return "FFI RFC3779 certificate extensions"; } + + void ffi_test(Test::Result& result, botan_rng_t /*unused*/) override { + botan_x509_cert_t ip_addr_blocks_cert; + if(!TEST_FFI_INIT(botan_x509_cert_load_file, + (&ip_addr_blocks_cert, Test::data_file("x509/x509test/IPAddrBlocksUnsorted.pem").c_str()))) { + return; + } + + size_t v4_count; + size_t v6_count; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_counts, (ip_addr_blocks_cert, &v4_count, &v6_count)); + result.test_sz_eq("V4 count is correct", v4_count, 3); + result.test_sz_eq("V6 count is correct", v6_count, 2); + + int has_safi; + uint8_t safi; + int present; + size_t count; + + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_ext_ip_addr_blocks_get_family, + (ip_addr_blocks_cert, 0, v4_count + 1, &has_safi, &safi, &present, &count)); + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_ext_ip_addr_blocks_get_family, + (ip_addr_blocks_cert, 1, v6_count + 1, &has_safi, &safi, &present, &count)); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (ip_addr_blocks_cert, 0, 2, &has_safi, &safi, &present, &count)); + + result.test_is_true("Family has a SAFI", has_safi == 1); + result.test_u8_eq("SAFI is correct", safi, 2); + result.test_is_true("Family is marked as inherit", present == 0); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (ip_addr_blocks_cert, 0, 1, &has_safi, &safi, &present, &count)); + + result.test_is_true("Family has a SAFI", has_safi == 1); + result.test_u8_eq("SAFI is correct", safi, 1); + result.test_is_true("Family is marked as inherit", present == 1); + result.test_sz_eq("Family has correct number of entries", count, 1); + + std::vector min_addr(4); + std::vector max_addr(4); + size_t out_len = 4; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_address, + (ip_addr_blocks_cert, 0, 1, 0, min_addr.data(), max_addr.data(), &out_len)); + + result.test_bin_eq("Min address is correct", min_addr, "C0A80000"); + result.test_bin_eq("Max address is correct", max_addr, "C8000000"); + + // only has 1 entry + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_ext_ip_addr_blocks_get_address, + (ip_addr_blocks_cert, 0, 1, 1, min_addr.data(), max_addr.data(), &out_len)); + + // only 5 families in total + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_ext_ip_addr_blocks_get_address, + (ip_addr_blocks_cert, 0, 5, 0, min_addr.data(), max_addr.data(), &out_len)); + + TEST_FFI_OK(botan_x509_cert_destroy, (ip_addr_blocks_cert)); + + botan_x509_cert_t as_blocks_cert; + TEST_FFI_OK(botan_x509_cert_load_file, + (&as_blocks_cert, Test::data_file("x509/x509test/ASNumberOnly.pem").c_str())); + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_info, (as_blocks_cert, 1, &present, &count)); + result.test_is_true("AS numbers are present", present == 1); + result.test_sz_eq("Correct number of AS ranges are present", count, 1); + + TEST_FFI_RC( + BOTAN_FFI_ERROR_NO_VALUE, botan_x509_ext_as_blocks_get_info, (as_blocks_cert, 0, &present, &count)); + + uint32_t min_as; + uint32_t max_as; + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_entry_at, (as_blocks_cert, 1, 0, &min_as, &max_as)); + result.test_u32_eq("Min AS number is correct", min_as, 0); + result.test_u32_eq("Max AS number is correct", max_as, 4294967295); + + TEST_FFI_RC(BOTAN_FFI_ERROR_OUT_OF_RANGE, + botan_x509_ext_as_blocks_get_entry_at, + (as_blocks_cert, 1, 1, &min_as, &max_as)); + + TEST_FFI_RC( + BOTAN_FFI_ERROR_NO_VALUE, botan_x509_ext_as_blocks_get_entry_at, (as_blocks_cert, 0, 0, &min_as, &max_as)); + + TEST_FFI_OK(botan_x509_cert_destroy, (as_blocks_cert)); + + botan_x509_cert_t no_ext_cert; + TEST_FFI_OK(botan_x509_cert_load_file, (&no_ext_cert, Test::data_file("x509/x509test/root.pem").c_str())); + TEST_FFI_RC( + BOTAN_FFI_ERROR_NO_VALUE, botan_x509_ext_ip_addr_blocks_get_counts, (no_ext_cert, &v4_count, &v6_count)); + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_ext_ip_addr_blocks_get_family, + (no_ext_cert, 0, 0, &has_safi, &safi, &present, &count)); + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_ext_ip_addr_blocks_get_address, + (no_ext_cert, 0, 0, 0, min_addr.data(), max_addr.data(), &out_len)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, botan_x509_ext_as_blocks_get_info, (no_ext_cert, 0, &present, &count)); + TEST_FFI_RC( + BOTAN_FFI_ERROR_NO_VALUE, botan_x509_ext_as_blocks_get_entry_at, (no_ext_cert, 0, 0, &min_as, &max_as)); + + TEST_FFI_OK(botan_x509_cert_destroy, (no_ext_cert)); + } +}; #endif class FFI_PKCS_Hashid_Test final : public FFI_Test { @@ -5775,6 +5886,7 @@ BOTAN_REGISTER_TEST("ffi", "ffi_srp6", FFI_SRP6_Test); 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); +BOTAN_REGISTER_TEST("ffi", "ffi_cert_ext_rfc3779", FFI_Cert_ExtRFC3779_Test); #endif #endif