Add initial test coverage for RFC 3779 code.
authortb <tb@openbsd.org>
Fri, 24 Dec 2021 03:00:37 +0000 (03:00 +0000)
committertb <tb@openbsd.org>
Fri, 24 Dec 2021 03:00:37 +0000 (03:00 +0000)
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 [new file with mode: 0644]
regress/lib/libcrypto/x509/rfc3779/rfc3779.c [new file with mode: 0644]

diff --git a/regress/lib/libcrypto/x509/rfc3779/Makefile b/regress/lib/libcrypto/x509/rfc3779/Makefile
new file mode 100644 (file)
index 0000000..3709471
--- /dev/null
@@ -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 <bsd.regress.mk>
diff --git a/regress/lib/libcrypto/x509/rfc3779/rfc3779.c b/regress/lib/libcrypto/x509/rfc3779/rfc3779.c
new file mode 100644 (file)
index 0000000..65bbf22
--- /dev/null
@@ -0,0 +1,1793 @@
+/*     $OpenBSD: rfc3779.c,v 1.1 2021/12/24 03:00:37 tb Exp $ */
+/*
+ * Copyright (c) 2021 Theo Buehler <tb@openbsd.org>
+ *
+ * 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 <stdio.h>
+#include <string.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/x509v3.h>
+
+#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;
+}