From e9b5d4af374b8c6dcad775059c5243b684e88357 Mon Sep 17 00:00:00 2001
From: tb <>
Date: Fri, 24 Dec 2021 03:00:37 +0000
Subject: 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.
---
 src/regress/lib/libcrypto/x509/rfc3779/Makefile  |   11 +
 src/regress/lib/libcrypto/x509/rfc3779/rfc3779.c | 1793 ++++++++++++++++++++++
 2 files changed, 1804 insertions(+)
 create mode 100644 src/regress/lib/libcrypto/x509/rfc3779/Makefile
 create mode 100644 src/regress/lib/libcrypto/x509/rfc3779/rfc3779.c

diff --git a/src/regress/lib/libcrypto/x509/rfc3779/Makefile b/src/regress/lib/libcrypto/x509/rfc3779/Makefile
new file mode 100644
index 0000000000..3709471c8f
--- /dev/null
+++ b/src/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 <bsd.regress.mk>
diff --git a/src/regress/lib/libcrypto/x509/rfc3779/rfc3779.c b/src/regress/lib/libcrypto/x509/rfc3779/rfc3779.c
new file mode 100644
index 0000000000..65bbf2294b
--- /dev/null
+++ b/src/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 <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;
+}
-- 
cgit v1.2.3-55-g6feb