From 691bc671f8d81bb2b350b443763dd5a08f24223a Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Tue, 27 May 2025 18:34:00 +0200 Subject: [PATCH] Improve RFC 3779 extension api --- src/lib/x509/x509_ext.cpp | 184 ++-- src/lib/x509/x509_ext.h | 112 +- .../x509/x509test/IPAddrBlocksUnsorted.pem | 17 + .../x509/x509test/InvalidIPAddrBlocks.pem | 15 + src/tests/test_x509_rpki.cpp | 975 ++++++++++++++++-- src/tests/tests.h | 7 + 6 files changed, 1116 insertions(+), 194 deletions(-) create mode 100644 src/tests/data/x509/x509test/IPAddrBlocksUnsorted.pem create mode 100644 src/tests/data/x509/x509test/InvalidIPAddrBlocks.pem diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp index 214258ce8b5..b7f7049cf93 100644 --- a/src/lib/x509/x509_ext.cpp +++ b/src/lib/x509/x509_ext.cpp @@ -871,6 +871,7 @@ std::vector IPAddressBlocks::encode_inner() const { void IPAddressBlocks::decode_inner(const std::vector& in) { BER_Decoder(in).decode_list(m_ip_addr_blocks).verify_end(); + sort_and_merge(); } void IPAddressBlocks::IPAddressFamily::encode_into(Botan::DER_Encoder& into) const { @@ -934,19 +935,16 @@ void IPAddressBlocks::sort_and_merge() { // // see: https://www.rfc-editor.org/rfc/rfc3779.html#section-2.2.3.3 // - // families with no safis are ordered before families with safis, - // v4 families are ordered before v6 families (i.e. they are sorted by afis) + // v4 families are ordered before v6 families (i.e. they are sorted by afis, primarily), + // families with no safis are ordered before families with safis // // families with the same afi/safi combination are then merged - // std::map is ordered, so a uint32_t of [afi,safi] will be in the right order - std::map> afam_map; + // std::map is ordered, so using a pair (afi, optional(safi)) here works - std::nullopt is sorted before any actual values + std::map>, std::vector> afam_map; for(const IPAddressFamily& block : m_ip_addr_blocks) { - uint32_t afam = block.afi(); - if(block.safi().has_value()) { - afam = static_cast(afam << 8) | block.safi().value(); - } - std::vector& fams = afam_map[afam]; + auto key = std::make_pair(block.afi(), block.safi()); + std::vector& fams = afam_map[key]; fams.push_back(block); } @@ -1156,7 +1154,7 @@ void IPAddressBlocks::IPAddressChoice::decode_from(Botan::BER_Decoder& from) } else if(next_tag == ASN1_Type::Sequence) { std::vector> ip_ranges; from.decode_list(ip_ranges); - m_ip_addr_ranges = ip_ranges; + m_ip_addr_ranges = sort_and_merge_ranges>(ip_ranges); } else { throw Decoding_Error(fmt("Unexpected type for IPAddressChoice {}", static_cast(next_tag))); } @@ -1217,22 +1215,23 @@ void IPAddressBlocks::IPAddressOrRange::encode_into(Botan::DER_Encoder& into) const uint8_t unused_bits = host % 8; bool octets_match = true; - for(size_t i = 0; i < static_cast(version_octets - discarded_octets); i++) { - if(min[i] != max[i]) { - octets_match = false; - break; - } - } - - // we only partially use this octet, and the used part has to match for prefix encoding bool used_bits_match = true; - // if all octets match we don't need to check this - if(!octets_match) { - // we have at least one non-matching, actually used octet - BOTAN_ASSERT_NOMSG(discarded_octets < version_octets); - const uint8_t shifted_min = (min[version_octets - 1 - discarded_octets] >> unused_bits); - const uint8_t shifted_max = (max[version_octets - 1 - discarded_octets] >> unused_bits); - used_bits_match = (shifted_min == shifted_max); + + // we have octets to check + if(discarded_octets < version_octets) { + // check all but the last octet + for(size_t i = 0; i < static_cast(version_octets - discarded_octets - 1); i++) { + if(min[i] != max[i]) { + octets_match = false; + break; + } + } + // check the last significant octet if we have matched so far + if(octets_match) { + const uint8_t shifted_min = (min[version_octets - 1 - discarded_octets] >> unused_bits); + const uint8_t shifted_max = (max[version_octets - 1 - discarded_octets] >> unused_bits); + used_bits_match = (shifted_min == shifted_max); + } } // both the full octets and the partially used one match @@ -1283,48 +1282,22 @@ void IPAddressBlocks::IPAddressOrRange::encode_into(Botan::DER_Encoder& into) template void IPAddressBlocks::IPAddressOrRange::decode_from(Botan::BER_Decoder& from) { - const size_t version_octets = static_cast(V); - const ASN1_Type next_tag = from.peek_next_object().type_tag(); // this can either be a prefix or a single address if(next_tag == ASN1_Type::BitString) { // construct a min and a max address from the prefix - std::vector prefix; - from.decode(prefix, ASN1_Type::OctetString, ASN1_Type::BitString, ASN1_Class::Universal); - - // we have to account for the octet at the beginning that specifies how many bits are unused in the last octet - if(prefix.empty() || prefix.size() > version_octets + 1) { - throw Decoding_Error( - fmt("IP address range entries must have a length between 2 and {} bits.", version_octets)); - } - - const uint8_t unused = prefix.front(); - const uint8_t discarded_octets = version_octets - (static_cast(prefix.size()) - 1); + std::vector prefix_min; + from.decode(prefix_min, ASN1_Type::OctetString, ASN1_Type::BitString, ASN1_Class::Universal); - prefix.erase(prefix.begin()); - - if(prefix.empty() && unused != 0) { - throw Decoding_Error("IP address range entry specified unused bits, but did not provide any octets."); - } - - for(size_t i = 0; i < discarded_octets; i++) { - prefix.push_back(0); - } + // copy because we modify the address in `decode_single_address`, but we need it twice for min and max + std::vector prefix_max(prefix_min); - m_min = IPAddress(prefix); - - for(size_t i = version_octets - discarded_octets; i < version_octets; i++) { - prefix[i] = 0xff; - } - - // set all unused bits to 1 - for(size_t i = 0; i < unused; i++) { - prefix[version_octets - 1 - discarded_octets] |= (1 << i); - } - - m_max = IPAddress(prefix); + // min address gets filled with 0's + m_min = decode_single_address(std::move(prefix_min), true); + // max address with 1's + m_max = decode_single_address(std::move(prefix_max), false); } else if(next_tag == ASN1_Type::Sequence) { // this is a range @@ -1336,69 +1309,64 @@ void IPAddressBlocks::IPAddressOrRange::decode_from(Botan::BER_Decoder& from) .decode(addr_max, ASN1_Type::OctetString, ASN1_Type::BitString, ASN1_Class::Universal) .end_cons(); - // address 1 (min) + m_min = decode_single_address(std::move(addr_min), true); + m_max = decode_single_address(std::move(addr_max), false); - // again accounting for the 'unused' octet at the beginning - if(addr_min.empty() || addr_min.size() > version_octets + 1) { - throw Decoding_Error( - fmt("IP address range entries must have a length between 1 and {} bytes.", version_octets)); - } - - // we don't technically need the unused bits, as per the encoding they are already zero, but we check just in case - const uint8_t unused_min = addr_min.front(); - addr_min.erase(addr_min.begin()); - - if(addr_min.empty() && unused_min != 0) { - throw Decoding_Error("IP address range entry specified unused bits, but did not provide any octets."); + if(m_min > m_max) { + throw Decoding_Error("IP address ranges must be sorted."); } + } else { + throw Decoding_Error(fmt("Unexpected type for IPAddressOrRange {}", static_cast(next_tag))); + } +} - // restore trailing zeros - const size_t addr_len_min = addr_min.size(); - for(size_t i = 0; i < version_octets - addr_len_min; i++) { - addr_min.push_back(0); - } +template +IPAddressBlocks::IPAddress IPAddressBlocks::IPAddressOrRange::decode_single_address(std::vector decoded, + bool min) { + const size_t version_octets = static_cast(V); - m_min = IPAddress(addr_min); + // decode a single address according to https://datatracker.ietf.org/doc/html/rfc3779#section-2.1.1 and following - // address 2 (max) + // we have to account for the octet at the beginning that specifies how many bits are unused in the last octet + if(decoded.empty() || decoded.size() > version_octets + 1) { + throw Decoding_Error(fmt("IP address range entries must have a length between 1 and {} bytes.", version_octets)); + } - if(addr_max.empty() || addr_max.size() > version_octets + 1) { - throw Decoding_Error( - fmt("IP address range entries must have a length between 1 and {} bytes.", version_octets)); - } + const uint8_t unused = decoded.front(); + const uint8_t discarded_octets = version_octets - (static_cast(decoded.size()) - 1); - // here we need the unused bits to restore the least significant 1s correctly - const uint8_t unused_max = addr_max.front(); - addr_max.erase(addr_max.begin()); + decoded.erase(decoded.begin()); - // we were given 0 actual address octets, but the unused octet claims there are unused bits - this doesn't make sense! - if(addr_max.empty() && unused_max != 0) { - throw Decoding_Error("IP address range entry specified unused bits, but did not provide any octets."); - } - - // restore trailing ones - const size_t addr_len_max = addr_max.size(); - for(size_t i = 0; i < version_octets - addr_len_max; i++) { - addr_max.push_back(0xff); - } + if(decoded.empty() && unused != 0) { + throw Decoding_Error("IP address range entry specified unused bits, but did not provide any octets."); + } - // restore ones stored in 'unused' octet (which is the last octet before trailing ones) - for(size_t i = 0; i < unused_max; i++) { - addr_max[addr_len_max - 1] |= (1 << i); - } + // if they were 8, the entire octet should have been discarded + if(unused > 7) { + throw Decoding_Error("IP address range entry specified invalid number of unused bits."); + } - m_max = IPAddress(addr_max); + // pad to version length with 0's for min addresses, 255's (0xff) for max addresses + uint8_t fill_discarded = min ? 0 : 0xff; + for(size_t i = 0; i < discarded_octets; i++) { + decoded.push_back(fill_discarded); + } - if(m_min > m_max) { - throw Decoding_Error("IP address ranges must be sorted."); + // for min addresses they should already be 0, but we set them to zero regardless + // for max addresses this turns the unused bits to 1 + for(size_t i = 0; i < unused; i++) { + if(min) { + decoded[version_octets - 1 - discarded_octets] &= ~(1 << i); + } else { + decoded[version_octets - 1 - discarded_octets] |= (1 << i); } - } else { - throw Decoding_Error(fmt("Unexpected type for IPAddressOrRange {}", static_cast(next_tag))); } + + return IPAddressBlocks::IPAddress(decoded); } template -IPAddressBlocks::IPAddress::IPAddress(std::span v) { +IPAddressBlocks::IPAddress::IPAddress(std::span v) { if(v.size() != Length) { throw Decoding_Error("number of bytes does not match IP version used"); } @@ -1510,6 +1478,10 @@ void ASBlocks::decode_inner(const std::vector& in) { void ASBlocks::ASIdentifiers::encode_into(Botan::DER_Encoder& into) const { into.start_sequence(); + if(!m_asnum.has_value() && !m_rdi.has_value()) { + throw Encoding_Error("One of asnum, rdi must be present"); + } + if(m_asnum.has_value()) { into.start_explicit(0); into.encode(m_asnum.value()); diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h index c58af220b67..5f1ea450677 100644 --- a/src/lib/x509/x509_ext.h +++ b/src/lib/x509/x509_ext.h @@ -580,14 +580,16 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio static constexpr size_t Length = static_cast(V); public: - IPAddress() = default; - explicit IPAddress(std::span v); + explicit IPAddress(std::span v); std::array value() const { return m_value; } - friend IPAddress operator++(const IPAddress v) { - std::array val = v.value(); - for(auto it = val.rbegin(); it != val.rend(); it++) { + private: + friend class IPAddressBlocks; + IPAddress() = default; + + void next() { + for(auto it = m_value.rbegin(); it != m_value.rend(); it++) { // we increment the current octet (*it)++; // if it did not wrap around we are done, else look at the next octet @@ -595,13 +597,12 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio break; } } - return IPAddress(val); } friend IPAddress operator+(IPAddress lhs, size_t rhs) { // we only really need to be able to compute +1, so this is fine for(size_t i = 0; i < rhs; i++) { - lhs = ++lhs; + lhs.next(); } return IPAddress(lhs); } @@ -621,7 +622,6 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio return lhs.value() == rhs.value(); } - private: std::array m_value; }; @@ -633,8 +633,7 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio IPAddressOrRange() = default; - // NOLINTNEXTLINE(*-explicit-conversions) FIXME - IPAddressOrRange(const IPAddress& addr) : m_min(addr), m_max(addr) {} + explicit IPAddressOrRange(const IPAddress& addr) : m_min(addr), m_max(addr) {} IPAddressOrRange(const IPAddress& min, const IPAddress& max) : m_min(min), m_max(max) { if(max < min) { @@ -649,6 +648,8 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio private: IPAddress m_min{}; IPAddress m_max{}; + + IPAddress decode_single_address(std::vector decoded, bool min); }; template @@ -661,8 +662,7 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio IPAddressChoice() = default; - // NOLINTNEXTLINE(*-explicit-conversions) FIXME - IPAddressChoice(std::optional>> ranges); + explicit IPAddressChoice(std::optional>> ranges); private: std::optional>> m_ip_addr_ranges; @@ -693,7 +693,7 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio const AddrChoice& addr_choice() const { return m_ip_addr_choice; } private: - uint16_t m_afi = 0; + uint16_t m_afi = 1; std::optional m_safi; AddrChoice m_ip_addr_choice; }; @@ -716,6 +716,38 @@ class BOTAN_PUBLIC_API(3, 9) IPAddressBlocks final : public Certificate_Extensio std::vector>& cert_status, size_t pos) override; + /// Add a single IP address to this extension (for the specified SAFI, if any) + template + void add_address(const std::array(V)>& address, + std::optional safi = std::nullopt) { + add_address(address, address, safi); + } + + /// Add an IP address range to this extension (for the specified SAFI, if any) + template + void add_address(const std::array(V)>& min, + const std::array(V)>& max, + std::optional safi = std::nullopt) { + std::vector> addresses = {IPAddressOrRange(IPAddress(min), IPAddress(max))}; + m_ip_addr_blocks.push_back(IPAddressFamily(IPAddressChoice(addresses), safi)); + sort_and_merge(); + } + + /// Make the extension contain no allowed IP addresses for the specified IP version (and SAFI, if any) + template + void restrict(std::optional safi = std::nullopt) { + std::vector> addresses = {}; + m_ip_addr_blocks.push_back(IPAddressFamily(IPAddressChoice(addresses), safi)); + sort_and_merge(); + } + + /// Mark the specified IP version as 'inherit' (for the specified SAFI, if any) + template + void inherit(std::optional safi = std::nullopt) { + m_ip_addr_blocks.push_back(IPAddressFamily(IPAddressChoice(), safi)); + sort_and_merge(); + } + const std::vector& addr_blocks() const { return m_ip_addr_blocks; } private: @@ -787,10 +819,8 @@ class BOTAN_PUBLIC_API(3, 9) ASBlocks final : public Certificate_Extension { void encode_into(DER_Encoder&) const override; void decode_from(BER_Decoder& from) override; - ASIdentifiers() = default; - - ASIdentifiers(const std::optional& asnum, - const std::optional& rdi) : + explicit ASIdentifiers(const std::optional& asnum, + const std::optional& rdi) : m_asnum(asnum), m_rdi(rdi) { if(!m_asnum.has_value() && !m_rdi.has_value()) { throw Decoding_Error("One of asnum, rdi must be present"); @@ -802,6 +832,9 @@ class BOTAN_PUBLIC_API(3, 9) ASBlocks final : public Certificate_Extension { const std::optional& rdi() const { return m_rdi; } private: + friend class ASBlocks; + ASIdentifiers() = default; + std::optional m_asnum; std::optional m_rdi; }; @@ -822,6 +855,40 @@ class BOTAN_PUBLIC_API(3, 9) ASBlocks final : public Certificate_Extension { std::vector>& cert_status, size_t pos) override; + /// Add a single asnum to this extension + void add_asnum(asnum_t asnum) { add_asnum(asnum, asnum); } + + /// Add an asnum range to this extension + void add_asnum(asnum_t min, asnum_t max) { + m_as_identifiers = ASIdentifiers(add_new(m_as_identifiers.asnum(), min, max), m_as_identifiers.rdi()); + } + + /// Make the extension contain no allowed asnum's + void restrict_asnum() { + std::vector empty; + m_as_identifiers = ASIdentifiers(ASIdentifierChoice(empty), m_as_identifiers.rdi()); + } + + /// Mark the asnum entry as 'inherit' + void inherit_asnum() { m_as_identifiers = ASIdentifiers(ASIdentifierChoice(), m_as_identifiers.rdi()); } + + /// Add a single rdi to this extension + void add_rdi(asnum_t rdi) { add_rdi(rdi, rdi); } + + /// Add an rdi range to this extension + void add_rdi(asnum_t min, asnum_t max) { + m_as_identifiers = ASIdentifiers(m_as_identifiers.asnum(), add_new(m_as_identifiers.rdi(), min, max)); + } + + /// Make the extension contain no allowed rdi's + void restrict_rdi() { + std::vector empty; + m_as_identifiers = ASIdentifiers(m_as_identifiers.asnum(), ASIdentifierChoice(empty)); + } + + /// Mark the rdi entry as 'inherit' + void inherit_rdi() { m_as_identifiers = ASIdentifiers(m_as_identifiers.asnum(), ASIdentifierChoice()); } + const ASIdentifiers& as_identifiers() const { return m_as_identifiers; } private: @@ -831,6 +898,17 @@ class BOTAN_PUBLIC_API(3, 9) ASBlocks final : public Certificate_Extension { bool should_encode() const override { return true; } + ASIdentifierChoice add_new(const std::optional& old, asnum_t min, asnum_t max) { + std::vector range; + if(!old.has_value() || !old.value().ranges().has_value()) { + range = {ASIdOrRange(min, max)}; + } else { + range = old.value().ranges().value(); + range.push_back(ASIdOrRange(min, max)); + } + return ASIdentifierChoice(range); + } + std::vector encode_inner() const override; void decode_inner(const std::vector&) override; }; diff --git a/src/tests/data/x509/x509test/IPAddrBlocksUnsorted.pem b/src/tests/data/x509/x509test/IPAddrBlocksUnsorted.pem new file mode 100644 index 00000000000..23804031c2b --- /dev/null +++ b/src/tests/data/x509/x509test/IPAddrBlocksUnsorted.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqzCCAmKgAwIBAgIRANPort9DlhqMt2QI6bFLA+IwCgYIKoZIzj0EAwIwSTEQ +MA4GA1UEAxMHVGVzdCBDQTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUJvdGFuIFBy +b2plY3QxEDAOBgNVBAsTB1Rlc3RpbmcwHhcNMjUwNjE0MTkxNjEzWhcNMjYwNjE0 +MTkxNjEzWjBJMRAwDgYDVQQDEwdUZXN0IENBMQswCQYDVQQGEwJVUzEWMBQGA1UE +ChMNQm90YW4gUHJvamVjdDEQMA4GA1UECxMHVGVzdGluZzBJMBMGByqGSM49AgEG +CCqGSM49AwEBAzIABN0stcHCSpEww/+tZrO2Uv36ZJmjLel058Rdr5tdShPCNEmy +MeXB+cGQ1kWVMh+sp6OCATkwggE1MHMGCCsGAQUFBwEHBGcwZTAHBAMAAgEFADAZ +BAIAAjATAxEA/////////////////////zAHBAMAAQIFADAVBAMAAQEwDjAMAwMD +wKgDBQHAqAIAMBcEAwABATAQMA4DBQHAqAICAwUAyAAAADAGBAIAAQUAMCEGA1Ud +DgQaBBgub8YveBEYQ3Q3XbeiHtrh38tnkuzOOtQwDgYDVR0PAQH/BAQDAgGGMFIG +A1UdEQRLMEmBFXRlc3RpbmdAcmFuZG9tYml0Lm5ldIITYm90YW4ucmFuZG9tYml0 +Lm5ldIYbaHR0cHM6Ly9ib3Rhbi5yYW5kb21iaXQubmV0MBIGA1UdEwEB/wQIMAYB +Af8CAQEwIwYDVR0jBBwwGoAYLm/GL3gRGEN0N123oh7a4d/LZ5LszjrUMAoGCCqG +SM49BAMCAzcAMDQCGF6Idq8d0ibVHxOTBA7xzFrquTz7crUfBAIYMNxljBJPw+CX +VaIdhfLji2fOE9P8vx9O +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/x509test/InvalidIPAddrBlocks.pem b/src/tests/data/x509/x509test/InvalidIPAddrBlocks.pem new file mode 100644 index 00000000000..d53bfdfaf28 --- /dev/null +++ b/src/tests/data/x509/x509test/InvalidIPAddrBlocks.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICUzCCAgmgAwIBAgIRAPphGYQXHPWC9Y9mBr3lVkQwCgYIKoZIzj0EAwIwSTEQ +MA4GA1UEAxMHVGVzdCBDQTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUJvdGFuIFBy +b2plY3QxEDAOBgNVBAsTB1Rlc3RpbmcwHhcNMjUwNjE0MTkyMTIwWhcNMjYwNjE0 +MTkyMTIwWjBJMRAwDgYDVQQDEwdUZXN0IENBMQswCQYDVQQGEwJVUzEWMBQGA1UE +ChMNQm90YW4gUHJvamVjdDEQMA4GA1UECxMHVGVzdGluZzBJMBMGByqGSM49AgEG +CCqGSM49AwEBAzIABLT1a9v+orDaWRmzckbwZge9S94Sc4rUhTTD/neIxwtoRYIh +cY49G1rv+KnGRXoeOKOB4TCB3jAcBggrBgEFBQcBBwQQMA4wDAQCAAEwBgMECQoA +IDAhBgNVHQ4EGgQYGgRoConyOCjqg7Gs2mK1rDUrR4Sjr8v0MA4GA1UdDwEB/wQE +AwIBhjBSBgNVHREESzBJgRV0ZXN0aW5nQHJhbmRvbWJpdC5uZXSCE2JvdGFuLnJh +bmRvbWJpdC5uZXSGG2h0dHBzOi8vYm90YW4ucmFuZG9tYml0Lm5ldDASBgNVHRMB +Af8ECDAGAQH/AgEBMCMGA1UdIwQcMBqAGBoEaAqJ8jgo6oOxrNpitaw1K0eEo6/L +9DAKBggqhkjOPQQDAgM4ADA1AhkAhMM9sBVJYDp1/6eZw+WX0tHH/OuPJeGxAhgM +w4b5TgHFvyYquWJ4pZq1SwmZnDotYXU= +-----END CERTIFICATE----- diff --git a/src/tests/test_x509_rpki.cpp b/src/tests/test_x509_rpki.cpp index 1e7f8118234..a033c972c5e 100644 --- a/src/tests/test_x509_rpki.cpp +++ b/src/tests/test_x509_rpki.cpp @@ -115,6 +115,15 @@ std::tuple get_sig_algo_padding() { return std::make_tuple(sig_algo, padding_method, hash_fn); } +Botan::X509_Certificate make_self_signed(std::unique_ptr& rng, + const Botan::X509_Cert_Options& opts = std::move(ca_opts())) { + auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding(); + auto key = generate_key(sig_algo, *rng); + const auto cert = Botan::X509::create_self_signed_cert(opts, *key, hash_fn, *rng); + + return cert; +} + CA_Creation_Result make_ca(std::unique_ptr& rng, const Botan::X509_Cert_Options& opts = std::move(ca_opts())) { auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding(); @@ -152,53 +161,136 @@ constexpr auto IPv6 = Botan::Cert_Extension::IPAddressBlocks::Version::IPv6; Test::Result test_x509_ip_addr_blocks_extension_decode() { Test::Result result("X509 IP Address Block decode"); result.start_timer(); - const std::string filename("IPAddrBlocksAll.pem"); - - Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); - using Botan::Cert_Extension::IPAddressBlocks; - auto ip_addr_blocks = cert.v3_extensions().get_extension_object_as(); - - const auto& addr_blocks = ip_addr_blocks->addr_blocks(); - result.confirm("cert has IPAddrBlocks extension", ip_addr_blocks != nullptr, true); - result.test_eq("cert has two IpAddrBlocks", addr_blocks.size(), 2); - - const auto& ipv4block = std::get>(addr_blocks[0].addr_choice()); - const auto& ipv6block = std::get>(addr_blocks[1].addr_choice()); - - auto& v4_blocks = ipv4block.ranges().value(); - - // 192.168.0.0 - result.test_eq("ipv4 block 0 min", v4_blocks[0].min().value(), "C0A80000"); - // 192.168.127.255 - result.test_eq("ipv4 block 0 max", v4_blocks[0].max().value(), "C0A87FFF"); - - // 193.168.0.0 - result.test_eq("ipv4 block 1 min", v4_blocks[1].min().value(), "C1A80000"); - // 193.169.255.255 - result.test_eq("ipv4 block 1 max", v4_blocks[1].max().value(), "C1A9FFFF"); - - // 194.168.0.0 - result.test_eq("ipv4 block 2 min", v4_blocks[2].min().value(), "C2A80000"); - // 195.175.1.2 - result.test_eq("ipv4 block 2 max", v4_blocks[2].max().value(), "C3AF0102"); + { + const std::string filename("IPAddrBlocksAll.pem"); + Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); + auto ip_addr_blocks = cert.v3_extensions().get_extension_object_as(); + + const auto& addr_blocks = ip_addr_blocks->addr_blocks(); + result.confirm("cert has IPAddrBlocks extension", ip_addr_blocks != nullptr, true); + result.test_eq("cert has two IpAddrBlocks", addr_blocks.size(), 2); + + const auto& ipv4block = std::get>(addr_blocks[0].addr_choice()); + const auto& ipv6block = std::get>(addr_blocks[1].addr_choice()); + + auto& v4_blocks = ipv4block.ranges().value(); + + // cert contains (in this order) + // 192.168.0.0 - 192.168.127.255 (192.168.0.0/17) + // 193.168.0.0 - 193.169.255.255 (193.168.0.0/15) + // 194.168.0.0 - 195.175.1.2 + // 196.168.0.1 - 196.168.0.1 (196.168.0.1/32) + + result.test_eq("ipv4 block 0 min", v4_blocks[0].min().value(), {192, 168, 0, 0}); + result.test_eq("ipv4 block 0 max", v4_blocks[0].max().value(), {192, 168, 127, 255}); + + result.test_eq("ipv4 block 1 min", v4_blocks[1].min().value(), {193, 168, 0, 0}); + result.test_eq("ipv4 block 1 max", v4_blocks[1].max().value(), {193, 169, 255, 255}); + result.test_eq("ipv4 block 2 min", v4_blocks[2].min().value(), {194, 168, 0, 0}); + result.test_eq("ipv4 block 2 max", v4_blocks[2].max().value(), {195, 175, 1, 2}); + + result.test_eq("ipv4 block 3 min", v4_blocks[3].min().value(), {196, 168, 0, 1}); + result.test_eq("ipv4 block 3 max", v4_blocks[3].max().value(), {196, 168, 0, 1}); + + auto& v6_blocks = ipv6block.ranges().value(); + + // cert contains (in this order) + // fa80::/65 + // fe20::/37 + // 2003:0:6829:3435:420:10c5:0:c4/128 + // ab01:0:0:0:0:0:0:1-cd02:0:0:0:0:0:0:2 + + result.test_eq("ipv6 block 0 min", + v6_blocks[0].min().value(), + {0x20, 0x03, 0x00, 0x00, 0x68, 0x29, 0x34, 0x35, 0x04, 0x20, 0x10, 0xc5, 0x00, 0x00, 0x00, 0xc4}); + result.test_eq("ipv6 block 0 max", + v6_blocks[0].max().value(), + {0x20, 0x03, 0x00, 0x00, 0x68, 0x29, 0x34, 0x35, 0x04, 0x20, 0x10, 0xc5, 0x00, 0x00, 0x00, 0xc4}); + result.test_eq("ipv6 block 1 min", + v6_blocks[1].min().value(), + {0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}); + result.test_eq("ipv6 block 1 max", + v6_blocks[1].max().value(), + {0xcd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}); + result.test_eq("ipv6 block 2 min", + v6_blocks[2].min().value(), + {0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + result.test_eq("ipv6 block 2 max", + v6_blocks[2].max().value(), + {0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}); + result.test_eq("ipv6 block 3 min", + v6_blocks[3].min().value(), + {0xfe, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + result.test_eq("ipv6 block 3 max", + v6_blocks[3].max().value(), + {0xfe, 0x20, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}); + } + { + const std::string filename("IPAddrBlocksUnsorted.pem"); + Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); + auto ip_addr_blocks = cert.v3_extensions().get_extension_object_as(); + + // cert contains (in this order) + // IPv6 (1) inherit + // IPv6 0xff....0xff + // IPv4 (2) inherit + // IPv4 (1) 192.168.0.0 - 192.168.2.1 + // IPv4 (1) 192.168.2.2 - 200.0.0.0 + // IPv4 inherit + + // IPv4 ranges should be merged, IPv4 should come before IPv6, all should be sorted by safi + + const auto& addr_blocks = ip_addr_blocks->addr_blocks(); + result.test_eq("cert has two IpAddrBlocks", addr_blocks.size(), 5); + + result.test_eq("block 0 has no safi", addr_blocks[0].safi(), std::optional{std::nullopt}); + result.confirm( + "block 0 is inherited", + !std::get>(addr_blocks[0].addr_choice()).ranges().has_value()); + + result.test_eq("block 1 has correct safi", addr_blocks[1].safi(), std::optional{1}); + const auto& block_1 = + std::get>(addr_blocks[1].addr_choice()).ranges().value(); + + result.confirm("block 1 has correct size", block_1.size() == 1); + result.test_eq("block 1 min is correct", block_1[0].min().value(), {192, 168, 0, 0}); + result.test_eq("block 1 max is correct", block_1[0].max().value(), {200, 0, 0, 0}); + + result.test_eq("block 2 has correct safi", addr_blocks[2].safi(), std::optional{2}); + result.confirm( + "block 2 is inherited", + !std::get>(addr_blocks[2].addr_choice()).ranges().has_value()); + + result.test_eq("block 3 has no safi", addr_blocks[3].safi(), std::optional{std::nullopt}); + const auto& block_3 = + std::get>(addr_blocks[3].addr_choice()).ranges().value(); + + result.confirm("block 3 has correct size", block_3.size() == 1); + result.test_eq("block 3 min is correct", + block_3[0].min().value(), + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}); + result.test_eq("block 3 max is correct", + block_3[0].max().value(), + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}); + + result.test_eq("block 24 has correct safi", addr_blocks[4].safi(), std::optional{1}); + result.confirm( + "block 4 is inherited", + !std::get>(addr_blocks[4].addr_choice()).ranges().has_value()); + } + { + const std::string filename("InvalidIPAddrBlocks.pem"); + Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); - // 196.168.0.1 - result.test_eq("ipv4 block 3 min", v4_blocks[3].min().value(), "C4A80001"); - // 196.168.0.1 - result.test_eq("ipv4 block 3 max", v4_blocks[3].max().value(), "C4A80001"); + // cert contains the 10.0.32.0/20 prefix, but with a 9 for the unused bits - auto& v6_blocks = ipv6block.ranges().value(); + result.confirm("extension is present", cert.v3_extensions().extension_set(IPAddressBlocks::static_oid())); - result.test_eq("ipv6 block 0 min", v6_blocks[0].min().value(), "FA800000000000000000000000000000"); - result.test_eq("ipv6 block 0 max", v6_blocks[0].max().value(), "FA800000000000007FFFFFFFFFFFFFFF"); - result.test_eq("ipv6 block 1 min", v6_blocks[1].min().value(), "FE200000000000000000000000000000"); - result.test_eq("ipv6 block 1 max", v6_blocks[1].max().value(), "FE20000007FFFFFFFFFFFFFFFFFFFFFF"); - result.test_eq("ipv6 block 2 min", v6_blocks[2].min().value(), "2003000068293435042010C5000000C4"); - result.test_eq("ipv6 block 2 max", v6_blocks[2].max().value(), "2003000068293435042010C5000000C4"); - result.test_eq("ipv6 block 3 min", v6_blocks[3].min().value(), "AB010000000000000000000000000001"); - result.test_eq("ipv6 block 3 max", v6_blocks[3].max().value(), "CD020000000000000000000000000002"); + auto ext = cert.v3_extensions().get_extension_object_as(); + result.confirm("extension is not decoded", ext == nullptr); + } result.end_timer(); return result; @@ -284,8 +376,155 @@ Test::Result test_x509_as_blocks_extension_decode() { #endif -Test::Result test_x509_ip_addr_blocks_extension_encode() { - Test::Result result("X509 IP Address Block encode"); +Test::Result test_x509_ip_addr_blocks_rfc3779_example() { + Test::Result result("X509 IP Address Blocks rfc3779 example"); + result.start_timer(); + + using Botan::Cert_Extension::IPAddressBlocks; + auto rng = Test::new_rng(__func__); + + // construct like in https://datatracker.ietf.org/doc/html/rfc3779#page-18 + std::unique_ptr blocks_1 = std::make_unique(); + blocks_1->add_address({10, 0, 32, 0}, {10, 0, 47, 255}, 1); + blocks_1->add_address({10, 0, 64, 0}, {10, 0, 64, 255}, 1); + blocks_1->add_address({10, 1, 0, 0}, {10, 1, 255, 255}, 1); + blocks_1->add_address({10, 2, 48, 0}, {10, 2, 63, 255}, 1); + blocks_1->add_address({10, 2, 64, 0}, {10, 2, 64, 255}, 1); + blocks_1->add_address({10, 3, 0, 0}, {10, 3, 255, 255}, 1); + blocks_1->inherit(); + + Botan::X509_Cert_Options opts_1 = ca_opts(); + opts_1.extensions.add(std::move(blocks_1)); + + auto cert_1 = make_self_signed(rng, opts_1); + + auto bits_1 = cert_1.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); + + result.test_eq( + "extension is encoded as specified", + bits_1, + "3035302B040300010130240304040A00200304000A00400303000A01300C0304040A02300304000A02400303000A033006040200020500"); + + auto ext_1 = cert_1.v3_extensions().get_extension_object_as(); + + auto ext_1_addr_fam_1 = ext_1->addr_blocks()[0]; + result.test_eq("extension 1 ipv4 safi", ext_1_addr_fam_1.safi(), std::optional(1)); + auto ext_1_ranges = + std::get>(ext_1_addr_fam_1.addr_choice()).ranges().value(); + result.test_eq("extension 1 range 1 min", ext_1_ranges[0].min().value(), {10, 0, 32, 0}); + result.test_eq("extension 1 range 1 max", ext_1_ranges[0].max().value(), {10, 0, 47, 255}); + + result.test_eq("extension 1 range 2 min", ext_1_ranges[1].min().value(), {10, 0, 64, 0}); + result.test_eq("extension 1 range 2 max", ext_1_ranges[1].max().value(), {10, 0, 64, 255}); + + result.test_eq("extension 1 range 3 min", ext_1_ranges[2].min().value(), {10, 1, 0, 0}); + result.test_eq("extension 1 range 3 max", ext_1_ranges[2].max().value(), {10, 1, 255, 255}); + + result.test_eq("extension 1 range 4 min", ext_1_ranges[3].min().value(), {10, 2, 48, 0}); + result.test_eq("extension 1 range 4 max", ext_1_ranges[3].max().value(), {10, 2, 64, 255}); + + result.test_eq("extension 1 range 5 min", ext_1_ranges[4].min().value(), {10, 3, 0, 0}); + result.test_eq("extension 1 range 5 max", ext_1_ranges[4].max().value(), {10, 3, 255, 255}); + + result.test_eq("extension 1 ipv6 safi", ext_1->addr_blocks()[1].safi(), std::optional{std::nullopt}); + result.confirm( + "extension 1 ipv6 inherited", + !std::get>(ext_1->addr_blocks()[1].addr_choice()).ranges().has_value()); + + // https://datatracker.ietf.org/doc/html/rfc3779#page-20 + std::unique_ptr blocks_2 = std::make_unique(); + blocks_2->add_address( + {0x20, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x20, 0x01, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}); + blocks_2->add_address({10, 0, 0, 0}, {10, 255, 255, 255}, 1); + blocks_2->add_address({172, 16, 0, 0}, {172, 31, 255, 255}, 1); + blocks_2->inherit(2); + + Botan::X509_Cert_Options opts_2 = ca_opts(); + opts_2.extensions.add(std::move(blocks_2)); + + auto cert_2 = make_self_signed(rng, opts_2); + + auto bits_2 = cert_2.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); + + // see https://www.rfc-editor.org/errata/eid6792 as to why the B0 specified in the RFC is a AC here + result.test_eq("extension is encoded as specified", + bits_2, + "302C3010040300010130090302000A030304AC10300704030001020500300F040200023009030700200100000002"); + + auto ext_2 = cert_2.v3_extensions().get_extension_object_as(); + + auto ext_2_addr_fam_1 = ext_2->addr_blocks()[0]; + result.test_eq("extension 2 ipv4 1 safi", ext_2_addr_fam_1.safi(), std::optional(1)); + auto ext_2_ranges_1 = + std::get>(ext_2_addr_fam_1.addr_choice()).ranges().value(); + result.test_eq("extension 2 fam 1 range 1 min", ext_2_ranges_1[0].min().value(), {10, 0, 0, 0}); + result.test_eq("extension 2 fam 1 range 1 max", ext_2_ranges_1[0].max().value(), {10, 255, 255, 255}); + + result.test_eq("extension 2 fam 1 range 2 min", ext_2_ranges_1[1].min().value(), {172, 16, 0, 0}); + result.test_eq("extension 2 fam 1 range 2 max", ext_2_ranges_1[1].max().value(), {172, 31, 255, 255}); + + result.test_eq("extension 2 ipv4 2 safi", ext_2->addr_blocks()[1].safi(), std::optional{2}); + result.confirm( + "extension 2 ipv4 2 inherited", + !std::get>(ext_2->addr_blocks()[1].addr_choice()).ranges().has_value()); + + auto ext_2_addr_fam_3 = ext_2->addr_blocks()[2]; + result.test_eq("extension 2 ipv4 1 safi", ext_2_addr_fam_3.safi(), std::optional(std::nullopt)); + auto ext_2_ranges_3 = + std::get>(ext_2_addr_fam_3.addr_choice()).ranges().value(); + result.test_eq("extension 2 fam 3 range 1 min", + ext_2_ranges_3[0].min().value(), + {0x20, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + result.test_eq("extension 2 fam 3 range 1 max", + ext_2_ranges_3[0].max().value(), + {0x20, 0x01, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}); + + result.end_timer(); + return result; +} + +Test::Result test_x509_ip_addr_blocks_encode_builder() { + Test::Result result("X509 IP Address Blocks encode (builder)"); + result.start_timer(); + + using Botan::Cert_Extension::IPAddressBlocks; + auto rng = Test::new_rng(__func__); + + std::unique_ptr blocks = std::make_unique(); + + // 64 - 127 + blocks->add_address({192, 168, 0b01000000, 0}, {192, 168, 0b01111111, 255}, 2); + + blocks->add_address({255, 255, 255, 255}); + // encoded as prefix + blocks->add_address({190, 5, 0, 0}, {190, 5, 0b01111111, 255}); + // encoded as min, max + blocks->add_address({127, 0, 0, 1}, {189, 5, 7, 255}); + + // full address range + blocks->add_address({0, 0, 0, 0}, {255, 255, 255, 255}, 1); + + blocks->add_address({123, 123, 2, 1}); + + Botan::X509_Cert_Options opts = ca_opts(); + opts.extensions.add(std::move(blocks)); + + auto cert = make_self_signed(rng, opts); + auto bits = cert.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); + + // hand validated with https://lapo.it/asn1js/ + result.test_eq( + "extension is encoded as specified", + bits, + "304630290402000130230305007B7B0201300D0305007F000001030403BD0500030407BE0500030500FFFFFFFF300A04030001013003030100300D04030001023006030406C0A840"); + + result.end_timer(); + return result; +} + +Test::Result test_x509_ip_addr_blocks_extension_encode_ctor() { + Test::Result result("X509 IP Address Block encode (ctor)"); result.start_timer(); using Botan::Cert_Extension::IPAddressBlocks; @@ -457,8 +696,8 @@ Test::Result test_x509_ip_addr_blocks_extension_encode() { return result; } -Test::Result test_x509_ip_addr_blocks_extension_encode_edge_cases() { - Test::Result result("X509 IP Address Block encode edge cases"); +Test::Result test_x509_ip_addr_blocks_extension_encode_edge_cases_ctor() { + Test::Result result("X509 IP Address Block encode edge cases (ctor)"); result.start_timer(); using Botan::Cert_Extension::IPAddressBlocks; @@ -618,14 +857,20 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { uint8_t v4_bytes_1[4] = {123, 123, 123, 123}; IPAddressBlocks::IPAddress v4_addr_1(v4_bytes_1); // create 2 prefixes from the v4 addresses -> they should be merged - IPAddressBlocks::IPAddressChoice v4_choice_dupl({{{{v4_addr_1}, {v4_addr_1}}}}); + + std::vector> v4_choice_vec{ + IPAddressBlocks::IPAddressOrRange(IPAddressBlocks::IPAddress({v4_addr_1}))}; + IPAddressBlocks::IPAddressChoice v4_choice_dupl(v4_choice_vec); result.confirm( "IPAddressChoice v4 merges ranges already in constructor", v4_choice_dupl.ranges().value().size() == 1, true); IPAddressBlocks::IPAddressFamily v4_fam_dupl(v4_choice_dupl, 0); uint8_t v6_bytes_1[16] = {123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123}; IPAddressBlocks::IPAddress v6_addr_1(v6_bytes_1); - IPAddressBlocks::IPAddressChoice v6_choice_dupl({{{{v6_addr_1}, {v6_addr_1}}}}); + + std::vector> v6_choice_vec{ + IPAddressBlocks::IPAddressOrRange(IPAddressBlocks::IPAddress({v6_addr_1}))}; + IPAddressBlocks::IPAddressChoice v6_choice_dupl(v6_choice_vec); result.confirm( "IPAddressChoice v6 merges already in constructor", v6_choice_dupl.ranges().value().size() == 1, true); IPAddressBlocks::IPAddressFamily v6_fam_dupl(v6_choice_dupl, 0); @@ -639,8 +884,8 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { /* considering the push order, the resulting order should be [0] v4 no safi - [1] v6 no safi [2] v4 safi + [1] v6 no safi [3] v6 safi */ for(size_t i = 0; i < 3; i++) { @@ -653,7 +898,7 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { } std::vector expected_blocks = { - v4_empty_fam, v6_empty_fam, v4_fam_dupl, v4_empty_fam_safi, v6_fam_dupl, v6_empty_fam_safi}; + v4_empty_fam, v4_fam_dupl, v4_empty_fam_safi, v6_empty_fam, v6_fam_dupl, v6_empty_fam_safi}; std::unique_ptr blocks = std::make_unique(addr_blocks); @@ -676,11 +921,15 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { uint32_t afam_a = a.afi(); if(a.safi().has_value()) { afam_a = static_cast(afam_a << 8) | a.safi().value(); + } else { + afam_a = static_cast(afam_a << 8); } uint32_t afam_b = b.afi(); if(b.safi().has_value()) { afam_b = static_cast(afam_b << 8) | b.safi().value(); + } else { + afam_b = static_cast(afam_b << 8); } if(afam_a > afam_b) { @@ -777,8 +1026,100 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { return result; } -Test::Result test_x509_ip_addr_blocks_path_validation_success() { - Test::Result result("X509 IP Address Block path validation success"); +Test::Result test_x509_ip_addr_blocks_path_validation_success_builder() { + Test::Result result("X509 IP Address Blocks path validation success (builder)"); + result.start_timer(); + + using Botan::Cert_Extension::IPAddressBlocks; + auto rng = Test::new_rng(__func__); + + /* + Creates a certificate chain of length 4. + Root: ipv4 and ipv6 + Inherit: has both values as 'inherit' + Dynamic: has either both 'inherit', both with values, or just one with a value + Subject: both ipv4 and ipv6 as a subset of Root / Dynamic + */ + + // Root cert + std::unique_ptr root_blocks = std::make_unique(); + + root_blocks->add_address({120, 0, 0, 1}, {130, 140, 150, 160}, 42); + root_blocks->add_address({10, 0, 0, 1}, {10, 255, 255, 255}, 42); + + root_blocks->add_address( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xA0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + root_blocks->add_address( + {0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + // Inherit cert + std::unique_ptr inherit_blocks = std::make_unique(); + + inherit_blocks->inherit(42); + inherit_blocks->inherit(); + + // Subject cert + std::unique_ptr sub_blocks = std::make_unique(); + + sub_blocks->add_address({124, 0, 255, 0}, {126, 0, 0, 1}, 42); + sub_blocks->add_address({10, 0, 2, 1}, {10, 42, 0, 255}, 42); + + sub_blocks->add_address( + {0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x0D, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + + Botan::X509_Cert_Options root_opts = ca_opts(); + root_opts.extensions.add(std::move(root_blocks)); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); + sub_opts.extensions.add(std::move(sub_blocks)); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + + Botan::Certificate_Store_In_Memory trusted; + trusted.add_certificate(root_cert); + + for(size_t i = 0; i < 4; i++) { + bool include_v4 = i & 1; + bool include_v6 = (i >> 1) & 1; + + // Dynamic Cert + std::unique_ptr dyn_blocks = std::make_unique(); + if(include_v4) { + dyn_blocks->add_address({122, 0, 0, 255}, {128, 255, 255, 255}, 42); + dyn_blocks->add_address({10, 0, 0, 255}, {10, 255, 0, 1}, 42); + } else { + dyn_blocks->inherit(42); + } + + if(include_v6) { + dyn_blocks->add_address( + {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + } else { + dyn_blocks->inherit(); + } + + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + + Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + Botan::X509_Certificate sub_cert = + dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + + const Botan::Path_Validation_Restrictions restrictions(false, 80); + std::vector certs = {sub_cert, dyn_cert, inherit_cert}; + + Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted); + result.require("path validation succeeds", path_result.successful_validation()); + } + + result.end_timer(); + return result; +} + +Test::Result test_x509_ip_addr_blocks_path_validation_success_ctor() { + Test::Result result("X509 IP Address Block path validation success (ctor)"); result.start_timer(); using Botan::Cert_Extension::IPAddressBlocks; @@ -936,8 +1277,92 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success() { return result; } -Test::Result test_x509_ip_addr_blocks_path_validation_failure() { - Test::Result result("X509 IP Address Block path validation failure"); +Test::Result test_x509_ip_addr_blocks_path_validation_failure_builder() { + Test::Result result("X509 IP Address Blocks path validation failure (builder)"); + result.start_timer(); + + using Botan::Cert_Extension::IPAddressBlocks; + auto rng = Test::new_rng(__func__); + + for(size_t i = 0; i < 7; i++) { + bool all_inherit = (i == 0); + bool different_safi = (i == 1); + bool too_small_subrange = (i == 2); + bool too_large_subrange = (i == 3); + bool no_more_issuer_ranges = (i == 4); + bool empty_issuer_ranges = (i == 5); + bool nullptr_extensions = (i == 6); + + // Root cert + std::unique_ptr root_blocks = std::make_unique(); + if(!all_inherit) { + root_blocks->add_address({120, 0, 0, 1}, {130, 140, 150, 160}, 42); + } else { + root_blocks->inherit(42); + } + + Botan::X509_Cert_Options root_opts = ca_opts(); + if(!nullptr_extensions) { + root_opts.extensions.add(std::move(root_blocks)); + } + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + + // Issuer Cert + std::unique_ptr iss_blocks = std::make_unique(); + if(!all_inherit) { + if(empty_issuer_ranges) { + iss_blocks->restrict(42); + } else { + iss_blocks->add_address({122, 0, 0, 255}, {128, 255, 255, 255}, 42); + } + } else { + iss_blocks->inherit(42); + } + + auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, rng); + + // Subject cert + std::unique_ptr sub_blocks = std::make_unique(); + + uint8_t safi = different_safi ? 41 : 42; + + if(!all_inherit) { + if(too_small_subrange) { + sub_blocks->add_address({118, 0, 255, 0}, {126, 0, 0, 1}, safi); + } else if(too_large_subrange) { + sub_blocks->add_address({124, 0, 255, 0}, {134, 0, 0, 1}, safi); + } else if(no_more_issuer_ranges) { + sub_blocks->add_address({140, 0, 0, 1}, {150, 0, 0, 1}, safi); + } else { + sub_blocks->add_address({124, 0, 255, 0}, {126, 0, 0, 1}, safi); + } + } else { + sub_blocks->inherit(safi); + } + + Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); + sub_opts.extensions.add(std::move(sub_blocks)); + + Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + Botan::X509_Certificate sub_cert = + iss_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + + const Botan::Path_Validation_Restrictions restrictions(false, 80); + std::vector certs = {sub_cert, iss_cert}; + + Botan::Certificate_Store_In_Memory trusted; + trusted.add_certificate(root_cert); + + Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted); + result.require("path validation fails", !path_result.successful_validation()); + } + + result.end_timer(); + return result; +} + +Test::Result test_x509_ip_addr_blocks_path_validation_failure_ctor() { + Test::Result result("X509 IP Address Block path validation failure (ctor)"); result.start_timer(); using Botan::Cert_Extension::IPAddressBlocks; @@ -1039,8 +1464,71 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure() { return result; } -Test::Result test_x509_as_blocks_extension_encode() { - Test::Result result("X509 AS Blocks encode"); +Test::Result test_x509_as_blocks_rfc3779_example() { + Test::Result result("X509 AS Blocks rfc3779 example"); + result.start_timer(); + + using Botan::Cert_Extension::ASBlocks; + auto rng = Test::new_rng(__func__); + + // construct like in https://datatracker.ietf.org/doc/html/rfc3779#page-21 + std::unique_ptr blocks = std::make_unique(); + blocks->add_asnum(135); + blocks->add_asnum(3000, 3999); + blocks->add_asnum(5001); + blocks->inherit_rdi(); + + Botan::X509_Cert_Options opts = ca_opts(); + opts.extensions.add(std::move(blocks)); + + auto cert = make_self_signed(rng, opts); + auto bits = cert.v3_extensions().get_extension_bits(ASBlocks::static_oid()); + + result.test_eq( + "extension is encoded as specified", bits, "301AA014301202020087300802020BB802020F9F02021389A1020500"); + + auto as_idents = cert.v3_extensions().get_extension_object_as()->as_identifiers(); + auto as_ids = as_idents.asnum().value().ranges().value(); + + result.confirm("extension has correct data", as_ids[0].min() == 135); + + result.end_timer(); + return result; +} + +Test::Result test_x509_as_blocks_encode_builder() { + Test::Result result("X509 IP Address Blocks encode (builder)"); + result.start_timer(); + + using Botan::Cert_Extension::ASBlocks; + auto rng = Test::new_rng(__func__); + + std::unique_ptr blocks = std::make_unique(); + + blocks->add_rdi(10); + blocks->add_rdi(20, 30); + blocks->add_rdi(42, 300); + blocks->add_rdi(9, 301); + + blocks->inherit_asnum(); + blocks->add_asnum(20); + // this overwrites the previous two + blocks->restrict_asnum(); + + Botan::X509_Cert_Options opts = ca_opts(); + opts.extensions.add(std::move(blocks)); + + auto cert = make_self_signed(rng, opts); + auto bits = cert.v3_extensions().get_extension_bits(ASBlocks::static_oid()); + + result.test_eq("extension is encoded as specified", bits, "3011A0023000A10B300930070201090202012D"); + + result.end_timer(); + return result; +} + +Test::Result test_x509_as_blocks_extension_encode_ctor() { + Test::Result result("X509 AS Blocks encode (ctor)"); result.start_timer(); using Botan::Cert_Extension::ASBlocks; @@ -1192,8 +1680,102 @@ Test::Result test_x509_as_blocks_range_merge() { return result; } -Test::Result test_x509_as_blocks_path_validation_success() { - Test::Result result("X509 AS Block path validation success"); +Test::Result test_x509_as_blocks_path_validation_success_builder() { + Test::Result result("X509 AS Block path validation success (builder)"); + result.start_timer(); + + using Botan::Cert_Extension::ASBlocks; + auto rng = Test::new_rng(__func__); + + /* + Creates a certificate chain of length 4. + Root: both asnum and rdi + Inherit: has both values as 'inherit' + Dynamic: has either both 'inherit', both with values, or just one with a value + Subject: both asnum and rdi as a subset of Root / Dynamic + */ + + // Root Cert, both as and rdi + + std::unique_ptr root_blocks = std::make_unique(); + + root_blocks->add_asnum(0, 999); + root_blocks->add_asnum(5042); + root_blocks->add_asnum(5043, 4294967295); + + root_blocks->add_rdi(1234, 5678); + root_blocks->add_rdi(32768); + root_blocks->add_rdi(32769, 4294967295); + + // Inherit cert, both as 'inherit' + std::unique_ptr inherit_blocks = std::make_unique(); + inherit_blocks->inherit_asnum(); + inherit_blocks->inherit_rdi(); + + // Subject cert + + std::unique_ptr sub_blocks = std::make_unique(); + + sub_blocks->add_asnum(120, 180); + sub_blocks->add_asnum(220, 240); + sub_blocks->add_asnum(260, 511); + sub_blocks->add_asnum(678); + sub_blocks->add_asnum(5043, 5100); + + sub_blocks->add_rdi(1500, 2300); + sub_blocks->add_rdi(2500, 4000); + sub_blocks->add_rdi(1567); + sub_blocks->add_rdi(33100, 40000); + + Botan::X509_Cert_Options root_opts = ca_opts(); + root_opts.extensions.add(std::move(root_blocks)); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); + sub_opts.extensions.add(std::move(sub_blocks)); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + + Botan::Certificate_Store_In_Memory trusted; + trusted.add_certificate(root_cert); + + for(size_t i = 0; i < 4; i++) { + bool include_asnum = i & 1; + bool include_rdi = (i >> 1) & 1; + + std::unique_ptr dyn_blocks = std::make_unique(); + if(include_asnum) { + dyn_blocks->add_asnum(100, 600); + dyn_blocks->add_asnum(678); + dyn_blocks->add_asnum(5042, 5101); + } else { + dyn_blocks->inherit_asnum(); + } + + if(include_rdi) { + dyn_blocks->add_rdi(1500, 5000); + dyn_blocks->add_rdi(33000, 60000); + } else { + dyn_blocks->inherit_rdi(); + } + + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + + Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + Botan::X509_Certificate sub_cert = + dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + + const Botan::Path_Validation_Restrictions restrictions(false, 80); + std::vector certs = {sub_cert, dyn_cert, inherit_cert}; + + Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted); + result.require("path validation succeeds", path_result.successful_validation()); + } + + result.end_timer(); + return result; +} + +Test::Result test_x509_as_blocks_path_validation_success_ctor() { + Test::Result result("X509 AS Block path validation success (ctor)"); result.start_timer(); using Botan::Cert_Extension::ASBlocks; @@ -1322,8 +1904,48 @@ Test::Result test_x509_as_blocks_path_validation_success() { return result; } -Test::Result test_x509_as_blocks_path_validation_extension_not_present() { - Test::Result result("X509 AS Block path validation extension not present"); +Test::Result test_x509_as_blocks_path_validation_extension_not_present_builder() { + Test::Result result("X509 AS Block path validation extension not present (builder)"); + result.start_timer(); + + using Botan::Cert_Extension::ASBlocks; + auto rng = Test::new_rng(__func__); + + std::unique_ptr sub_blocks = std::make_unique(); + sub_blocks->add_asnum(120, 180); + sub_blocks->add_asnum(220, 224); + sub_blocks->add_asnum(260, 511); + sub_blocks->add_asnum(678); + sub_blocks->add_asnum(5043, 5100); + + sub_blocks->add_rdi(1500, 2300); + sub_blocks->add_rdi(2500, 4000); + sub_blocks->add_rdi(1567); + sub_blocks->add_rdi(33100, 40000); + + // create a root ca that does not have any extension + Botan::X509_Cert_Options root_opts = ca_opts(); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); + sub_opts.extensions.add(std::move(sub_blocks)); + Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + Botan::X509_Certificate sub_cert = root_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + + Botan::Certificate_Store_In_Memory trusted; + trusted.add_certificate(root_cert); + + const Botan::Path_Validation_Restrictions restrictions(false, 80); + const std::vector certs = {sub_cert}; + + Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted); + result.require("path validation fails", !path_result.successful_validation()); + + result.end_timer(); + return result; +} + +Test::Result test_x509_as_blocks_path_validation_extension_not_present_ctor() { + Test::Result result("X509 AS Block path validation extension not present (ctor)"); result.start_timer(); using Botan::Cert_Extension::ASBlocks; @@ -1380,8 +2002,211 @@ Test::Result test_x509_as_blocks_path_validation_extension_not_present() { return result; } -Test::Result test_x509_as_blocks_path_validation_failure() { - Test::Result result("X509 AS Block path validation failure"); +Test::Result test_x509_as_blocks_path_validation_failure_builder() { + Test::Result result("X509 AS Block path validation failure (builder)"); + result.start_timer(); + + using Botan::Cert_Extension::ASBlocks; + auto rng = Test::new_rng(__func__); + + /* + This executes a few permutations, messing around with edge cases when it comes to constructing ranges. + + Each test is expected to fail and creates the following certificate chain: + Root -> Issuer -> Subject + + 00: set all the asnum choices to 'inherit' for each cert + 01: 00 but for rdis + 02: make smallest min asnum of the subject smaller than the smallest min asnum of the issuer + 03: 02 but for rdis + 04: both 02 and 03 + 05: make largest max asnum of the subject larger than the largest max asnum of the issuer + 06: 05 but for rdis + 07: both 05 and 06 + 08: make the certs have multiple ranges and make one asnum range that is not the smallest and not the largest overlap with it's maximum + 09: 08 but for rdis + 10: both 08 and 09 + 11: same as 08 but the range in the subject is not contiguous, instead it is the issuers range but split into two ranges (e.g issuer range is 40-60, subject ranges are 40-49 and 51-61) + 12: 11 but for rdis + 13: both 11 and 12 + 14: 08 but using the minimum instead of the maximum + 15: 14 but for rdis + 16: both 14 and 15 + 17: same as 11 but using the minimum instead of the maximum + 18: 17 but for rdis + 19: both 18 and 19 + 20: make the issuer ranges empty but have an entry in the subject ranges + */ + for(size_t i = 0; i < 21; i++) { + // enable / disable all the different edge cases + bool inherit_all_asnums = (i == 0); + bool inherit_all_rdis = (i == 1); + bool push_asnum_min_edge_ranges = (i == 2) || (i == 4); + bool push_rdi_min_edge_ranges = (i == 3) || (i == 4); + bool push_asnum_max_edge_ranges = (i == 5) || (i == 7); + bool push_rdi_max_edge_ranges = (i == 6) || (i == 7); + bool push_asnum_max_middle_ranges = (i == 8) || (i == 10); + bool push_rdi_max_middle_ranges = (i == 9) || (i == 10); + bool push_asnum_max_split_ranges = (i == 11) || (i == 13); + bool push_rdi_max_split_ranges = (i == 12) || (i == 13); + bool push_asnum_min_middle_ranges = (i == 14) || (i == 16); + bool push_rdi_min_middle_ranges = (i == 15) || (i == 16); + bool push_asnum_min_split_ranges = (i == 17) || (i == 19); + bool push_rdi_min_split_ranges = (i == 18) || (i == 19); + bool empty_issuer_non_empty_subject = (i == 20); + + // Root cert + std::unique_ptr root_blocks = std::make_unique(); + + if(!inherit_all_asnums) { + if(push_asnum_min_edge_ranges || push_asnum_max_edge_ranges) { + // 100-200 for 02,03,04 + root_blocks->add_asnum(100, 200); + } else if(push_asnum_max_middle_ranges || push_asnum_min_middle_ranges) { + // 10-20,30-40,50-60 for 08,09,10 + root_blocks->add_asnum(10, 20); + root_blocks->add_asnum(30, 40); + root_blocks->add_asnum(50, 60); + } else if(push_asnum_max_split_ranges || push_asnum_min_split_ranges) { + // 10-20,30-50,60-70 for 11,12,13 + root_blocks->add_asnum(10, 20); + root_blocks->add_asnum(30, 50); + root_blocks->add_asnum(60, 70); + } + } else { + root_blocks->inherit_asnum(); + } + + // same values but for rdis + if(!inherit_all_rdis) { + if(push_rdi_min_edge_ranges || push_rdi_max_edge_ranges) { + root_blocks->add_rdi(100, 200); + } else if(push_rdi_max_middle_ranges || push_rdi_min_middle_ranges) { + root_blocks->add_rdi(10, 20); + root_blocks->add_rdi(30, 40); + root_blocks->add_rdi(50, 60); + } else if(push_rdi_max_split_ranges || push_rdi_min_split_ranges) { + root_blocks->add_rdi(10, 20); + root_blocks->add_rdi(30, 50); + root_blocks->add_rdi(60, 70); + } + } else { + root_blocks->inherit_rdi(); + } + + if(empty_issuer_non_empty_subject) { + root_blocks->restrict_asnum(); + root_blocks->restrict_rdi(); + } + + // Issuer cert + // the issuer cert has the same ranges as the root cert + // it is used to check that the 'inherit' check is bubbled up until the root cert is hit + auto issu_blocks = root_blocks->copy(); + + // Subject cert + std::unique_ptr sub_blocks = std::make_unique(); + + std::vector sub_as_ranges; + std::vector sub_rdi_ranges; + + if(!inherit_all_asnums) { + // assign the subject asnum ranges + if(push_asnum_min_edge_ranges) { + // 99-200 for 02 (so overlapping to the left) + sub_blocks->add_asnum(99, 200); + } else if(push_asnum_max_edge_ranges) { + // 100-201 for 03 (so overlapping to the right) + sub_blocks->add_asnum(100, 201); + } else if(push_asnum_max_middle_ranges) { + // same as root, but change the range in the middle to overlap to the right for 08 + sub_blocks->add_asnum(10, 20); + sub_blocks->add_asnum(30, 41); + sub_blocks->add_asnum(50, 60); + } else if(push_asnum_max_split_ranges) { + // change the range in the middle to be cut at 45 for case 11 + // the left range is 30-44 + // the right range is 46-51 (overlapping the issuer range to the right) + sub_blocks->add_asnum(10, 20); + sub_blocks->add_asnum(30, 44); + sub_blocks->add_asnum(46, 51); + sub_blocks->add_asnum(60, 70); + } else if(push_asnum_min_middle_ranges) { + // just change the test in the middle to overlap to the left for case 14 + sub_blocks->add_asnum(10, 20); + sub_blocks->add_asnum(29, 40); + sub_blocks->add_asnum(50, 60); + } else if(push_asnum_min_split_ranges) { + // again split the range in the middle at 45 for case 17 + // creating two ranges 29-44 and 46-50 (so overlapping to the left) + sub_blocks->add_asnum(10, 20); + sub_blocks->add_asnum(29, 44); + sub_blocks->add_asnum(46, 50); + sub_blocks->add_asnum(60, 70); + } else if(empty_issuer_non_empty_subject) { + sub_blocks->add_asnum(50); + } + } else { + sub_blocks->inherit_asnum(); + } + + if(!inherit_all_rdis) { + // same values but for rdis + if(push_rdi_min_edge_ranges) { + sub_blocks->add_rdi(99, 200); + } else if(push_rdi_max_edge_ranges) { + sub_blocks->add_rdi(100, 201); + } else if(push_rdi_max_middle_ranges) { + sub_blocks->add_rdi(10, 20); + sub_blocks->add_rdi(30, 41); + sub_blocks->add_rdi(50, 60); + } else if(push_rdi_max_split_ranges) { + sub_blocks->add_rdi(10, 20); + sub_blocks->add_rdi(30, 44); + sub_blocks->add_rdi(46, 51); + sub_blocks->add_rdi(60, 70); + } else if(push_rdi_min_middle_ranges) { + sub_blocks->add_rdi(10, 20); + sub_blocks->add_rdi(29, 40); + sub_blocks->add_rdi(50, 60); + } else if(push_rdi_min_split_ranges) { + sub_blocks->add_rdi(10, 20); + sub_blocks->add_rdi(29, 44); + sub_blocks->add_rdi(46, 50); + sub_blocks->add_rdi(60, 70); + } + } else { + sub_blocks->inherit_rdi(); + } + + Botan::X509_Cert_Options root_opts = ca_opts(); + root_opts.extensions.add(std::move(root_blocks)); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, rng); + + Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); + sub_opts.extensions.add(std::move(sub_blocks)); + Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + Botan::X509_Certificate sub_cert = + issu_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + + Botan::Certificate_Store_In_Memory trusted; + trusted.add_certificate(root_cert); + + const Botan::Path_Validation_Restrictions restrictions(false, 80); + const std::vector certs = {sub_cert, issu_cert}; + + Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted); + // in all cases, the validation should fail, since we are creating invalid scenarios + result.confirm("path validation fails at iteration " + std::to_string(i), !path_result.successful_validation()); + } + + result.end_timer(); + return result; +} + +Test::Result test_x509_as_blocks_path_validation_failure_ctor() { + Test::Result result("X509 AS Block path validation failure (ctor)"); result.start_timer(); using Botan::Cert_Extension::ASBlocks; @@ -1584,18 +2409,26 @@ class X509_RPKI_Tests final : public Test { results.push_back(test_x509_ip_addr_blocks_extension_decode()); results.push_back(test_x509_as_blocks_extension_decode()); #endif - - results.push_back(test_x509_ip_addr_blocks_extension_encode()); - results.push_back(test_x509_ip_addr_blocks_extension_encode_edge_cases()); + results.push_back(test_x509_ip_addr_blocks_rfc3779_example()); + results.push_back(test_x509_ip_addr_blocks_encode_builder()); + results.push_back(test_x509_ip_addr_blocks_extension_encode_ctor()); + results.push_back(test_x509_ip_addr_blocks_extension_encode_edge_cases_ctor()); results.push_back(test_x509_ip_addr_blocks_range_merge()); results.push_back(test_x509_ip_addr_blocks_family_merge()); - results.push_back(test_x509_ip_addr_blocks_path_validation_success()); - results.push_back(test_x509_ip_addr_blocks_path_validation_failure()); - results.push_back(test_x509_as_blocks_extension_encode()); + results.push_back(test_x509_ip_addr_blocks_path_validation_success_builder()); + results.push_back(test_x509_ip_addr_blocks_path_validation_success_ctor()); + results.push_back(test_x509_ip_addr_blocks_path_validation_failure_builder()); + results.push_back(test_x509_ip_addr_blocks_path_validation_failure_ctor()); + results.push_back(test_x509_as_blocks_rfc3779_example()); + results.push_back(test_x509_as_blocks_encode_builder()); + results.push_back(test_x509_as_blocks_extension_encode_ctor()); results.push_back(test_x509_as_blocks_range_merge()); - results.push_back(test_x509_as_blocks_path_validation_success()); - results.push_back(test_x509_as_blocks_path_validation_extension_not_present()); - results.push_back(test_x509_as_blocks_path_validation_failure()); + results.push_back(test_x509_as_blocks_path_validation_success_builder()); + results.push_back(test_x509_as_blocks_path_validation_success_ctor()); + results.push_back(test_x509_as_blocks_path_validation_extension_not_present_builder()); + results.push_back(test_x509_as_blocks_path_validation_extension_not_present_ctor()); + results.push_back(test_x509_as_blocks_path_validation_failure_builder()); + results.push_back(test_x509_as_blocks_path_validation_failure_ctor()); return results; } }; diff --git a/src/tests/tests.h b/src/tests/tests.h index a73b1b927a1..44146cf578f 100644 --- a/src/tests/tests.h +++ b/src/tests/tests.h @@ -494,6 +494,13 @@ class Test { return test_eq(nullptr, what, produced.data(), produced.size(), expected.data(), expected.size()); } + template + bool test_eq(const std::string& what, + const std::array& produced, + const std::array& expected) { + return test_eq(nullptr, what, produced.data(), produced.size(), expected.data(), expected.size()); + } + bool test_ne(const std::string& what, std::span produced, std::span expected) {