/* * (C) 2025 Jack Lloyd * (C) 2025 Anton Einax, Dominik Schricker * * Botan is released under the Simplified BSD License (see license.txt) */ #include "tests.h" #if defined(BOTAN_HAS_X509_CERTIFICATES) #include #include #include #include #include #include #include #include #include #endif namespace Botan_Tests { namespace { #if defined(BOTAN_HAS_X509_CERTIFICATES) && \ (defined(BOTAN_HAS_ECDSA) || defined(BOTAN_HAS_RSA) || defined(BOTAN_HAS_ED25519)) struct CA_Creation_Result { Botan::X509_Certificate ca_cert; Botan::X509_CA ca; std::unique_ptr sub_key; std::string sig_algo; std::string hash_fn; }; Botan::X509_Time from_date(const int y, const int m, const int d) { const size_t this_year = Botan::calendar_point(std::chrono::system_clock::now()).year(); const Botan::calendar_point t(static_cast(this_year + y), m, d, 0, 0, 0); return Botan::X509_Time(t.to_std_timepoint()); } Botan::X509_Cert_Options ca_opts(const std::string& sig_padding = "") { Botan::X509_Cert_Options opts("Test CA/US/Botan Project/Testing"); opts.uri = "https://botan.randombit.net"; opts.dns = "botan.randombit.net"; opts.email = "testing@randombit.net"; opts.set_padding_scheme(sig_padding); opts.CA_key(1); return opts; } Botan::X509_Cert_Options req_opts(const std::string& algo, const std::string& sig_padding = "") { Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing"); opts.uri = "https://botan.randombit.net"; opts.dns = "botan.randombit.net"; opts.email = "testing@randombit.net"; opts.set_padding_scheme(sig_padding); opts.not_before("160101200000Z"); opts.not_after("300101200000Z"); opts.challenge = "zoom"; if(algo == "RSA") { opts.constraints = Botan::Key_Constraints::KeyEncipherment; } else if(algo == "DSA" || algo == "ECDSA" || algo == "ECGDSA" || algo == "ECKCDSA") { opts.constraints = Botan::Key_Constraints::DigitalSignature; } return opts; } std::tuple> get_sig_algo_padding_and_generate_key(Botan::RandomNumberGenerator& rng) { const auto generate_key = [&](const std::string& alg_name, const std::string& params = "") { auto key = Botan::create_private_key(alg_name, rng, params); if(!key) { throw Test_Error("Could not generate key for algorithm " + alg_name); } return key; }; #if defined(BOTAN_HAS_ECDSA) && defined(BOTAN_HAS_SHA2_32) // Try to find a group that is supported in this build const auto group_name = Test::supported_ec_group_name(); if(group_name) { return {"ECDSA", "", "SHA-256", generate_key("ECDSA", *group_name)}; } #endif #if defined(BOTAN_HAS_ED25519) return {"Ed25519", "", "SHA-512", generate_key("Ed25519")}; #elif defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1) return {"RSA", "PKCS1v15(SHA-256)", "SHA-256", generate_key("RSA", "1536")}; #else throw Test_Error("No suitable signature algorithm available in this build"); #endif } 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, key] = get_sig_algo_padding_and_generate_key(*rng); return Botan::X509::create_self_signed_cert(opts, *key, hash_fn, *rng); } 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, ca_key] = get_sig_algo_padding_and_generate_key(*rng); const auto ca_cert = Botan::X509::create_self_signed_cert(opts, *ca_key, hash_fn, *rng); Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng); auto sub_key = ca_key->generate_another(*rng); return CA_Creation_Result{ca_cert, std::move(ca), std::move(sub_key), sig_algo, hash_fn}; } std::pair make_and_sign_ca( std::unique_ptr ext, Botan::X509_CA& parent_ca, std::unique_ptr& rng) { auto [sig_algo, padding_method, hash_fn, key] = get_sig_algo_padding_and_generate_key(*rng); Botan::X509_Cert_Options opts = ca_opts(); opts.extensions.add(std::move(ext)); const Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, hash_fn, *rng); Botan::X509_Certificate cert = parent_ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); Botan::X509_CA ca(cert, *key, hash_fn, padding_method, *rng); return std::make_pair(std::move(cert), std::move(ca)); } constexpr auto IPv4 = Botan::Cert_Extension::IPAddressBlocks::Version::IPv4; constexpr auto IPv6 = Botan::Cert_Extension::IPAddressBlocks::Version::IPv6; #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) Test::Result test_x509_ip_addr_blocks_extension_decode() { Test::Result result("X509 IP Address Block decode"); result.start_timer(); using Botan::Cert_Extension::IPAddressBlocks; { const std::string filename("IPAddrBlocksAll.pem"); const Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); const auto* ip_addr_blocks = cert.v3_extensions().get_extension_object_as(); const auto& addr_blocks = ip_addr_blocks->addr_blocks(); result.test_is_true("cert has IPAddrBlocks extension", ip_addr_blocks != nullptr); result.test_sz_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()); const 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_bin_eq("ipv4 block 0 min", v4_blocks[0].min().value(), {192, 168, 0, 0}); result.test_bin_eq("ipv4 block 0 max", v4_blocks[0].max().value(), {192, 168, 127, 255}); result.test_bin_eq("ipv4 block 1 min", v4_blocks[1].min().value(), {193, 168, 0, 0}); result.test_bin_eq("ipv4 block 1 max", v4_blocks[1].max().value(), {193, 169, 255, 255}); result.test_bin_eq("ipv4 block 2 min", v4_blocks[2].min().value(), {194, 168, 0, 0}); result.test_bin_eq("ipv4 block 2 max", v4_blocks[2].max().value(), {195, 175, 1, 2}); result.test_bin_eq("ipv4 block 3 min", v4_blocks[3].min().value(), {196, 168, 0, 1}); result.test_bin_eq("ipv4 block 3 max", v4_blocks[3].max().value(), {196, 168, 0, 1}); const 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_bin_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_bin_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_bin_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_bin_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_bin_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_bin_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_bin_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_bin_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"); const Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); const 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_sz_eq("cert has two IpAddrBlocks", addr_blocks.size(), 5); result.test_opt_u8_eq("block 0 has no safi", addr_blocks[0].safi(), std::nullopt); result.test_is_true( "block 0 is inherited", !std::get>(addr_blocks[0].addr_choice()).ranges().has_value()); result.test_opt_u8_eq("block 1 has correct safi", addr_blocks[1].safi(), 1); const auto& block_1 = std::get>(addr_blocks[1].addr_choice()).ranges().value(); result.test_sz_eq("block 1 has correct size", block_1.size(), 1); result.test_bin_eq("block 1 min is correct", block_1[0].min().value(), {192, 168, 0, 0}); result.test_bin_eq("block 1 max is correct", block_1[0].max().value(), {200, 0, 0, 0}); result.test_opt_u8_eq("block 2 has correct safi", addr_blocks[2].safi(), 2); result.test_is_true( "block 2 is inherited", !std::get>(addr_blocks[2].addr_choice()).ranges().has_value()); result.test_opt_u8_eq("block 3 has no safi", addr_blocks[3].safi(), std::nullopt); const auto& block_3 = std::get>(addr_blocks[3].addr_choice()).ranges().value(); result.test_sz_eq("block 3 has correct size", block_3.size(), 1); result.test_bin_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_bin_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_opt_u8_eq("block 24 has correct safi", addr_blocks[4].safi(), 1); result.test_is_true( "block 4 is inherited", !std::get>(addr_blocks[4].addr_choice()).ranges().has_value()); } { const std::string filename("InvalidIPAddrBlocks.pem"); const Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); // cert contains the 10.0.32.0/20 prefix, but with a 9 for the unused bits result.test_is_true("extension is present", cert.v3_extensions().extension_set(IPAddressBlocks::static_oid())); const auto* ext = cert.v3_extensions().get_extension_object_as(); result.test_is_true("extension is not decoded", ext == nullptr); } result.end_timer(); return result; } Test::Result test_x509_as_blocks_extension_decode() { Test::Result result("X509 AS Block decode"); result.start_timer(); using Botan::Cert_Extension::ASBlocks; { const std::string filename("ASNumberCert.pem"); const Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); const auto* as_blocks = cert.v3_extensions().get_extension_object_as(); const auto& identifier = as_blocks->as_identifiers(); result.test_is_true("cert has ASBlock extension", as_blocks != nullptr); const auto& asnum = identifier.asnum().value().ranges().value(); const auto& rdi = identifier.rdi().value().ranges().value(); // cert contains asnum 0-999, 5042, 0-4294967295 result.test_u32_eq("asnum entry 0 min", asnum[0].min(), 0); result.test_u32_eq("asnum entry 0 max", asnum[0].max(), 4294967295); // and rdi 1234-5678, 32768, 0-4294967295 result.test_u32_eq("rdi entry 0 min", rdi[0].min(), 0); result.test_u32_eq("rdi entry 0 max", rdi[0].max(), 4294967295); } { const std::string filename("ASNumberOnly.pem"); const Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); const auto* as_blocks = cert.v3_extensions().get_extension_object_as(); const auto& identifier = as_blocks->as_identifiers(); result.test_is_true("cert has ASBlock extension", as_blocks != nullptr); const auto& asnum = identifier.asnum().value().ranges().value(); result.test_is_false("cert has no RDI entries", identifier.rdi().has_value()); // contains 0-999, 0-4294967295 result.test_u32_eq("asnum entry 0 min", asnum[0].min(), 0); result.test_u32_eq("asnum entry 0 max", asnum[0].max(), 4294967295); } { const std::string filename("ASRdiOnly.pem"); const Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); const auto* as_blocks = cert.v3_extensions().get_extension_object_as(); const auto& identifier = as_blocks->as_identifiers(); result.test_is_true("cert has ASBlock extension", as_blocks != nullptr); result.test_is_false("cert has no ASNUM entries", identifier.asnum().has_value()); const auto& rdi = identifier.rdi().value().ranges().value(); // contains 1234-5678, 0-4294967295 result.test_u32_eq("rdi entry 0 min", rdi[0].min(), 0); result.test_u32_eq("rdi entry 0 max", rdi[0].max(), 4294967295); } { const std::string filename("ASNumberInherit.pem"); const Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename)); const auto* as_blocks = cert.v3_extensions().get_extension_object_as(); const auto& identifier = as_blocks->as_identifiers(); result.test_is_true("cert has ASBlock extension", as_blocks != nullptr); result.test_is_false("asnum has no entries", identifier.asnum().value().ranges().has_value()); const auto& rdi = identifier.rdi().value().ranges().value(); // contains 1234-5678, 0-4294967295 result.test_u32_eq("rdi entry 0 min", rdi[0].min(), 0); result.test_u32_eq("rdi entry 0 max", rdi[0].max(), 4294967295); } result.end_timer(); return result; } #endif 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_bin_eq( "extension is encoded as specified", bits_1, "3035302B040300010130240304040A00200304000A00400303000A01300C0304040A02300304000A02400303000A033006040200020500"); const auto* ext_1 = cert_1.v3_extensions().get_extension_object_as(); auto ext_1_addr_fam_1 = ext_1->addr_blocks()[0]; result.test_opt_u8_eq("extension 1 ipv4 safi", ext_1_addr_fam_1.safi(), 1); auto ext_1_ranges = std::get>(ext_1_addr_fam_1.addr_choice()).ranges().value(); result.test_bin_eq("extension 1 range 1 min", ext_1_ranges[0].min().value(), {10, 0, 32, 0}); result.test_bin_eq("extension 1 range 1 max", ext_1_ranges[0].max().value(), {10, 0, 47, 255}); result.test_bin_eq("extension 1 range 2 min", ext_1_ranges[1].min().value(), {10, 0, 64, 0}); result.test_bin_eq("extension 1 range 2 max", ext_1_ranges[1].max().value(), {10, 0, 64, 255}); result.test_bin_eq("extension 1 range 3 min", ext_1_ranges[2].min().value(), {10, 1, 0, 0}); result.test_bin_eq("extension 1 range 3 max", ext_1_ranges[2].max().value(), {10, 1, 255, 255}); result.test_bin_eq("extension 1 range 4 min", ext_1_ranges[3].min().value(), {10, 2, 48, 0}); result.test_bin_eq("extension 1 range 4 max", ext_1_ranges[3].max().value(), {10, 2, 64, 255}); result.test_bin_eq("extension 1 range 5 min", ext_1_ranges[4].min().value(), {10, 3, 0, 0}); result.test_bin_eq("extension 1 range 5 max", ext_1_ranges[4].max().value(), {10, 3, 255, 255}); result.test_opt_u8_eq("extension 1 ipv6 safi", ext_1->addr_blocks()[1].safi(), std::nullopt); result.test_is_true( "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_bin_eq("extension is encoded as specified", bits_2, "302C3010040300010130090302000A030304AC10300704030001020500300F040200023009030700200100000002"); const auto* ext_2 = cert_2.v3_extensions().get_extension_object_as(); auto ext_2_addr_fam_1 = ext_2->addr_blocks()[0]; result.test_opt_u8_eq("extension 2 ipv4 1 safi", ext_2_addr_fam_1.safi(), 1); auto ext_2_ranges_1 = std::get>(ext_2_addr_fam_1.addr_choice()).ranges().value(); result.test_bin_eq("extension 2 fam 1 range 1 min", ext_2_ranges_1[0].min().value(), {10, 0, 0, 0}); result.test_bin_eq("extension 2 fam 1 range 1 max", ext_2_ranges_1[0].max().value(), {10, 255, 255, 255}); result.test_bin_eq("extension 2 fam 1 range 2 min", ext_2_ranges_1[1].min().value(), {172, 16, 0, 0}); result.test_bin_eq("extension 2 fam 1 range 2 max", ext_2_ranges_1[1].max().value(), {172, 31, 255, 255}); result.test_opt_u8_eq("extension 2 ipv4 2 safi", ext_2->addr_blocks()[1].safi(), 2); result.test_is_true( "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_opt_u8_eq("extension 2 ipv4 1 safi", ext_2_addr_fam_3.safi(), std::nullopt); auto ext_2_ranges_3 = std::get>(ext_2_addr_fam_3.addr_choice()).ranges().value(); result.test_bin_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_bin_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_bin_eq( "extension is encoded as specified", bits, "304630290402000130230305007B7B0201300D0305007F000001030403BD0500030407BE0500030500FFFFFFFF300A04030001013003030100300D04030001023006030406C0A840"); result.end_timer(); return result; } namespace { template bool bit_set(size_t v) { if(((v >> I) & 1) == 1) { return true; } else { return false; } } } // namespace 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; auto rng = Test::new_rng(__func__); auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); for(size_t i = 0; i < 64; i++) { const bool push_ipv4_ranges = bit_set<0>(i); const bool push_ipv6_ranges = bit_set<1>(i); const bool inherit_ipv4 = bit_set<2>(i); const bool inherit_ipv6 = bit_set<3>(i); const bool push_ipv4_family = bit_set<4>(i); const bool push_ipv6_family = bit_set<5>(i); Botan::X509_Cert_Options opts = req_opts(sig_algo); std::vector a = {123, 123, 2, 1}; auto ipv4_1 = IPAddressBlocks::IPAddress(a); a = {255, 255, 255, 255}; auto ipv4_2 = IPAddressBlocks::IPAddress(a); // encoded as min, max a = {127, 0, 0, 1}; auto ipv4_range_1_min = IPAddressBlocks::IPAddress(a); a = {189, 5, 7, 255}; auto ipv4_range_1_max = IPAddressBlocks::IPAddress(a); // encoded as prefix a = {190, 5, 0, 0}; auto ipv4_range_2_min = IPAddressBlocks::IPAddress(a); a = {190, 5, 127, 255}; auto ipv4_range_2_max = IPAddressBlocks::IPAddress(a); a = {0xAB, 0xCD, 0xDE, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; auto ipv6_1 = IPAddressBlocks::IPAddress(a); a = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; auto ipv6_2 = IPAddressBlocks::IPAddress(a); // encoded as min, max a = {0xAF, 0x23, 0x34, 0x45, 0x67, 0x2A, 0x7A, 0xEF, 0x8C, 0x00, 0x00, 0x00, 0x66, 0x00, 0x52, 0x00}; auto ipv6_range_1_min = IPAddressBlocks::IPAddress(a); a = {0xAF, 0xCD, 0xDE, 0xF0, 0x00, 0x0F, 0xEE, 0x00, 0xBB, 0x4A, 0x9B, 0x00, 0x00, 0x4C, 0x00, 0xCC}; auto ipv6_range_1_max = IPAddressBlocks::IPAddress(a); // encoded as prefix a = {0xBF, 0xCD, 0xDE, 0xF0, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; auto ipv6_range_2_min = IPAddressBlocks::IPAddress(a); a = {0xBF, 0xCD, 0xDE, 0xF0, 0x00, 0x00, 0x00, 0x07, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; auto ipv6_range_2_max = IPAddressBlocks::IPAddress(a); auto ipv4_range_1 = IPAddressBlocks::IPAddressOrRange(ipv4_1); auto ipv4_range_2 = IPAddressBlocks::IPAddressOrRange(ipv4_range_1_min, ipv4_range_1_max); auto ipv4_range_3 = IPAddressBlocks::IPAddressOrRange(ipv4_range_2_min, ipv4_range_2_max); auto ipv4_range_4 = IPAddressBlocks::IPAddressOrRange(ipv4_2); auto ipv6_range_1 = IPAddressBlocks::IPAddressOrRange(ipv6_1); auto ipv6_range_2 = IPAddressBlocks::IPAddressOrRange(ipv6_range_1_min, ipv6_range_1_max); auto ipv6_range_3 = IPAddressBlocks::IPAddressOrRange(ipv6_range_2_min, ipv6_range_2_max); auto ipv6_range_4 = IPAddressBlocks::IPAddressOrRange(ipv6_2); std::vector> ipv4_ranges; if(push_ipv4_ranges) { ipv4_ranges.push_back(ipv4_range_1); ipv4_ranges.push_back(ipv4_range_2); ipv4_ranges.push_back(ipv4_range_3); ipv4_ranges.push_back(ipv4_range_4); } std::vector> ipv6_ranges; if(push_ipv6_ranges) { ipv6_ranges.push_back(ipv6_range_1); ipv6_ranges.push_back(ipv6_range_2); ipv6_ranges.push_back(ipv6_range_3); ipv6_ranges.push_back(ipv6_range_4); } auto ipv4_addr_choice = IPAddressBlocks::IPAddressChoice(); if(!inherit_ipv4) { ipv4_addr_choice = IPAddressBlocks::IPAddressChoice(ipv4_ranges); } auto ipv6_addr_choice = IPAddressBlocks::IPAddressChoice(); if(!inherit_ipv6) { ipv6_addr_choice = IPAddressBlocks::IPAddressChoice(ipv6_ranges); } auto ipv4_addr_family = IPAddressBlocks::IPAddressFamily(ipv4_addr_choice); auto ipv6_addr_family = IPAddressBlocks::IPAddressFamily(ipv6_addr_choice); std::vector addr_blocks; if(push_ipv4_family) { addr_blocks.push_back(ipv4_addr_family); } if(push_ipv6_family) { addr_blocks.push_back(ipv6_addr_family); } std::unique_ptr blocks = std::make_unique(addr_blocks); opts.extensions.add(std::move(blocks)); const Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); const Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); result.test_is_true("cert has IPAddrBlocks extension", ip_blocks != nullptr); const auto& dec_addr_blocks = ip_blocks->addr_blocks(); if(!push_ipv4_family && !push_ipv6_family) { result.test_is_true("no address family entries", dec_addr_blocks.empty()); continue; } if(push_ipv4_family) { auto family = dec_addr_blocks[0]; result.test_u16_eq("ipv4 family afi", ipv4_addr_family.afi(), family.afi()); result.test_opt_u8_eq("ipv4 family safi", ipv4_addr_family.safi(), family.safi()); auto choice = std::get>(family.addr_choice()); if(!inherit_ipv4) { auto ranges = choice.ranges().value(); if(push_ipv4_ranges) { result.test_bin_eq("ipv4 entry 0 min", ranges[0].min().value(), ipv4_range_1.min().value()); result.test_bin_eq("ipv4 entry 0 max", ranges[0].max().value(), ipv4_range_1.max().value()); result.test_bin_eq("ipv4 entry 1 min", ranges[1].min().value(), ipv4_range_2.min().value()); result.test_bin_eq("ipv4 entry 1 max", ranges[1].max().value(), ipv4_range_2.max().value()); result.test_bin_eq("ipv4 entry 2 min", ranges[2].min().value(), ipv4_range_3.min().value()); result.test_bin_eq("ipv4 entry 2 max", ranges[2].max().value(), ipv4_range_3.max().value()); result.test_bin_eq("ipv4 entry 3 min", ranges[3].min().value(), ipv4_range_4.min().value()); result.test_bin_eq("ipv4 entry 3 max", ranges[3].max().value(), ipv4_range_4.max().value()); } else { result.test_is_true("ipv4 range has no entries", ranges.empty()); } } else { result.test_is_false("ipv4 family inherit", choice.ranges().has_value()); } } if(push_ipv6_family) { auto family = dec_addr_blocks[dec_addr_blocks.size() - 1]; result.test_u16_eq("ipv6 family afi", ipv6_addr_family.afi(), family.afi()); result.test_opt_u8_eq("ipv6 family safi", ipv6_addr_family.safi(), family.safi()); auto choice = std::get>(family.addr_choice()); if(!inherit_ipv6) { auto ranges = choice.ranges().value(); if(push_ipv6_ranges) { result.test_bin_eq("ipv6 entry 0 min", ranges[0].min().value(), ipv6_range_1.min().value()); result.test_bin_eq("ipv6 entry 0 max", ranges[0].max().value(), ipv6_range_1.max().value()); result.test_bin_eq("ipv6 entry 1 min", ranges[1].min().value(), ipv6_range_2.min().value()); result.test_bin_eq("ipv6 entry 1 max", ranges[1].max().value(), ipv6_range_2.max().value()); result.test_bin_eq("ipv6 entry 2 min", ranges[2].min().value(), ipv6_range_3.min().value()); result.test_bin_eq("ipv6 entry 2 max", ranges[2].max().value(), ipv6_range_3.max().value()); result.test_bin_eq("ipv6 entry 3 min", ranges[3].min().value(), ipv6_range_4.min().value()); result.test_bin_eq("ipv6 entry 3 max", ranges[3].max().value(), ipv6_range_4.max().value()); } else { result.test_is_true("ipv6 range has no entries", ranges.empty()); } } else { result.test_is_false("ipv6 family inherit", choice.ranges().has_value()); } } } } result.end_timer(); return result; } 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; auto rng = Test::new_rng(__func__); // trailing 0s, trailing 1s, and some arbitrary values std::vector edge_values = {0, 2, 4, 8, 16, 32, 64, 128, 1, 3, 7, 15, 31, 63, 127, 255, 12, 46, 123, 160, 234}; auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); for(size_t i = 0; i < edge_values.size(); i++) { for(size_t j = 0; j < 4; j++) { const bool modify_min = bit_set<0>(j); const bool modify_max = bit_set<1>(j); for(size_t k = 0; k < 18; k++) { if(!modify_min && !modify_max && (k > 0 || i > 0)) { // we don't modify anything, this is the extreme edge case of 0.0 ... - 255.255. ... // so we only need to do this once break; } Botan::X509_Cert_Options opts = req_opts(sig_algo); std::vector min_bytes(16, 0x00); std::vector max_bytes(16, 0xFF); if(modify_min) { min_bytes[15 - (k < 2 ? 0 : k - 2)] = edge_values[i]; } if(modify_max) { max_bytes[15 - (k > 15 ? 15 : k)] = edge_values[i]; } auto address_min = IPAddressBlocks::IPAddress(min_bytes); auto address_max = IPAddressBlocks::IPAddress(max_bytes); auto ipv6_range = IPAddressBlocks::IPAddressOrRange(address_min, address_max); std::vector> ipv6_ranges; ipv6_ranges.push_back(ipv6_range); auto ipv6_addr_choice = IPAddressBlocks::IPAddressChoice(ipv6_ranges); auto ipv6_addr_family = IPAddressBlocks::IPAddressFamily(ipv6_addr_choice); std::vector addr_blocks; addr_blocks.push_back(ipv6_addr_family); std::unique_ptr blocks = std::make_unique(addr_blocks); opts.extensions.add(std::move(blocks)); const Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); const Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); result.test_not_null("cert has IPAddrBlocks extension", ip_blocks); const auto& dec_addr_blocks = ip_blocks->addr_blocks(); auto family = dec_addr_blocks[0]; result.test_u16_eq("ipv6 family afi", ipv6_addr_family.afi(), family.afi()); result.test_opt_u8_eq("ipv6 family safi", ipv6_addr_family.safi(), family.safi()); auto choice = std::get>(family.addr_choice()); auto ranges = choice.ranges().value(); result.test_bin_eq("ipv6 edge case min", ranges[0].min().value(), ipv6_range.min().value()); result.test_bin_eq("ipv6 edge case max", ranges[0].max().value(), ipv6_range.max().value()); } } } } result.end_timer(); return result; } Test::Result test_x509_ip_addr_blocks_range_merge() { Test::Result result("X509 IP Address Block range merge"); result.start_timer(); using Botan::Cert_Extension::IPAddressBlocks; auto rng = Test::new_rng(__func__); auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); Botan::X509_Cert_Options opts = req_opts(sig_algo); const std::vector>> addresses = { {{11, 0, 0, 0}, {{11, 0, 0, 0}}}, {{123, 123, 123, 123}, {123, 123, 123, 123}}, {{10, 4, 5, 9}, {{10, 255, 0, 0}}}, {{12, 0, 0, 0}, {191, 0, 0, 1}}, {{190, 0, 0, 0}, {193, 0, 255, 255}}, {{10, 10, 10, 10}, {10, 20, 20, 20}}, {{5, 0, 0, 0}, {10, 255, 255, 255}}, {{192, 0, 0, 0}, {192, 255, 255, 255}}, {{11, 0, 0, 1}, {11, 255, 255, 255}}, }; std::vector> ipv6_ranges; for(auto pair : addresses) { auto address_min = IPAddressBlocks::IPAddress(pair[0]); auto address_max = IPAddressBlocks::IPAddress(pair[1]); auto range = IPAddressBlocks::IPAddressOrRange(address_min, address_max); ipv6_ranges.push_back(range); } auto ipv6_addr_choice = IPAddressBlocks::IPAddressChoice(ipv6_ranges); auto ipv6_addr_family = IPAddressBlocks::IPAddressFamily(ipv6_addr_choice); std::vector addr_blocks; addr_blocks.push_back(ipv6_addr_family); std::unique_ptr blocks = std::make_unique(addr_blocks); opts.extensions.add(std::move(blocks)); const Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); const Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); result.test_not_null("cert has IPAddrBlocks extension", ip_blocks); const auto& dec_addr_blocks = ip_blocks->addr_blocks(); auto family = dec_addr_blocks[0]; auto choice = std::get>(family.addr_choice()); auto ranges = choice.ranges().value(); const std::array expected_min = {5, 0, 0, 0}; const std::array expected_max = {193, 0, 255, 255}; result.test_bin_eq("range expected min", ranges[0].min().value(), expected_min); result.test_bin_eq("range expected max", ranges[0].max().value(), expected_max); result.test_sz_eq("range length", ranges.size(), 1); } result.end_timer(); return result; } Test::Result test_x509_ip_addr_blocks_family_merge() { Test::Result result("X509 IP Address Block family merge"); result.start_timer(); using Botan::Cert_Extension::IPAddressBlocks; auto rng = Test::new_rng(__func__); auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); Botan::X509_Cert_Options opts = req_opts(sig_algo); std::vector addr_blocks; IPAddressBlocks::IPAddressChoice v4_empty_choice; IPAddressBlocks::IPAddressChoice v6_empty_choice; const uint8_t v4_bytes_1[4] = {123, 123, 123, 123}; const IPAddressBlocks::IPAddress v4_addr_1(v4_bytes_1); // create 2 prefixes from the v4 addresses -> they should be merged std::vector> v4_choice_vec{ IPAddressBlocks::IPAddressOrRange(IPAddressBlocks::IPAddress({v4_addr_1}))}; IPAddressBlocks::IPAddressChoice v4_choice_dupl(v4_choice_vec); result.test_sz_eq( "IPAddressChoice v4 merges ranges already in constructor", v4_choice_dupl.ranges().value().size(), 1); const IPAddressBlocks::IPAddressFamily v4_fam_dupl(v4_choice_dupl, 0); const uint8_t v6_bytes_1[16] = {123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123}; const IPAddressBlocks::IPAddress v6_addr_1(v6_bytes_1); std::vector> v6_choice_vec{ IPAddressBlocks::IPAddressOrRange(IPAddressBlocks::IPAddress({v6_addr_1}))}; IPAddressBlocks::IPAddressChoice v6_choice_dupl(v6_choice_vec); result.test_sz_eq("IPAddressChoice v6 merges already in constructor", v6_choice_dupl.ranges().value().size(), 1); const IPAddressBlocks::IPAddressFamily v6_fam_dupl(v6_choice_dupl, 0); const IPAddressBlocks::IPAddressFamily v4_empty_fam(v4_empty_choice); const IPAddressBlocks::IPAddressFamily v6_empty_fam(v6_empty_choice); const IPAddressBlocks::IPAddressFamily v4_empty_fam_safi(v4_empty_choice, 2); const IPAddressBlocks::IPAddressFamily v6_empty_fam_safi(v6_empty_choice, 2); /* considering the push order, the resulting order should be [0] v4 no safi [2] v4 safi [1] v6 no safi [3] v6 safi */ for(size_t i = 0; i < 3; i++) { addr_blocks.push_back(v4_empty_fam_safi); addr_blocks.push_back(v6_empty_fam); addr_blocks.push_back(v4_fam_dupl); addr_blocks.push_back(v6_empty_fam_safi); addr_blocks.push_back(v6_fam_dupl); addr_blocks.push_back(v4_empty_fam); } std::vector expected_blocks = { 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); opts.extensions.add(std::move(blocks)); const Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); const Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); result.test_not_null("cert has IPAddrBlocks extension", ip_blocks); const auto& dec_blocks = ip_blocks->addr_blocks(); result.test_sz_eq("blocks got merged lengthwise", dec_blocks.size(), expected_blocks.size()); bool sorted = true; for(size_t i = 0; i < dec_blocks.size() - 1; i++) { const IPAddressBlocks::IPAddressFamily& a = dec_blocks[i]; const IPAddressBlocks::IPAddressFamily& b = dec_blocks[i + 1]; 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) { sorted = false; break; } } result.test_is_true("blocks got sorted", sorted); for(size_t i = 0; i < dec_blocks.size(); i++) { const IPAddressBlocks::IPAddressFamily& dec = dec_blocks[i]; const IPAddressBlocks::IPAddressFamily& exp = expected_blocks[i]; result.test_u16_eq("blocks match push order by afi at index " + std::to_string(i), dec.afi(), exp.afi()); result.test_opt_u8_eq("blocks match push order by safi at index " + std::to_string(i), dec.safi(), exp.safi()); if((exp.afi() == 1) && (dec.afi() == 1)) { auto dec_choice = std::get>(dec.addr_choice()); auto exp_choice = std::get>(exp.addr_choice()); if(!exp_choice.ranges().has_value()) { result.test_is_false("block ranges should inherit at index " + std::to_string(i), dec_choice.ranges().has_value()); } else { result.test_is_true("block ranges should not inherit at index " + std::to_string(i), dec_choice.ranges().has_value()); if(dec_choice.ranges().has_value() == false) { continue; } auto dec_ranges = dec_choice.ranges().value(); auto exp_ranges = exp_choice.ranges().value(); result.test_sz_eq("block ranges got merged lengthwise at index " + std::to_string(i), dec_ranges.size(), exp_ranges.size()); if(dec_ranges.size() != exp_ranges.size()) { continue; } for(size_t j = 0; j < exp_ranges.size(); j++) { result.test_bin_eq( "block ranges min got merged valuewise at indices " + std::to_string(i) + "," + std::to_string(j), exp_ranges[j].min().value(), dec_ranges[j].min().value()); result.test_bin_eq( "block ranges max got merged valuewise at indices " + std::to_string(i) + "," + std::to_string(j), exp_ranges[j].max().value(), dec_ranges[j].max().value()); } } } else if((exp.afi() == 2) && (dec.afi() == 2)) { auto dec_choice = std::get>(dec.addr_choice()); auto exp_choice = std::get>(exp.addr_choice()); if(!exp_choice.ranges().has_value()) { result.test_is_false("block ranges should inherit at index " + std::to_string(i), dec_choice.ranges().has_value()); } else { result.test_is_true("block ranges should not inherit at index " + std::to_string(i), dec_choice.ranges().has_value()); if(dec_choice.ranges().has_value() == false) { continue; } auto dec_ranges = dec_choice.ranges().value(); auto exp_ranges = exp_choice.ranges().value(); result.test_sz_eq("block ranges got merged lengthwise at index " + std::to_string(i), dec_ranges.size(), exp_ranges.size()); if(dec_ranges.size() != exp_ranges.size()) { continue; } for(size_t j = 0; j < exp_ranges.size(); j++) { result.test_bin_eq( "block ranges min got merged valuewise at indices " + std::to_string(i) + "," + std::to_string(j), exp_ranges[j].min().value(), dec_ranges[j].min().value()); result.test_bin_eq( "block ranges max got merged valuewise at indices " + std::to_string(i) + "," + std::to_string(j), exp_ranges[j].max().value(), dec_ranges[j].max().value()); } } } } result.end_timer(); return result; } 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++) { const bool include_v4 = bit_set<0>(i); const bool include_v6 = bit_set<1>(i); // 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); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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); const std::vector certs = {sub_cert, dyn_cert, inherit_cert}; const 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; 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::vector a = {120, 0, 0, 1}; auto root_ipv4_range_1_min = IPAddressBlocks::IPAddress{a}; a = {130, 140, 150, 160}; auto root_ipv4_range_1_max = IPAddressBlocks::IPAddress{a}; a = {10, 0, 0, 1}; auto root_ipv4_range_2_min = IPAddressBlocks::IPAddress(a); a = {10, 255, 255, 255}; auto root_ipv4_range_2_max = IPAddressBlocks::IPAddress(a); a = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; auto root_ipv6_range_1_min = IPAddressBlocks::IPAddress(a); a = {0xA0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; auto root_ipv6_range_1_max = IPAddressBlocks::IPAddress(a); a = {0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; auto root_ipv6_range_2_min = IPAddressBlocks::IPAddress(a); a = {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; auto root_ipv6_range_2_max = IPAddressBlocks::IPAddress(a); auto root_ipv4_range_1 = IPAddressBlocks::IPAddressOrRange(root_ipv4_range_1_min, root_ipv4_range_1_max); auto root_ipv4_range_2 = IPAddressBlocks::IPAddressOrRange(root_ipv4_range_2_min, root_ipv4_range_2_max); auto root_ipv6_range_1 = IPAddressBlocks::IPAddressOrRange(root_ipv6_range_1_min, root_ipv6_range_1_max); auto root_ipv6_range_2 = IPAddressBlocks::IPAddressOrRange(root_ipv6_range_2_min, root_ipv6_range_2_max); auto root_ipv4_ranges = {root_ipv4_range_1, root_ipv4_range_2}; auto root_ipv6_ranges = {root_ipv6_range_1, root_ipv6_range_2}; auto root_ipv4_choice = IPAddressBlocks::IPAddressChoice(root_ipv4_ranges); auto root_ipv6_choice = IPAddressBlocks::IPAddressChoice(root_ipv6_ranges); auto root_ipv4_family = IPAddressBlocks::IPAddressFamily(root_ipv4_choice, 42); auto root_ipv6_family = IPAddressBlocks::IPAddressFamily(root_ipv6_choice); auto root_addr_blocks = {root_ipv4_family, root_ipv6_family}; std::unique_ptr root_blocks = std::make_unique(root_addr_blocks); // Inherit cert auto inherit_ipv4_choice = IPAddressBlocks::IPAddressChoice(); auto inherit_ipv6_choice = IPAddressBlocks::IPAddressChoice(); auto inherit_ipv4_family = IPAddressBlocks::IPAddressFamily(inherit_ipv4_choice, 42); auto inherit_ipv6_family = IPAddressBlocks::IPAddressFamily(inherit_ipv6_choice); auto inherit_addr_blocks = {inherit_ipv4_family, inherit_ipv6_family}; std::unique_ptr inherit_blocks = std::make_unique(inherit_addr_blocks); // Dynamic Cert a = {122, 0, 0, 255}; auto dyn_ipv4_range_1_min = IPAddressBlocks::IPAddress(a); a = {128, 255, 255, 255}; auto dyn_ipv4_range_1_max = IPAddressBlocks::IPAddress(a); a = {10, 0, 0, 255}; auto dyn_ipv4_range_2_min = IPAddressBlocks::IPAddress(a); a = {10, 255, 0, 1}; auto dyn_ipv4_range_2_max = IPAddressBlocks::IPAddress(a); a = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; auto dyn_ipv6_range_1_min = IPAddressBlocks::IPAddress(a); a = {0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; auto dyn_ipv6_range_1_max = IPAddressBlocks::IPAddress(a); auto dyn_ipv4_range_1 = IPAddressBlocks::IPAddressOrRange(dyn_ipv4_range_1_min, dyn_ipv4_range_1_max); auto dyn_ipv4_range_2 = IPAddressBlocks::IPAddressOrRange(dyn_ipv4_range_2_min, dyn_ipv4_range_2_max); auto dyn_ipv6_range = IPAddressBlocks::IPAddressOrRange(dyn_ipv6_range_1_min, dyn_ipv6_range_1_max); auto dyn_ipv4_ranges = {dyn_ipv4_range_1, dyn_ipv4_range_2}; auto dyn_ipv6_ranges = {dyn_ipv6_range}; // Subject cert a = {124, 0, 255, 0}; auto sub_ipv4_range_1_min = IPAddressBlocks::IPAddress(a); a = {126, 0, 0, 1}; auto sub_ipv4_range_1_max = IPAddressBlocks::IPAddress(a); a = {10, 0, 2, 1}; auto sub_ipv4_range_2_min = IPAddressBlocks::IPAddress(a); a = {10, 42, 0, 255}; auto sub_ipv4_range_2_max = IPAddressBlocks::IPAddress(a); a = {0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; auto sub_ipv6_range_1_min = IPAddressBlocks::IPAddress(a); a = {0x0D, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; auto sub_ipv6_range_1_max = IPAddressBlocks::IPAddress(a); auto sub_ipv4_range_1 = IPAddressBlocks::IPAddressOrRange(sub_ipv4_range_1_min, sub_ipv4_range_1_max); auto sub_ipv4_range_2 = IPAddressBlocks::IPAddressOrRange(sub_ipv4_range_2_min, sub_ipv4_range_2_max); auto sub_ipv6_range = IPAddressBlocks::IPAddressOrRange(sub_ipv6_range_1_min, sub_ipv6_range_1_max); auto sub_ipv4_ranges = {sub_ipv4_range_1, sub_ipv4_range_2}; auto sub_ipv6_ranges = {sub_ipv6_range}; auto sub_ipv4_choice = IPAddressBlocks::IPAddressChoice(sub_ipv4_ranges); auto sub_ipv6_choice = IPAddressBlocks::IPAddressChoice(sub_ipv6_ranges); auto sub_ipv4_family = IPAddressBlocks::IPAddressFamily(sub_ipv4_choice, 42); auto sub_ipv6_family = IPAddressBlocks::IPAddressFamily(sub_ipv6_choice); auto sub_addr_blocks = {sub_ipv4_family, sub_ipv6_family}; std::unique_ptr sub_blocks = std::make_unique(sub_addr_blocks); Botan::X509_Cert_Options root_opts = ca_opts(); root_opts.extensions.add(std::move(root_blocks)); auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); sub_opts.extensions.add(std::move(sub_blocks)); auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); for(size_t i = 0; i < 4; i++) { const bool include_v4 = bit_set<0>(i); const bool include_v6 = bit_set<1>(i); auto dyn_ipv4_choice = IPAddressBlocks::IPAddressChoice(include_v4 ? std::optional(dyn_ipv4_ranges) : std::nullopt); auto dyn_ipv6_choice = IPAddressBlocks::IPAddressChoice(include_v6 ? std::optional(dyn_ipv6_ranges) : std::nullopt); auto dyn_ipv4_family = IPAddressBlocks::IPAddressFamily(dyn_ipv4_choice, 42); auto dyn_ipv6_family = IPAddressBlocks::IPAddressFamily(dyn_ipv6_choice); auto dyn_addr_blocks = {dyn_ipv4_family, dyn_ipv6_family}; std::unique_ptr dyn_blocks = std::make_unique(dyn_addr_blocks); auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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); const std::vector certs = {sub_cert, dyn_cert, inherit_cert}; const 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_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++) { const bool all_inherit = (i == 0); const bool different_safi = (i == 1); const bool too_small_subrange = (i == 2); const bool too_large_subrange = (i == 3); const bool no_more_issuer_ranges = (i == 4); const bool empty_issuer_ranges = (i == 5); const 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)); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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); const std::vector certs = {sub_cert, iss_cert}; Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); const 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; auto rng = Test::new_rng(__func__); for(size_t i = 0; i < 7; i++) { const bool all_inherit = (i == 0); const bool different_safi = (i == 1); const bool too_small_subrange = (i == 2); const bool too_large_subrange = (i == 3); const bool no_more_issuer_ranges = (i == 4); const bool empty_issuer_ranges = (i == 5); const bool nullptr_extensions = (i == 6); // Root cert std::vector a = {120, 0, 0, 1}; auto root_range_1_min = IPAddressBlocks::IPAddress{a}; a = {130, 140, 150, 160}; auto root_range_1_max = IPAddressBlocks::IPAddress{a}; auto root_range_1 = IPAddressBlocks::IPAddressOrRange(root_range_1_min, root_range_1_max); auto root_ranges = {root_range_1}; auto root_choice = IPAddressBlocks::IPAddressChoice(all_inherit ? std::nullopt : std::optional(root_ranges)); auto root_family = IPAddressBlocks::IPAddressFamily(root_choice, 42); auto root_addr_blocks = {root_family}; std::unique_ptr root_blocks = std::make_unique(root_addr_blocks); Botan::X509_Cert_Options root_opts = ca_opts(); 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 a = {122, 0, 0, 255}; auto iss_range_1_min = IPAddressBlocks::IPAddress(a); a = {128, 255, 255, 255}; auto iss_range_1_max = IPAddressBlocks::IPAddress(a); auto iss_range_1 = IPAddressBlocks::IPAddressOrRange(iss_range_1_min, iss_range_1_max); std::vector> iss_ranges; if(!empty_issuer_ranges) { iss_ranges.push_back(iss_range_1); } auto iss_choice = IPAddressBlocks::IPAddressChoice(all_inherit ? std::nullopt : std::optional(iss_ranges)); auto iss_family = IPAddressBlocks::IPAddressFamily(iss_choice, 42); auto iss_addr_blocks = {iss_family}; std::unique_ptr iss_blocks = std::make_unique(iss_addr_blocks); auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, rng); // Subject cert if(too_small_subrange) { a = {118, 0, 255, 0}; } else if(no_more_issuer_ranges) { a = {140, 0, 0, 1}; } else { a = {124, 0, 255, 0}; } auto sub_range_1_min = IPAddressBlocks::IPAddress(a); if(too_large_subrange) { a = {134, 0, 0, 1}; } else if(no_more_issuer_ranges) { a = {150, 0, 0, 1}; } else { a = {126, 0, 0, 1}; } auto sub_range_1_max = IPAddressBlocks::IPAddress(a); auto sub_range_1 = IPAddressBlocks::IPAddressOrRange(sub_range_1_min, sub_range_1_max); auto sub_ranges = {sub_range_1}; auto sub_choice = IPAddressBlocks::IPAddressChoice(all_inherit ? std::nullopt : std::optional(sub_ranges)); auto sub_family = IPAddressBlocks::IPAddressFamily(sub_choice, different_safi ? 41 : 42); auto sub_addr_blocks = {sub_family}; std::unique_ptr sub_blocks = std::make_unique(sub_addr_blocks); Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); sub_opts.extensions.add(std::move(sub_blocks)); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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); const std::vector certs = {sub_cert, iss_cert}; Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); const 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_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_bin_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.test_u32_eq("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_bin_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; auto rng = Test::new_rng(__func__); auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); for(size_t i = 0; i < 16; i++) { const bool push_asnum = bit_set<0>(i); const bool push_rdi = bit_set<1>(i); const bool include_asnum = bit_set<2>(i); const bool include_rdi = bit_set<3>(i); if(!include_asnum && !include_rdi) { continue; } const ASBlocks::ASIdOrRange asnum_id_or_range0 = ASBlocks::ASIdOrRange(0, 999); const ASBlocks::ASIdOrRange asnum_id_or_range1 = ASBlocks::ASIdOrRange(5042); const ASBlocks::ASIdOrRange asnum_id_or_range2 = ASBlocks::ASIdOrRange(5043, 4294967295); const ASBlocks::ASIdOrRange rdi_id_or_range0 = ASBlocks::ASIdOrRange(1234, 5678); const ASBlocks::ASIdOrRange rdi_id_or_range1 = ASBlocks::ASIdOrRange(32768); const ASBlocks::ASIdOrRange rdi_id_or_range2 = ASBlocks::ASIdOrRange(32769, 4294967295); std::vector as_ranges; if(push_asnum) { as_ranges.push_back(asnum_id_or_range0); as_ranges.push_back(asnum_id_or_range1); as_ranges.push_back(asnum_id_or_range2); } std::vector rdi_ranges; if(push_rdi) { rdi_ranges.push_back(rdi_id_or_range0); rdi_ranges.push_back(rdi_id_or_range1); rdi_ranges.push_back(rdi_id_or_range2); } ASBlocks::ASIdentifierChoice asnum = ASBlocks::ASIdentifierChoice(as_ranges); ASBlocks::ASIdentifierChoice rdi = ASBlocks::ASIdentifierChoice(rdi_ranges); const ASBlocks::ASIdentifiers ident = ASBlocks::ASIdentifiers(include_asnum ? std::optional(asnum) : std::nullopt, include_rdi ? std::optional(rdi) : std::nullopt); std::unique_ptr blocks = std::make_unique(ident); Botan::X509_Cert_Options opts = req_opts(sig_algo); opts.extensions.add(std::move(blocks)); const Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); const Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* as_blocks = cert.v3_extensions().get_extension_object_as(); result.test_is_true("cert has ASBlock extension", as_blocks != nullptr); const auto& identifier = as_blocks->as_identifiers(); if(include_asnum) { const auto& asnum_entries = identifier.asnum().value().ranges().value(); if(push_asnum) { result.test_u32_eq("asnum entry 0 min", asnum_entries[0].min(), 0); result.test_u32_eq("asnum entry 0 max", asnum_entries[0].max(), 999); result.test_u32_eq("asnum entry 1 min", asnum_entries[1].min(), 5042); result.test_u32_eq("asnum entry 1 max", asnum_entries[1].max(), 4294967295); } else { result.test_is_true("asnum has no entries", asnum_entries.empty()); } } else { result.test_is_false("no asnum entry", identifier.asnum().has_value()); } if(include_rdi) { const auto& rdi_entries = identifier.rdi().value().ranges().value(); if(push_rdi) { result.test_u32_eq("rdi entry 0 min", rdi_entries[0].min(), 1234); result.test_u32_eq("rdi entry 0 max", rdi_entries[0].max(), 5678); result.test_u32_eq("rdi entry 1 min", rdi_entries[1].min(), 32768); result.test_u32_eq("rdi entry 1 max", rdi_entries[1].max(), 4294967295); } else { result.test_is_true("rdi has no entries", rdi_entries.empty()); } } else { result.test_is_false("rdi has no entry", identifier.rdi().has_value()); } } } result.end_timer(); return result; } Test::Result test_x509_as_blocks_range_merge() { Test::Result result("X509 AS Block range merge"); result.start_timer(); using Botan::Cert_Extension::ASBlocks; auto rng = Test::new_rng(__func__); auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); Botan::X509_Cert_Options opts = req_opts(sig_algo); const std::vector> ranges = { {2005, 37005}, {60, 70}, {22, 50}, {35, 2000}, {2001, 2004}, {21, 21}, {0, 20}, }; std::vector as_ranges; as_ranges.reserve(ranges.size()); for(const auto& pair : ranges) { as_ranges.emplace_back(pair[0], pair[1]); } ASBlocks::ASIdentifierChoice asnum = ASBlocks::ASIdentifierChoice(as_ranges); const ASBlocks::ASIdentifiers ident = ASBlocks::ASIdentifiers(std::optional(asnum), std::nullopt); std::unique_ptr blocks = std::make_unique(ident); opts.extensions.add(std::move(blocks)); const Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); const Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* as_blocks = cert.v3_extensions().get_extension_object_as(); result.test_is_true("cert has ASBlock extension", as_blocks != nullptr); const auto& identifier = as_blocks->as_identifiers(); const auto& asnum_entries = identifier.asnum().value().ranges().value(); result.test_u32_eq("asnum entry 0 min", asnum_entries[0].min(), 0); result.test_u32_eq("asnum entry 0 max", asnum_entries[0].max(), 37005); result.test_sz_eq("asnum length", asnum_entries.size(), 1); } result.end_timer(); return result; } 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++) { const bool include_asnum = bit_set<0>(i); const bool include_rdi = bit_set<1>(i); 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); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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); const std::vector certs = {sub_cert, dyn_cert, inherit_cert}; const 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; 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 const ASBlocks::ASIdOrRange root_asnum_id_or_range0 = ASBlocks::ASIdOrRange(0, 999); const ASBlocks::ASIdOrRange root_asnum_id_or_range1 = ASBlocks::ASIdOrRange(5042); const ASBlocks::ASIdOrRange root_asnum_id_or_range2 = ASBlocks::ASIdOrRange(5043, 4294967295); const ASBlocks::ASIdOrRange root_rdi_id_or_range0 = ASBlocks::ASIdOrRange(1234, 5678); const ASBlocks::ASIdOrRange root_rdi_id_or_range1 = ASBlocks::ASIdOrRange(32768); const ASBlocks::ASIdOrRange root_rdi_id_or_range2 = ASBlocks::ASIdOrRange(32769, 4294967295); std::vector root_as_ranges; root_as_ranges.push_back(root_asnum_id_or_range0); root_as_ranges.push_back(root_asnum_id_or_range1); root_as_ranges.push_back(root_asnum_id_or_range2); std::vector root_rdi_ranges; root_rdi_ranges.push_back(root_rdi_id_or_range0); root_rdi_ranges.push_back(root_rdi_id_or_range1); root_rdi_ranges.push_back(root_rdi_id_or_range2); ASBlocks::ASIdentifierChoice root_asnum = ASBlocks::ASIdentifierChoice(root_as_ranges); ASBlocks::ASIdentifierChoice root_rdi = ASBlocks::ASIdentifierChoice(root_rdi_ranges); const ASBlocks::ASIdentifiers root_ident = ASBlocks::ASIdentifiers(root_asnum, root_rdi); std::unique_ptr root_blocks = std::make_unique(root_ident); // Inherit cert, both as 'inherit' ASBlocks::ASIdentifierChoice inherit_asnum = ASBlocks::ASIdentifierChoice(); ASBlocks::ASIdentifierChoice inherit_rdi = ASBlocks::ASIdentifierChoice(); const ASBlocks::ASIdentifiers inherit_ident = ASBlocks::ASIdentifiers(inherit_asnum, inherit_rdi); std::unique_ptr inherit_blocks = std::make_unique(inherit_ident); // Dynamic cert const ASBlocks::ASIdOrRange dyn_asnum_id_or_range0 = ASBlocks::ASIdOrRange(100, 600); const ASBlocks::ASIdOrRange dyn_asnum_id_or_range1 = ASBlocks::ASIdOrRange(678); const ASBlocks::ASIdOrRange dyn_asnum_id_or_range2 = ASBlocks::ASIdOrRange(5042, 5101); const ASBlocks::ASIdOrRange dyn_rdi_id_or_range0 = ASBlocks::ASIdOrRange(1500, 5000); const ASBlocks::ASIdOrRange dyn_rdi_id_or_range1 = ASBlocks::ASIdOrRange(33000, 60000); std::vector dyn_as_ranges; dyn_as_ranges.push_back(dyn_asnum_id_or_range0); dyn_as_ranges.push_back(dyn_asnum_id_or_range1); dyn_as_ranges.push_back(dyn_asnum_id_or_range2); std::vector dyn_rdi_ranges; dyn_rdi_ranges.push_back(dyn_rdi_id_or_range0); dyn_rdi_ranges.push_back(dyn_rdi_id_or_range1); // Subject cert const ASBlocks::ASIdOrRange sub_asnum_id_or_range0 = ASBlocks::ASIdOrRange(120, 180); const ASBlocks::ASIdOrRange sub_asnum_id_or_range1 = ASBlocks::ASIdOrRange(220, 240); const ASBlocks::ASIdOrRange sub_asnum_id_or_range2 = ASBlocks::ASIdOrRange(260, 511); const ASBlocks::ASIdOrRange sub_asnum_id_or_range3 = ASBlocks::ASIdOrRange(678); const ASBlocks::ASIdOrRange sub_asnum_id_or_range4 = ASBlocks::ASIdOrRange(5043, 5100); const ASBlocks::ASIdOrRange sub_rdi_id_or_range0 = ASBlocks::ASIdOrRange(1500, 2300); const ASBlocks::ASIdOrRange sub_rdi_id_or_range1 = ASBlocks::ASIdOrRange(2500, 4000); const ASBlocks::ASIdOrRange sub_rdi_id_or_range2 = ASBlocks::ASIdOrRange(1567); const ASBlocks::ASIdOrRange sub_rdi_id_or_range3 = ASBlocks::ASIdOrRange(33100, 40000); std::vector sub_as_ranges; sub_as_ranges.push_back(sub_asnum_id_or_range0); sub_as_ranges.push_back(sub_asnum_id_or_range1); sub_as_ranges.push_back(sub_asnum_id_or_range2); sub_as_ranges.push_back(sub_asnum_id_or_range3); sub_as_ranges.push_back(sub_asnum_id_or_range4); std::vector sub_rdi_ranges; sub_rdi_ranges.push_back(sub_rdi_id_or_range0); sub_rdi_ranges.push_back(sub_rdi_id_or_range1); sub_rdi_ranges.push_back(sub_rdi_id_or_range2); sub_rdi_ranges.push_back(sub_rdi_id_or_range3); ASBlocks::ASIdentifierChoice sub_asnum = ASBlocks::ASIdentifierChoice(sub_as_ranges); ASBlocks::ASIdentifierChoice sub_rdi = ASBlocks::ASIdentifierChoice(sub_rdi_ranges); const ASBlocks::ASIdentifiers sub_ident = ASBlocks::ASIdentifiers(sub_asnum, sub_rdi); std::unique_ptr sub_blocks = std::make_unique(sub_ident); Botan::X509_Cert_Options root_opts = ca_opts(); root_opts.extensions.add(std::move(root_blocks)); auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); sub_opts.extensions.add(std::move(sub_blocks)); auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); for(size_t i = 0; i < 4; i++) { const bool include_asnum = bit_set<0>(i); const bool include_rdi = bit_set<1>(i); ASBlocks::ASIdentifierChoice dyn_asnum = ASBlocks::ASIdentifierChoice(include_asnum ? std::optional(dyn_as_ranges) : std::nullopt); ASBlocks::ASIdentifierChoice dyn_rdi = ASBlocks::ASIdentifierChoice(include_rdi ? std::optional(dyn_rdi_ranges) : std::nullopt); const ASBlocks::ASIdentifiers dyn_ident = ASBlocks::ASIdentifiers(dyn_asnum, dyn_rdi); std::unique_ptr dyn_blocks = std::make_unique(dyn_ident); auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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); const std::vector certs = {sub_cert, dyn_cert, inherit_cert}; const 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_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 const 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)); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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}; const 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; auto rng = Test::new_rng(__func__); // Subject cert const ASBlocks::ASIdOrRange sub_asnum_id_or_range0 = ASBlocks::ASIdOrRange(120, 180); const ASBlocks::ASIdOrRange sub_asnum_id_or_range1 = ASBlocks::ASIdOrRange(220, 240); const ASBlocks::ASIdOrRange sub_asnum_id_or_range2 = ASBlocks::ASIdOrRange(260, 511); const ASBlocks::ASIdOrRange sub_asnum_id_or_range3 = ASBlocks::ASIdOrRange(678); const ASBlocks::ASIdOrRange sub_asnum_id_or_range4 = ASBlocks::ASIdOrRange(5043, 5100); const ASBlocks::ASIdOrRange sub_rdi_id_or_range0 = ASBlocks::ASIdOrRange(1500, 2300); const ASBlocks::ASIdOrRange sub_rdi_id_or_range1 = ASBlocks::ASIdOrRange(2500, 4000); const ASBlocks::ASIdOrRange sub_rdi_id_or_range2 = ASBlocks::ASIdOrRange(1567); const ASBlocks::ASIdOrRange sub_rdi_id_or_range3 = ASBlocks::ASIdOrRange(33100, 40000); std::vector sub_as_ranges; sub_as_ranges.push_back(sub_asnum_id_or_range0); sub_as_ranges.push_back(sub_asnum_id_or_range1); sub_as_ranges.push_back(sub_asnum_id_or_range2); sub_as_ranges.push_back(sub_asnum_id_or_range3); sub_as_ranges.push_back(sub_asnum_id_or_range4); std::vector sub_rdi_ranges; sub_rdi_ranges.push_back(sub_rdi_id_or_range0); sub_rdi_ranges.push_back(sub_rdi_id_or_range1); sub_rdi_ranges.push_back(sub_rdi_id_or_range2); sub_rdi_ranges.push_back(sub_rdi_id_or_range3); ASBlocks::ASIdentifierChoice sub_asnum = ASBlocks::ASIdentifierChoice(sub_as_ranges); ASBlocks::ASIdentifierChoice sub_rdi = ASBlocks::ASIdentifierChoice(sub_rdi_ranges); const ASBlocks::ASIdentifiers sub_ident = ASBlocks::ASIdentifiers(sub_asnum, sub_rdi); std::unique_ptr sub_blocks = std::make_unique(sub_ident); // create a root ca that does not have any extension const 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)); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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}; const 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_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 const bool inherit_all_asnums = (i == 0); const bool inherit_all_rdis = (i == 1); const bool push_asnum_min_edge_ranges = (i == 2) || (i == 4); const bool push_rdi_min_edge_ranges = (i == 3) || (i == 4); const bool push_asnum_max_edge_ranges = (i == 5) || (i == 7); const bool push_rdi_max_edge_ranges = (i == 6) || (i == 7); const bool push_asnum_max_middle_ranges = (i == 8) || (i == 10); const bool push_rdi_max_middle_ranges = (i == 9) || (i == 10); const bool push_asnum_max_split_ranges = (i == 11) || (i == 13); const bool push_rdi_max_split_ranges = (i == 12) || (i == 13); const bool push_asnum_min_middle_ranges = (i == 14) || (i == 16); const bool push_rdi_min_middle_ranges = (i == 15) || (i == 16); const bool push_asnum_min_split_ranges = (i == 17) || (i == 19); const bool push_rdi_min_split_ranges = (i == 18) || (i == 19); const 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(); 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)); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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}; const 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.test_is_true("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; 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 const bool inherit_all_asnums = (i == 0); const bool inherit_all_rdis = (i == 1); const bool push_asnum_min_edge_ranges = (i == 2) || (i == 4); const bool push_rdi_min_edge_ranges = (i == 3) || (i == 4); const bool push_asnum_max_edge_ranges = (i == 5) || (i == 7); const bool push_rdi_max_edge_ranges = (i == 6) || (i == 7); const bool push_asnum_max_middle_ranges = (i == 8) || (i == 10); const bool push_rdi_max_middle_ranges = (i == 9) || (i == 10); const bool push_asnum_max_split_ranges = (i == 11) || (i == 13); const bool push_rdi_max_split_ranges = (i == 12) || (i == 13); const bool push_asnum_min_middle_ranges = (i == 14) || (i == 16); const bool push_rdi_min_middle_ranges = (i == 15) || (i == 16); const bool push_asnum_min_split_ranges = (i == 17) || (i == 19); const bool push_rdi_min_split_ranges = (i == 18) || (i == 19); const bool empty_issuer_non_empty_subject = (i == 20); // Root cert std::vector root_as_ranges; std::vector root_rdi_ranges; // assign the root ranges if(push_asnum_min_edge_ranges || push_asnum_max_edge_ranges) { // 100-200 for 02,03,04 root_as_ranges.push_back(ASBlocks::ASIdOrRange(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_as_ranges.push_back(ASBlocks::ASIdOrRange(10, 20)); root_as_ranges.push_back(ASBlocks::ASIdOrRange(30, 40)); root_as_ranges.push_back(ASBlocks::ASIdOrRange(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_as_ranges.push_back(ASBlocks::ASIdOrRange(10, 20)); root_as_ranges.push_back(ASBlocks::ASIdOrRange(30, 50)); root_as_ranges.push_back(ASBlocks::ASIdOrRange(60, 70)); } // same values but for rdis if(push_rdi_min_edge_ranges || push_rdi_max_edge_ranges) { root_rdi_ranges.push_back(ASBlocks::ASIdOrRange(100, 200)); } else if(push_rdi_max_middle_ranges || push_rdi_min_middle_ranges) { root_rdi_ranges.push_back(ASBlocks::ASIdOrRange(10, 20)); root_rdi_ranges.push_back(ASBlocks::ASIdOrRange(30, 40)); root_rdi_ranges.push_back(ASBlocks::ASIdOrRange(50, 60)); } else if(push_rdi_max_split_ranges || push_rdi_min_split_ranges) { root_rdi_ranges.push_back(ASBlocks::ASIdOrRange(10, 20)); root_rdi_ranges.push_back(ASBlocks::ASIdOrRange(30, 50)); root_rdi_ranges.push_back(ASBlocks::ASIdOrRange(60, 70)); } // 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 const std::vector issu_as_ranges; const std::vector issu_rdi_ranges; // Subject cert std::vector sub_as_ranges; std::vector sub_rdi_ranges; // assign the subject asnum ranges if(push_asnum_min_edge_ranges) { // 99-200 for 02 (so overlapping to the left) sub_as_ranges.push_back(ASBlocks::ASIdOrRange(99, 200)); } else if(push_asnum_max_edge_ranges) { // 100-201 for 03 (so overlapping to the right) sub_as_ranges.push_back(ASBlocks::ASIdOrRange(100, 201)); } else if(push_asnum_max_middle_ranges) { // just change the range in the middle to overlap to the right for 08 sub_as_ranges = root_as_ranges; sub_as_ranges[1] = ASBlocks::ASIdOrRange(30, 41); } 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_as_ranges = root_as_ranges; sub_as_ranges[1] = ASBlocks::ASIdOrRange(30, 44); // pushing the new range created by splitting to the back since they will be sorted anyway sub_as_ranges.push_back(ASBlocks::ASIdOrRange(46, 51)); } else if(push_asnum_min_middle_ranges) { // just change the test in the middle to overlap to the left for case 14 sub_as_ranges = root_as_ranges; sub_as_ranges[1] = ASBlocks::ASIdOrRange(29, 40); } 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_as_ranges = root_as_ranges; sub_as_ranges[1] = ASBlocks::ASIdOrRange(29, 44); sub_as_ranges.push_back(ASBlocks::ASIdOrRange(46, 50)); } else if(empty_issuer_non_empty_subject) { sub_as_ranges.push_back(ASBlocks::ASIdOrRange(50)); } // same values but for rdis if(push_rdi_min_edge_ranges) { sub_rdi_ranges.push_back(ASBlocks::ASIdOrRange(99, 200)); } else if(push_rdi_max_edge_ranges) { sub_rdi_ranges.push_back(ASBlocks::ASIdOrRange(100, 201)); } else if(push_rdi_max_middle_ranges) { sub_rdi_ranges = root_rdi_ranges; sub_rdi_ranges[1] = ASBlocks::ASIdOrRange(30, 41); } else if(push_rdi_max_split_ranges) { sub_rdi_ranges = root_rdi_ranges; sub_rdi_ranges[1] = ASBlocks::ASIdOrRange(30, 44); sub_rdi_ranges.push_back(ASBlocks::ASIdOrRange(46, 51)); } else if(push_rdi_min_middle_ranges) { sub_rdi_ranges = root_rdi_ranges; sub_rdi_ranges[1] = ASBlocks::ASIdOrRange(29, 40); } else if(push_rdi_min_split_ranges) { sub_rdi_ranges = root_rdi_ranges; sub_rdi_ranges[1] = ASBlocks::ASIdOrRange(29, 44); sub_rdi_ranges.push_back(ASBlocks::ASIdOrRange(46, 50)); } // for cases 00 and 01, set all certs to inherit (so std::nullopt) // in all other cases use the ranges created beforehand ASBlocks::ASIdentifierChoice root_asnum = ASBlocks::ASIdentifierChoice(inherit_all_asnums ? std::nullopt : std::optional(root_as_ranges)); ASBlocks::ASIdentifierChoice root_rdi = ASBlocks::ASIdentifierChoice(inherit_all_rdis ? std::nullopt : std::optional(root_rdi_ranges)); const ASBlocks::ASIdentifiers root_ident = ASBlocks::ASIdentifiers(root_asnum, root_rdi); std::unique_ptr root_blocks = std::make_unique(root_ident); const ASBlocks::ASIdentifiers& issu_ident = root_ident; std::unique_ptr issu_blocks = std::make_unique(issu_ident); ASBlocks::ASIdentifierChoice sub_asnum = ASBlocks::ASIdentifierChoice(inherit_all_asnums ? std::nullopt : std::optional(sub_as_ranges)); ASBlocks::ASIdentifierChoice sub_rdi = ASBlocks::ASIdentifierChoice(inherit_all_rdis ? std::nullopt : std::optional(sub_rdi_ranges)); const ASBlocks::ASIdentifiers sub_ident = ASBlocks::ASIdentifiers(sub_asnum, sub_rdi); std::unique_ptr sub_blocks = std::make_unique(sub_ident); Botan::X509_Cert_Options root_opts = ca_opts(); root_opts.extensions.add(std::move(root_blocks)); auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, rng); Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); sub_opts.extensions.add(std::move(sub_blocks)); const Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); const 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}; const 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.test_is_true("path validation fails at iteration " + std::to_string(i), !path_result.successful_validation()); } result.end_timer(); return result; } class X509_RPKI_Tests final : public Test { public: std::vector run() override { std::vector results; #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) 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_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_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_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; } }; BOTAN_REGISTER_TEST("x509", "x509_rpki", X509_RPKI_Tests); #endif } // namespace } // namespace Botan_Tests