From 4defbdf563e50ad895d5cd1af1e5ea7287d3430e Mon Sep 17 00:00:00 2001 From: tb Date: Fri, 24 Dec 2021 03:00:37 +0000 Subject: [PATCH] Add initial test coverage for RFC 3779 code. This exercises the code paths that are reached from the validator and also tests that the public API behaves as expected. There is a lot more that could be done here, but this test is already big enough. Missing are tests for X509v3_{addr,asid}_validate_{path,resource_set}() themselves. One test failure is ignored and will be fixed in the near future when a bad logic error in range_should_be_prefix() is fixed. A consequence of this bug is that we will currently accept and generate DER that doesn't conform to RFC 3779. --- regress/lib/libcrypto/x509/rfc3779/Makefile | 11 + regress/lib/libcrypto/x509/rfc3779/rfc3779.c | 1793 ++++++++++++++++++ 2 files changed, 1804 insertions(+) create mode 100644 regress/lib/libcrypto/x509/rfc3779/Makefile create mode 100644 regress/lib/libcrypto/x509/rfc3779/rfc3779.c diff --git a/regress/lib/libcrypto/x509/rfc3779/Makefile b/regress/lib/libcrypto/x509/rfc3779/Makefile new file mode 100644 index 00000000000..3709471c8f7 --- /dev/null +++ b/regress/lib/libcrypto/x509/rfc3779/Makefile @@ -0,0 +1,11 @@ +# $OpenBSD: Makefile,v 1.1 2021/12/24 03:00:37 tb Exp $ + +.include "../../Makefile.inc" + +PROG= rfc3779 +LDADD= ${CRYPTO_INT} +DPADD= ${LIBCRYPTO} +WARNINGS= Yes +CFLAGS+= -DLIBRESSL_INTERNAL -Werror -g -O0 + +.include diff --git a/regress/lib/libcrypto/x509/rfc3779/rfc3779.c b/regress/lib/libcrypto/x509/rfc3779/rfc3779.c new file mode 100644 index 00000000000..65bbf2294bd --- /dev/null +++ b/regress/lib/libcrypto/x509/rfc3779/rfc3779.c @@ -0,0 +1,1793 @@ +/* $OpenBSD: rfc3779.c,v 1.1 2021/12/24 03:00:37 tb Exp $ */ +/* + * Copyright (c) 2021 Theo Buehler + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include + +#define RAW_ADDRESS_SIZE 16 + +static void +hexdump(const unsigned char *buf, size_t len) +{ + size_t i; + + for (i = 1; i <= len; i++) + fprintf(stderr, " 0x%02hhx,%s", buf[i - 1], i % 8 ? "" : "\n"); + + if (len % 8) + fprintf(stderr, "\n"); +} + +static void +report_hexdump(const char *func, const char *description, const char *msg, + const unsigned char *want, size_t want_len, + const unsigned char *got, size_t got_len) +{ + fprintf(stderr, "%s: \"%s\" %s\nwant:\n", func, description, msg); + hexdump(want, want_len); + fprintf(stderr, "got:\n"); + hexdump(got, got_len); +} + +static int +afi_size(int afi) +{ + switch (afi) { + case IANA_AFI_IPV4: + return 4; + case IANA_AFI_IPV6: + return 16; + } + return 0; +} + +struct IPAddressOrRange_test { + const char *description; + const uint8_t der[32]; + size_t der_len; + unsigned afi; + const uint8_t min[RAW_ADDRESS_SIZE]; + const uint8_t max[RAW_ADDRESS_SIZE]; +}; + +const struct IPAddressOrRange_test IPAddressOrRange_test_data[] = { + /* Examples from RFC 3779, section 2.1.1 */ + { + .description = "address 10.5.0.4", + .der = { + 0x03, 0x05, 0x00, 0x0a, 0x05, 0x00, 0x04, + }, + .der_len = 7, + .afi = IANA_AFI_IPV4, + .min = { + 0x0a, 0x05, 0x00, 0x04, + }, + .max = { + 0x0a, 0x05, 0x00, 0x04, + } + }, + { + .description = "prefix 10.5.0/23", + .der = { + 0x03, 0x04, 0x01, 0x0a, 0x05, 0x00, + }, + .der_len = 6, + .afi = IANA_AFI_IPV4, + .min = { + 0x0a, 0x05, 0x00, 0x00, + }, + .max = { + 0x0a, 0x05, 0x01, 0xff, + } + }, + { + .description = "address 2001:0:200:3::1", + .der = { + 0x03, 0x11, 0x00, 0x20, 0x01, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, + }, + .der_len = 19, + .afi = IANA_AFI_IPV6, + .min = { + 0x20, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, + .max = { + 0x20, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, + }, + { + .description = "prefix 2001:0:200/39", + .der = { + 0x03, 0x06, 0x01, 0x20, 0x01, 0x00, 0x00, 0x02, + }, + .der_len = 8, + .afi = IANA_AFI_IPV6, + .min = { + 0x20, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + .max = { + 0x20, 0x01, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + }, + + /* Examples from RFC 3779, Section 2.1.2 */ + { + .description = "prefix 10.5.0/23 as a range", + .der = { + /* Sequence */ + 0x30, 0x0b, + /* 10.5.0.0 */ + 0x03, 0x03, 0x00, 0x0a, 0x05, + /* 10.5.1.255 */ + 0x03, 0x04, 0x01, 0x0a, 0x05, 0x00, + }, + .der_len = 13, + .afi = IANA_AFI_IPV4, + .min = { + 0x0a, 0x05, 0x00, 0x00, + }, + .max = { + 0x0a, 0x05, 0x01, 0xff, + } + }, + { + .description = "prefix 2001:0:200/39 as a range", + .der = { + /* Sequence */ + 0x30, 0x10, + /* 2001:0:200:: */ + 0x03, 0x06, 0x01, 0x20, 0x01, 0x00, 0x00, 0x02, + /* 2001:0:3ff:ffff:ffff:ffff:ffff:ffff */ + 0x03, 0x06, 0x02, 0x20, 0x01, 0x00, 0x00, 0x00, + }, + .der_len = 18, + .afi = IANA_AFI_IPV6, + .min = { + 0x20, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + .max = { + 0x20, 0x01, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + } + }, + { + .description = "prefix 0/0", + .der = { + 0x03, 0x01, 0x00, + }, + .der_len = 3, + .afi = IANA_AFI_IPV4, + .min = { + 0x00, 0x00, 0x00, 0x00, + }, + .max = { + 0xff, 0xff, 0xff, 0xff, + } + }, + { + .description = "prefix 10.64/12", + .der = { + 0x03, 0x03, 0x04, 0x0a, 0x40, + }, + .der_len = 5, + .afi = IANA_AFI_IPV4, + .min = { + 0x0a, 0x40, 0x00, 0x00, + }, + .max = { + 0x0a, 0x4f, 0xff, 0xff, + }, + }, + { + .description = "prefix 10.64/20", + .der = { + 0x03, 0x04, 0x04, 0x0a, 0x40, 0x00, + }, + .der_len = 6, + .afi = IANA_AFI_IPV4, + .min = { + 0x0a, 0x40, 0x00, 0x00, + }, + .max = { + 0x0a, 0x40, 0x0f, 0xff, + }, + }, +}; + +const size_t N_IPADDRESSORRANGE_TESTS = + sizeof(IPAddressOrRange_test_data) / sizeof(IPAddressOrRange_test_data[0]); + +static int +test_IPAddressOrRange(const struct IPAddressOrRange_test *test) +{ + IPAddressOrRange *aor; + const unsigned char *p; + unsigned char min[RAW_ADDRESS_SIZE] = {0}, max[RAW_ADDRESS_SIZE] = {0}; + unsigned char *out = NULL; + int out_len; + int afi_len; + int memcmp_failed = 0; + int failed = 1; + + /* + * First, decode DER from the test case. + */ + + p = &test->der[0]; + if ((aor = d2i_IPAddressOrRange(NULL, &p, test->der_len)) == NULL) { + fprintf(stderr, "%s: \"%s\" d2i_IPAddressOrRange failed\n", + __func__, test->description); + goto err; + } + + /* + * Now extract minimum and maximum from the parsed range. + */ + + afi_len = afi_size(test->afi); + + if (X509v3_addr_get_range(aor, test->afi, min, max, sizeof min) != + afi_len) { + fprintf(stderr, "%s: \"%s\" X509v3_addr_get_range failed\n", + __func__, test->description); + goto err; + } + + /* + * Check that min and max match expectations. + */ + + if (memcmp(min, test->min, afi_len) != 0) { + memcmp_failed |= 1; + report_hexdump(__func__, test->description, "memcmp min failed", + test->min, afi_len, min, afi_len); + } + if (memcmp(max, test->max, afi_len) != 0) { + memcmp_failed |= 1; + report_hexdump(__func__, test->description, "memcmp max failed", + test->max, afi_len, max, afi_len); + } + if (memcmp_failed) + goto err; + + /* + * Now turn the parsed IPAddressOrRange back into DER and check that + * it matches the DER in the test case. + */ + + out = NULL; + if ((out_len = i2d_IPAddressOrRange(aor, &out)) <= 0) { + fprintf(stderr, "%s: \"%s\" i2d_IPAddressOrRange failed\n", + __func__, test->description); + goto err; + } + + memcmp_failed = (size_t)out_len != test->der_len; + if (!memcmp_failed) + memcmp_failed = memcmp(test->der, out, out_len); + + if (memcmp_failed) { + report_hexdump(__func__, test->description, "memcmp DER failed", + test->der, test->der_len, out, out_len); + goto err; + } + + failed = 0; + err: + IPAddressOrRange_free(aor); + free(out); + + return failed; +} + +static int +run_IPAddressOrRange_tests(void) +{ + size_t i; + int failed = 0; + + for (i = 0; i < N_IPADDRESSORRANGE_TESTS; i++) + failed |= + test_IPAddressOrRange(&IPAddressOrRange_test_data[i]); + + return failed; +} + +/* + * XXX: These should really be part of the public API... + */ +static IPAddrBlocks *IPAddrBlocks_new(void); +static void IPAddrBlocks_free(IPAddrBlocks *addr); +static __unused IPAddrBlocks *d2i_IPAddrBlocks(IPAddrBlocks **addrs, + const unsigned char **in, long len); +static int i2d_IPAddrBlocks(IPAddrBlocks *addrs, unsigned char **out); + +static IPAddrBlocks * +IPAddrBlocks_new(void) +{ + IPAddrBlocks *addrs; + + /* + * XXX The comparison function IPAddressFamily_cmp() isn't public. + * Start with the default and exploit a side effect of the lovely API + * which helpfully sets the correct function in a few places. Let's + * use the cheapest and easiest to reach one. + */ + if ((addrs = sk_IPAddressFamily_new_null()) == NULL) + return NULL; + if (!X509v3_addr_canonize(addrs)) { + IPAddrBlocks_free(addrs); + return NULL; + } + + return addrs; +} + +static void +IPAddrBlocks_free(IPAddrBlocks *addr) +{ + sk_IPAddressFamily_pop_free(addr, IPAddressFamily_free); +} + +/* + * We want {d2i,i2d}_IPAddrBlocks() to play with the DER of the extension. + * These don't exist, so we have to implement them ourselves. IPAddrBlocks_it + * isn't public, so we need to fetch it from the library. We cache it in a + * static variable to avoid the cost of a binary search through all supported + * extensions on each call. + */ + +static const ASN1_ITEM_EXP * +get_IPAddrBlocks_it(void) +{ + static const ASN1_ITEM_EXP *my_IPAddrBlocks_it; + const X509V3_EXT_METHOD *v3_addr; + + if (my_IPAddrBlocks_it != NULL) + return my_IPAddrBlocks_it; + + if ((v3_addr = X509V3_EXT_get_nid(NID_sbgp_ipAddrBlock)) == NULL) { + fprintf(stderr, "could not get v3_addr\n"); + return NULL; + } + + my_IPAddrBlocks_it = v3_addr->it; + + return my_IPAddrBlocks_it; +} + +static __unused IPAddrBlocks * +d2i_IPAddrBlocks(IPAddrBlocks **addrs, const unsigned char **in, long len) +{ + const ASN1_ITEM_EXP *my_IPAddrBlocks_it = get_IPAddrBlocks_it(); + + if (my_IPAddrBlocks_it == NULL) + return NULL; + + return (IPAddrBlocks *)ASN1_item_d2i((ASN1_VALUE **)addrs, in, len, + my_IPAddrBlocks_it); +} + +static int +i2d_IPAddrBlocks(IPAddrBlocks *addrs, unsigned char **out) +{ + const ASN1_ITEM_EXP *my_IPAddrBlocks_it = get_IPAddrBlocks_it(); + + if (my_IPAddrBlocks_it == NULL) + return -1; + + return ASN1_item_i2d((ASN1_VALUE *)addrs, out, my_IPAddrBlocks_it); +} + +struct ipv4_prefix { + unsigned char addr[4]; + size_t addr_len; + size_t prefix_len; +}; + +struct ipv4_range { + unsigned char min[4]; + unsigned char max[4]; +}; + +union ipv4_choice { + struct ipv4_prefix prefix; + struct ipv4_range range; +}; + +struct ipv6_prefix { + unsigned char addr[16]; + size_t addr_len; + size_t prefix_len; +}; + +struct ipv6_range { + unsigned char min[16]; + unsigned char max[16]; +}; + +union ipv6_choice { + struct ipv6_prefix prefix; + struct ipv6_range range; +}; + +enum choice_type { + choice_prefix, + choice_range, + choice_inherit, + choice_last, +}; + +union ip { + union ipv4_choice ipv4; + union ipv6_choice ipv6; +}; + +enum safi { + safi_none, + safi_unicast, + safi_multicast, +}; + +struct ip_addr_block { + unsigned int afi; + enum safi safi; + enum choice_type type; + union ip addr; +}; + +struct build_addr_block_test_data { + char *description; + struct ip_addr_block addrs[16]; + char der[128]; + size_t der_len; + int memcmp_fails; + int is_canonical; + int inherits; + unsigned int afis[4]; + int afi_len; +}; + +struct build_addr_block_test_data build_addr_block_tests[] = { + { + .description = "RFC 3779, Appendix B, example 1", + .addrs = { + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 0, 32, + }, + .addr_len = 3, + .prefix_len = 20, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 0, 64, + }, + .addr_len = 3, + .prefix_len = 24, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 1, + }, + .addr_len = 2, + .prefix_len = 16, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 2, 48, + }, + .addr_len = 3, + .prefix_len = 20, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 2, 64, + }, + .addr_len = 3, + .prefix_len = 24, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 3, + }, + .addr_len = 2, + .prefix_len = 16, + }, + }, + { + .afi = IANA_AFI_IPV6, + .safi = safi_none, + .type = choice_inherit, + }, + { + .type = choice_last, + }, + }, + .der = { + 0x30, 0x35, 0x30, 0x2b, 0x04, 0x03, 0x00, 0x01, + 0x01, 0x30, 0x24, 0x03, 0x04, 0x04, 0x0a, 0x00, + 0x20, 0x03, 0x04, 0x00, 0x0a, 0x00, 0x40, 0x03, + 0x03, 0x00, 0x0a, 0x01, 0x30, 0x0c, 0x03, 0x04, + 0x04, 0x0a, 0x02, 0x30, 0x03, 0x04, 0x00, 0x0a, + 0x02, 0x40, 0x03, 0x03, 0x00, 0x0a, 0x03, 0x30, + 0x06, 0x04, 0x02, 0x00, 0x02, 0x05, 0x00, + }, + .der_len = 55, + .is_canonical = 0, + .inherits = 1, + .afis = { + IANA_AFI_IPV4, IANA_AFI_IPV6, + }, + .afi_len = 2, + }, + { + .description = "RFC 3779, Appendix B, example 1 canonical", + .addrs = { + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 0, 32, + }, + .addr_len = 3, + .prefix_len = 20, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 0, 64, + }, + .addr_len = 3, + .prefix_len = 24, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 1, + }, + .addr_len = 2, + .prefix_len = 16, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_range, + .addr.ipv4.range = { + .min = { + 10, 2, 48, 00, + }, + .max = { + 10, 2, 64, 255, + }, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, 3, + }, + .addr_len = 2, + .prefix_len = 16, + }, + }, + { + .afi = IANA_AFI_IPV6, + .safi = safi_none, + .type = choice_inherit, + }, + { + .type = choice_last, + }, + }, + .der = { + 0x30, 0x35, 0x30, 0x2b, 0x04, 0x03, 0x00, 0x01, + 0x01, 0x30, 0x24, 0x03, 0x04, 0x04, 0x0a, 0x00, + 0x20, 0x03, 0x04, 0x00, 0x0a, 0x00, 0x40, 0x03, + 0x03, 0x00, 0x0a, 0x01, 0x30, 0x0c, 0x03, 0x04, + 0x04, 0x0a, 0x02, 0x30, 0x03, 0x04, 0x00, 0x0a, + 0x02, 0x40, 0x03, 0x03, 0x00, 0x0a, 0x03, 0x30, + 0x06, 0x04, 0x02, 0x00, 0x02, 0x05, 0x00, + }, + .der_len = 55, + .is_canonical = 1, + .inherits = 1, + .afis = { + IANA_AFI_IPV4, IANA_AFI_IPV6, + }, + .afi_len = 2, + }, + { + .description = "RFC 3779, Appendix B, example 2", + .addrs = { + { + .afi = IANA_AFI_IPV6, + .safi = safi_none, + .type = choice_prefix, + .addr.ipv6.prefix = { + .addr = { + 0x20, 0x01, 0x00, 0x00, + 0x00, 0x02, + }, + .addr_len = 6, + .prefix_len = 48, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 10, + }, + .addr_len = 1, + .prefix_len = 8, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_unicast, + .type = choice_prefix, + .addr.ipv4.prefix = { + .addr = { + 172, 16, + }, + .addr_len = 2, + .prefix_len = 12, + }, + }, + { + .afi = IANA_AFI_IPV4, + .safi = safi_multicast, + .type = choice_inherit, + }, + { + .type = choice_last, + }, + }, + .der = { + 0x30, 0x2c, 0x30, 0x10, 0x04, 0x03, 0x00, 0x01, + 0x01, 0x30, 0x09, 0x03, 0x02, 0x00, 0x0a, 0x03, + 0x03, 0x04, 0xac, 0x10, 0x30, 0x07, 0x04, 0x03, + 0x00, 0x01, 0x02, 0x05, 0x00, 0x30, 0x0f, 0x04, + 0x02, 0x00, 0x02, 0x30, 0x09, 0x03, 0x07, 0x00, + 0x20, 0x01, 0x00, 0x00, 0x00, 0x02, + }, + .der_len = 46, + .is_canonical = 0, + .inherits = 1, + .afis = { + IANA_AFI_IPV4, IANA_AFI_IPV4, + }, + .afi_len = 2, + }, + { + .description = "Range should be prefix 127/8", + .addrs = { + { + .afi = IANA_AFI_IPV4, + .safi = safi_none, + .type = choice_range, + .addr.ipv4.range = { + .min = { + 127, 0, 0, 0, + }, + .max = { + 127, 255, 255, 255, + }, + }, + }, + { + .type = choice_last, + }, + }, + .der = { + 0x30, 0x0c, 0x30, 0x0a, 0x04, 0x02, 0x00, 0x01, + 0x30, 0x04, 0x03, 0x02, 0x00, 0x7f, + }, + .der_len = 14, + .memcmp_fails = 1, + .is_canonical = 1, + .inherits = 0, + .afis = { + IANA_AFI_IPV4, + }, + .afi_len = 1, + }, +}; + +const size_t N_BUILD_ADDR_BLOCK_TESTS = + sizeof(build_addr_block_tests) / sizeof(build_addr_block_tests[0]); + +static unsigned int * +addr_block_get_safi(const struct ip_addr_block *addr) +{ + static unsigned int safi; + + switch (addr->safi) { + case safi_none: + return NULL; + case safi_unicast: + safi = 1; + break; + case safi_multicast: + safi = 2; + break; + } + + return &safi; +} + +static int +addr_block_add_ipv4_addr(IPAddrBlocks *block, enum choice_type type, + union ipv4_choice *ipv4, unsigned int *safi) +{ + switch (type) { + case choice_prefix: + return X509v3_addr_add_prefix(block, IANA_AFI_IPV4, safi, + ipv4->prefix.addr, ipv4->prefix.prefix_len); + case choice_range: + return X509v3_addr_add_range(block, IANA_AFI_IPV4, safi, + ipv4->range.min, ipv4->range.max); + case choice_inherit: + return X509v3_addr_add_inherit(block, IANA_AFI_IPV4, safi); + case choice_last: + default: + return 0; + } +} + +static int +addr_block_add_ipv6_addr(IPAddrBlocks *block, enum choice_type type, + union ipv6_choice *ipv6, unsigned int *safi) +{ + switch (type) { + case choice_prefix: + return X509v3_addr_add_prefix(block, IANA_AFI_IPV6, safi, + ipv6->prefix.addr, ipv6->prefix.prefix_len); + case choice_range: + return X509v3_addr_add_range(block, IANA_AFI_IPV6, safi, + ipv6->range.min, ipv6->range.max); + case choice_inherit: + return X509v3_addr_add_inherit(block, IANA_AFI_IPV6, safi); + case choice_last: + default: + return 0; + } +} + +static int +addr_block_add_addrs(IPAddrBlocks *block, struct ip_addr_block addrs[]) +{ + struct ip_addr_block *addr; + unsigned int *safi; + + for (addr = &addrs[0]; addr->type != choice_last; addr++) { + safi = addr_block_get_safi(addr); + switch (addr->afi) { + case IANA_AFI_IPV4: + if (!addr_block_add_ipv4_addr(block, addr->type, + &addr->addr.ipv4, safi)) + return 0; + break; + case IANA_AFI_IPV6: + if (!addr_block_add_ipv6_addr(block, addr->type, + &addr->addr.ipv6, safi)) + return 0; + break; + default: + fprintf(stderr, "%s: corrupt test data", __func__); + exit(1); + } + } + + return 1; +} + +static int +build_addr_block_test(struct build_addr_block_test_data *test) +{ + IPAddrBlocks *addrs = NULL; + unsigned char *out = NULL; + int out_len; + int i; + int memcmp_failed = 1; + int failed = 1; + + if ((addrs = IPAddrBlocks_new()) == NULL) + goto err; + + if (!addr_block_add_addrs(addrs, test->addrs)) + goto err; + + if (X509v3_addr_is_canonical(addrs) != test->is_canonical) { + fprintf(stderr, "%s: \"%s\" X509v3_addr_is_canonical not %d\n", + __func__, test->description, test->is_canonical); + goto err; + } + + if (!X509v3_addr_canonize(addrs)) { + fprintf(stderr, "%s: \"%s\" failed to canonize\n", + __func__, test->description); + goto err; + } + + if (!X509v3_addr_is_canonical(addrs)) { + fprintf(stderr, "%s: \"%s\" canonization wasn't canonical\n", + __func__, test->description); + goto err; + } + + if ((out_len = i2d_IPAddrBlocks(addrs, &out)) <= 0) { + fprintf(stderr, "%s: \"%s\" i2d_IPAddrBlocks failed\n", + __func__, test->description); + goto err; + } + + memcmp_failed = (size_t)out_len != test->der_len; + if (!memcmp_failed) + memcmp_failed = memcmp(out, test->der, test->der_len); + if (memcmp_failed) { + report_hexdump(__func__, test->description, "memcmp DER failed", + test->der, test->der_len, out, out_len); + if (!test->memcmp_fails) + goto err; + fprintf(stderr, "ignoring expected failure\n"); + } + + if (X509v3_addr_inherits(addrs) != test->inherits) { + fprintf(stderr, "%s: \"%s\" X509v3_addr_inherits not %d\n", + __func__, test->description, test->inherits); + goto err; + } + + for (i = 0; i < sk_IPAddressFamily_num(addrs) && i < test->afi_len; i++) { + IPAddressFamily *family; + unsigned int afi; + + family = sk_IPAddressFamily_value(addrs, i); + + if ((afi = X509v3_addr_get_afi(family)) == 0) { + fprintf(stderr, "%s: \"%s\" X509v3_addr_get_afi" + " failed\n", __func__, test->description); + goto err; + } + if (test->afis[i] != afi){ + fprintf(stderr, "%s: \"%s\" afi[%d] mismatch. " + "want: %u, got: %u\n", __func__, + test->description, i, test->afis[i], afi); + goto err; + } + } + if (i != test->afi_len) { + fprintf(stderr, "%s: \"%s\" checked %d afis, expected %d\n", + __func__, test->description, i, test->afi_len); + goto err; + } + + failed = 0; + + err: + IPAddrBlocks_free(addrs); + free(out); + + return failed; +} + +static int +run_IPAddrBlock_tests(void) +{ + size_t i; + int failed = 0; + + for (i = 0; i < N_BUILD_ADDR_BLOCK_TESTS; i++) + failed |= build_addr_block_test(&build_addr_block_tests[i]); + + return failed; +} + +struct asid_or_range { + int type; + int inherit; + const unsigned char *min; + const unsigned char *max; +}; + +struct ASIdentifiers_build_test { + const char *description; + int should_build; + int inherits; + int canonical; + int should_canonize; + struct asid_or_range delegations[8]; + const unsigned char der[128]; + size_t der_len; +}; + +/* Sentinel value used for marking the end of the delegations table. */ +#define V3_ASID_END -1 + +const struct ASIdentifiers_build_test ASIdentifiers_build_data[] = { + { + .description = "RFC 3779, Appendix C", + .should_build = 1, + .inherits = 1, + .canonical = 1, + .delegations = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "135", + .max = NULL, + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "3000", + .max = "3999", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "5001", + .max = NULL, + }, + { + .type = V3_ASID_RDI, + .inherit = 1, + .min = NULL, + .max = NULL, + }, + { + .type = V3_ASID_END, + }, + }, + .der = { + 0x30, 0x1a, 0xa0, 0x14, 0x30, 0x12, 0x02, 0x02, + 0x00, 0x87, 0x30, 0x08, 0x02, 0x02, 0x0b, 0xb8, + 0x02, 0x02, 0x0f, 0x9f, 0x02, 0x02, 0x13, 0x89, + 0xa1, 0x02, 0x05, 0x00, + }, + .der_len = 28, + }, + { + .description = "RFC 3779, Appendix C without rdi", + .should_build = 1, + .inherits = 0, + .canonical = 1, + .delegations = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "135", + .max = NULL, + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "3000", + .max = "3999", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "5001", + .max = NULL, + }, + { + .type = V3_ASID_END, + }, + }, + .der = { + 0x30, 0x16, 0xa0, 0x14, 0x30, 0x12, 0x02, 0x02, + 0x00, 0x87, 0x30, 0x08, 0x02, 0x02, 0x0b, 0xb8, + 0x02, 0x02, 0x0f, 0x9f, 0x02, 0x02, 0x13, 0x89, + }, + .der_len = 24, + }, + { + .description = "RFC 3779, Appendix C variant", + .should_build = 1, + .inherits = 0, + .canonical = 1, + .delegations = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "135", + .max = NULL, + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "3000", + .max = "3999", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "5001", + .max = NULL, + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "135", + .max = NULL, + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "3000", + .max = "3999", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "5001", + .max = NULL, + }, + { + .type = V3_ASID_END, + }, + }, + .der = { + 0x30, 0x2c, 0xa0, 0x14, 0x30, 0x12, 0x02, 0x02, + 0x00, 0x87, 0x30, 0x08, 0x02, 0x02, 0x0b, 0xb8, + 0x02, 0x02, 0x0f, 0x9f, 0x02, 0x02, 0x13, 0x89, + 0xa1, 0x14, 0x30, 0x12, 0x02, 0x02, 0x00, 0x87, + 0x30, 0x08, 0x02, 0x02, 0x0b, 0xb8, 0x02, 0x02, + 0x0f, 0x9f, 0x02, 0x02, 0x13, 0x89, + }, + .der_len = 46, + }, + { + .description = "inherit only", + .should_build = 1, + .inherits = 1, + .canonical = 1, + .delegations = { + { + .type = V3_ASID_ASNUM, + .inherit = 1, + }, + { + .type = V3_ASID_RDI, + .inherit = 1, + }, + { + .type = V3_ASID_END, + }, + }, + .der = { + 0x30, 0x08, 0xa0, 0x02, 0x05, 0x00, 0xa1, 0x02, + 0x05, 0x00, + }, + .der_len = 10, + }, + { + .description = "adjacent unsorted ranges are merged", + .should_build = 1, + .inherits = 0, + .canonical = 0, + .should_canonize = 1, + .delegations = { + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "27", + .max = NULL, + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "28", + .max = "57", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "66", + .max = "68", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "58", + .max = "63", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "64", + .max = NULL, + }, + { + .type = V3_ASID_END, + }, + }, + .der = { + 0x30, 0x14, 0xa1, 0x12, 0x30, 0x10, 0x30, 0x06, + 0x02, 0x01, 0x1b, 0x02, 0x01, 0x40, 0x30, 0x06, + 0x02, 0x01, 0x42, 0x02, 0x01, 0x44, + }, + .der_len = 22, + }, + { + .description = "range of length 0", + .should_build = 1, + .inherits = 1, + .canonical = 1, + .should_canonize = 1, + .delegations = { + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "27", + .max = "27", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 1, + }, + { + .type = V3_ASID_END, + }, + }, + .der = { + 0x30, 0x10, 0xa0, 0x02, 0x05, 0x00, 0xa1, 0x0a, + 0x30, 0x08, 0x30, 0x06, 0x02, 0x01, 0x1b, 0x02, + 0x01, 0x1b, + }, + .der_len = 18, + }, + { + .description = "reversed range doesn't canonize", + .should_build = 1, + .inherits = 0, + .canonical = 0, + .should_canonize = 0, + .delegations = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "57", + .max = "42", + }, + { + .type = V3_ASID_END, + }, + }, + }, + { + .description = "overlapping ranges don't canonize", + .should_build = 1, + .inherits = 0, + .canonical = 0, + .should_canonize = 0, + .delegations = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "42", + .max = "57", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "57", + .max = "60", + }, + { + .type = V3_ASID_END, + }, + }, + }, + { + .description = "reversed interior range doesn't canonize", + .should_build = 1, + .inherits = 0, + .canonical = 0, + .should_canonize = 0, + .delegations = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "1", + .max = "2", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "57", + .max = "42", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "65523", + .max = "65535", + }, + { + .type = V3_ASID_END, + }, + }, + }, + { + .description = "can't inherit and add AS ids", + .should_build = 0, + .inherits = 0, + .canonical = 0, + .should_canonize = 0, + .delegations = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "1", + .max = "2", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 1, + }, + { + .type = V3_ASID_END, + }, + }, + }, + { + .description = "can't inherit and add rdis", + .should_build = 0, + .inherits = 0, + .canonical = 0, + .should_canonize = 0, + .delegations = { + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "1", + .max = "2", + }, + { + .type = V3_ASID_RDI, + .inherit = 1, + }, + { + .type = V3_ASID_END, + }, + }, + }, +}; + +const size_t N_ASIDENTIFIERS_BUILD_TESTS = + sizeof(ASIdentifiers_build_data) / sizeof(ASIdentifiers_build_data[0]); + +static int +add_as_delegation(ASIdentifiers *asid, const struct asid_or_range *delegation) +{ + ASN1_INTEGER *min = NULL, *max = NULL; + int ret = 0; + + if (delegation->inherit) + return X509v3_asid_add_inherit(asid, delegation->type); + + if ((min = s2i_ASN1_INTEGER(NULL, delegation->min)) == NULL) + goto err; + + if (delegation->max != NULL) { + if ((max = s2i_ASN1_INTEGER(NULL, delegation->max)) == NULL) + goto err; + } + + if (!X509v3_asid_add_id_or_range(asid, delegation->type, min, max)) + goto err; + min = NULL; + max = NULL; + + ret = 1; + + err: + ASN1_INTEGER_free(min); + ASN1_INTEGER_free(max); + + return ret; +} + +static ASIdentifiers * +build_asid(const struct asid_or_range delegations[]) +{ + ASIdentifiers *asid = NULL; + const struct asid_or_range *delegation; + + if ((asid = ASIdentifiers_new()) == NULL) + goto err; + + for (delegation = &delegations[0]; delegation->type != V3_ASID_END; + delegation++) { + if (!add_as_delegation(asid, delegation)) + goto err; + } + + return asid; + + err: + ASIdentifiers_free(asid); + return NULL; +} + +static int +build_asid_test(const struct ASIdentifiers_build_test *test) +{ + ASIdentifiers *asid = NULL; + unsigned char *out = NULL; + int out_len; + int memcmp_failed = 1; + int failed = 1; + + if ((asid = build_asid(test->delegations)) == NULL) { + if (!test->should_build) { + failed = 0; + return failed; + } + fprintf(stderr, "%s: \"%s\" failed to build\n", __func__, + test->description); + return failed; + } + + if (!test->canonical) { + if (X509v3_asid_is_canonical(asid)) { + fprintf(stderr, "%s: \"%s\" shouldn't be canonical\n", + __func__, test->description); + goto err; + } + if (X509v3_asid_canonize(asid) != test->should_canonize) { + fprintf(stderr, "%s: \"%s\" failed to canonize\n", + __func__, test->description); + goto err; + } + if (!test->should_canonize) { + failed = 0; + goto err; + } + } + + /* + * Verify that asid is in canonical form before converting it to DER. + */ + if (!X509v3_asid_is_canonical(asid)) { + fprintf(stderr, "%s: asid is not canonical\n", __func__); + goto err; + } + + /* + * Convert asid to DER and check that it matches expectations + */ + out = NULL; + if ((out_len = i2d_ASIdentifiers(asid, &out)) <= 0) { + fprintf(stderr, "%s: \"%s\" i2d_ASIdentifiers failed\n", + __func__, test->description); + goto err; + } + + + memcmp_failed = (size_t)out_len != test->der_len; + if (!memcmp_failed) + memcmp_failed = memcmp(out, test->der, test->der_len); + if (memcmp_failed) { + report_hexdump(__func__, test->description, "memcmp DER failed", + test->der, test->der_len, out, out_len); + goto err; + } + + /* + * Verify that asid inherits as expected + */ + if (X509v3_asid_inherits(asid) != test->inherits) { + fprintf(stderr, "%s: \"%s\" unexpected asid inherit %d\n", + __func__, test->description, test->inherits); + goto err; + } + + failed = 0; + + err: + free(out); + ASIdentifiers_free(asid); + + return failed; +} + +static int +run_ASIdentifiers_build_test(void) +{ + size_t i; + int failed = 0; + + for (i = 0; i < N_ASIDENTIFIERS_BUILD_TESTS; i++) + failed |= build_asid_test(&ASIdentifiers_build_data[i]); + + return failed; +} + +struct ASIdentifiers_subset_test { + const char *description; + struct asid_or_range delegationsA[8]; + struct asid_or_range delegationsB[8]; + int is_subset; + int is_subset_if_canonized; +}; + +/* + * XXX: X509v3_asid_subset() assumes that both asnum and rdi are present + * while they are both marked OPTIONAL in RFC 3779, 3.2.3... + */ +const struct ASIdentifiers_subset_test ASIdentifiers_subset_data[] = { + { + .description = "simple subset relation", + .delegationsA = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "2", + .max = "4", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "2", + .max = NULL, + }, + { + .type = V3_ASID_END, + }, + }, + .delegationsB = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "1", + .max = "5", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "1", + .max = "5", + }, + { + .type = V3_ASID_END, + }, + }, + .is_subset = 1, + .is_subset_if_canonized = 1, + }, + { + .description = "subset relation only after canonization", + .delegationsA = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "2", + .max = NULL, + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "3", + .max = "4", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "2", + .max = NULL, + }, + { + .type = V3_ASID_END, + }, + }, + .delegationsB = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "1", + .max = "3", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "4", + .max = "5", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "1", + .max = "5", + }, + { + .type = V3_ASID_END, + }, + }, + .is_subset = 0, + .is_subset_if_canonized = 1, + }, + { + .description = "no subset if A inherits", + .delegationsA = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "2", + .max = NULL, + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "3", + .max = "4", + }, + { + .type = V3_ASID_RDI, + .inherit = 1, + }, + { + .type = V3_ASID_END, + }, + }, + .delegationsB = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "1", + .max = "3", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "4", + .max = "5", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "1", + .max = "5", + }, + { + .type = V3_ASID_END, + }, + }, + .is_subset = 0, + .is_subset_if_canonized = 0, + }, + { + .description = "no subset if B inherits", + .delegationsA = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "2", + .max = NULL, + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "3", + .max = "4", + }, + { + .type = V3_ASID_RDI, + .inherit = 0, + .min = "5", + .max = NULL, + }, + { + .type = V3_ASID_END, + }, + }, + .delegationsB = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "1", + .max = "3", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "4", + .max = "5", + }, + { + .type = V3_ASID_RDI, + .inherit = 1, + }, + { + .type = V3_ASID_END, + }, + }, + .is_subset = 0, + .is_subset_if_canonized = 0, + }, + { + .description = "no subset if both inherit", + .delegationsA = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "2", + .max = NULL, + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "3", + .max = "4", + }, + { + .type = V3_ASID_RDI, + .inherit = 1, + }, + { + .type = V3_ASID_END, + }, + }, + .delegationsB = { + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "1", + .max = "3", + }, + { + .type = V3_ASID_ASNUM, + .inherit = 0, + .min = "4", + .max = "5", + }, + { + .type = V3_ASID_RDI, + .inherit = 1, + }, + { + .type = V3_ASID_END, + }, + }, + .is_subset = 0, + .is_subset_if_canonized = 0, + }, +}; + +const size_t N_ASIDENTIFIERS_SUBSET_TESTS = + sizeof(ASIdentifiers_build_data) / sizeof(ASIdentifiers_build_data[0]); + +static int +asid_subset_test(const struct ASIdentifiers_subset_test *test) +{ + ASIdentifiers *asidA = NULL, *asidB = NULL; + int failed = 0; + + if ((asidA = build_asid(test->delegationsA)) == NULL) + goto err; + if ((asidB = build_asid(test->delegationsB)) == NULL) + goto err; + + if (X509v3_asid_subset(asidA, asidB) != test->is_subset) { + fprintf(stderr, "%s: \"%s\" X509v3_asid_subset failed\n", + __func__, test->description); + failed = 1; + } + + if (!test->is_subset) { + if (!X509v3_asid_canonize(asidA)) + goto err; + if (!X509v3_asid_canonize(asidB)) + goto err; + if (X509v3_asid_subset(asidA, asidB) != + test->is_subset_if_canonized) { + fprintf(stderr, "%s: \"%s\" canonized subset failed\n", + __func__, test->description); + failed = 1; + } + } + + err: + ASIdentifiers_free(asidA); + ASIdentifiers_free(asidB); + + return failed; +} + +static int +run_ASIdentifiers_subset_test(void) +{ + size_t i; + int failed = 0; + + for (i = 0; i < N_ASIDENTIFIERS_SUBSET_TESTS; i++) + failed |= asid_subset_test(&ASIdentifiers_subset_data[i]); + + return failed; +} + +int +main(void) +{ + int failed = 0; + + failed |= run_IPAddressOrRange_tests(); + failed |= run_IPAddrBlock_tests(); + failed |= run_ASIdentifiers_build_test(); + failed |= run_ASIdentifiers_subset_test(); + + return failed; +} -- 2.20.1