Index | Thread | Search

From:
Theo Buehler <tb@theobuehler.org>
Subject:
rpki-client: move most rfc 3779 parsing code
To:
tech@openbsd.org
Date:
Wed, 29 Apr 2026 08:43:23 +0200

Download raw body.

Thread
  • Theo Buehler:

    rpki-client: move most rfc 3779 parsing code

This diff moves most of the RFC 3779 extension unpacking into ip.c and
as.c and renames the two sbgp_* cert extension handler into cert_* for
consistency inside cert.c. Two static cert_*as*() helpers stay there.

The cert.c code is complex enough without them, and ip.c and as.c exist
for this reason. The sbgp_* prototypes in extern.h are already grouped
with the ip_* and as_* functions rather than the cert_* functions.

The bulk of the diff is a plain move of code I reworked when migrating
rpki-client to templated ASN.1. There is perhaps an argument that the
functions should remain close to each other due to their similarity.
I can't remember that this ever helped me.

I dislike how append_as() and append_ip() rely on the caller many
levels down to have allocated enough room, but that's for another
round of bikeshedding.

Index: as.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/as.c,v
diff -u -p -r1.18 as.c
--- as.c	9 Apr 2026 07:47:31 -0000	1.18
+++ as.c	29 Apr 2026 06:06:46 -0000
@@ -1,6 +1,7 @@
 /*	$OpenBSD: as.c,v 1.18 2026/04/09 07:47:31 tb Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2022 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
@@ -15,6 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <assert.h>
 #include <err.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -154,4 +156,180 @@ as_warn(const char *fn, const char *msg,
 		warnx("%s: corrupt cert", fn);
 		break;
 	}
+}
+
+/*
+ * Append an AS identifier structure to our list of results.
+ * Makes sure that the identifiers do not overlap or improperly inherit
+ * as defined by RFC 3779 section 3.3.
+ */
+static int
+append_as(const char *fn, struct cert_as *ases, size_t *num_ases,
+    const struct cert_as *as)
+{
+	if (!as_check_overlap(as, fn, ases, *num_ases, 0))
+		return 0;
+	ases[(*num_ases)++] = *as;
+	return 1;
+}
+
+/*
+ * Parse a range of AS identifiers as in 3.2.3.8.
+ * Returns zero on failure, non-zero on success.
+ */
+int
+sbgp_as_range(const char *fn, struct cert_as *ases, size_t *num_ases,
+    const ASRange *range)
+{
+	struct cert_as		 as;
+
+	memset(&as, 0, sizeof(struct cert_as));
+	as.type = CERT_AS_RANGE;
+
+	if (!as_id_parse(range->min, &as.range.min)) {
+		warnx("%s: RFC 3779 section 3.2.3.8 (via RFC 1930): "
+		    "malformed AS identifier", fn);
+		return 0;
+	}
+
+	if (!as_id_parse(range->max, &as.range.max)) {
+		warnx("%s: RFC 3779 section 3.2.3.8 (via RFC 1930): "
+		    "malformed AS identifier", fn);
+		return 0;
+	}
+
+	if (as.range.max == as.range.min) {
+		warnx("%s: RFC 3379 section 3.2.3.8: ASRange: "
+		    "range is singular", fn);
+		return 0;
+	} else if (as.range.max < as.range.min) {
+		warnx("%s: RFC 3379 section 3.2.3.8: ASRange: "
+		    "range is out of order", fn);
+		return 0;
+	}
+
+	return append_as(fn, ases, num_ases, &as);
+}
+
+/*
+ * Parse an entire 3.2.3.10 integer type.
+ */
+int
+sbgp_as_id(const char *fn, struct cert_as *ases, size_t *num_ases,
+    const ASN1_INTEGER *i)
+{
+	struct cert_as	 as;
+
+	memset(&as, 0, sizeof(struct cert_as));
+	as.type = CERT_AS_ID;
+
+	if (!as_id_parse(i, &as.id)) {
+		warnx("%s: RFC 3779 section 3.2.3.10 (via RFC 1930): "
+		    "malformed AS identifier", fn);
+		return 0;
+	}
+	if (as.id == 0) {
+		warnx("%s: RFC 3779 section 3.2.3.10 (via RFC 1930): "
+		    "AS identifier zero is reserved", fn);
+		return 0;
+	}
+
+	return append_as(fn, ases, num_ases, &as);
+}
+
+static int
+sbgp_as_inherit(const char *fn, struct cert_as *ases, size_t *num_ases)
+{
+	struct cert_as as;
+
+	memset(&as, 0, sizeof(struct cert_as));
+	as.type = CERT_AS_INHERIT;
+
+	return append_as(fn, ases, num_ases, &as);
+}
+
+int
+sbgp_parse_asids(const char *fn, const ASIdentifiers *asidentifiers,
+    struct cert_as **out_as, size_t *out_num_ases)
+{
+	const ASIdOrRanges	*aors = NULL;
+	struct cert_as		*as = NULL;
+	size_t			 num_ases = 0, num;
+	int			 i;
+
+	assert(*out_as == NULL && *out_num_ases == 0);
+
+	if (asidentifiers->rdi != NULL) {
+		warnx("%s: RFC 6487 section 4.8.11: autonomousSysIds: "
+		    "should not have RDI values", fn);
+		goto out;
+	}
+
+	if (asidentifiers->asnum == NULL) {
+		warnx("%s: RFC 6487 section 4.8.11: autonomousSysIds: "
+		    "no AS number resource set", fn);
+		goto out;
+	}
+
+	switch (asidentifiers->asnum->type) {
+	case ASIdentifierChoice_inherit:
+		num = 1;
+		break;
+	case ASIdentifierChoice_asIdsOrRanges:
+		aors = asidentifiers->asnum->u.asIdsOrRanges;
+		num = sk_ASIdOrRange_num(aors);
+		break;
+	default:
+		warnx("%s: RFC 3779 section 3.2.3.2: ASIdentifierChoice: "
+		    "unknown type %d", fn, asidentifiers->asnum->type);
+		goto out;
+	}
+
+	if (num == 0) {
+		warnx("%s: RFC 6487 section 4.8.11: empty asIdsOrRanges", fn);
+		goto out;
+	}
+	if (num >= MAX_AS_SIZE) {
+		warnx("%s: too many AS number entries: limit %d",
+		    fn, MAX_AS_SIZE);
+		goto out;
+	}
+	as = calloc(num, sizeof(struct cert_as));
+	if (as == NULL)
+		err(1, NULL);
+
+	if (aors == NULL) {
+		if (!sbgp_as_inherit(fn, as, &num_ases))
+			goto out;
+	}
+
+	for (i = 0; i < sk_ASIdOrRange_num(aors); i++) {
+		const ASIdOrRange *aor;
+
+		aor = sk_ASIdOrRange_value(aors, i);
+		switch (aor->type) {
+		case ASIdOrRange_id:
+			if (!sbgp_as_id(fn, as, &num_ases, aor->u.id))
+				goto out;
+			break;
+		case ASIdOrRange_range:
+			if (!sbgp_as_range(fn, as, &num_ases, aor->u.range))
+				goto out;
+			break;
+		default:
+			warnx("%s: RFC 3779 section 3.2.3.5: ASIdOrRange: "
+			    "unknown type %d", fn, aor->type);
+			goto out;
+		}
+	}
+
+	*out_as = as;
+	*out_num_ases = num_ases;
+
+	return 1;
+
+ out:
+	free(as);
+
+	return 0;
 }
Index: cert.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v
diff -u -p -r1.232 cert.c
--- cert.c	7 Apr 2026 10:59:19 -0000	1.232
+++ cert.c	8 Apr 2026 06:08:51 -0000
@@ -1024,217 +1024,12 @@ cert_policies(const char *fn, struct cer
 }
 
 /*
- * Append an IP address structure to our list of results, ensuring there is
- * at most one inheritance marker per AFI and no overlapping ranges.
- */
-static int
-append_ip(const char *fn, struct cert_ip *ips, size_t *num_ips,
-    const struct cert_ip *ip)
-{
-	if (!ip_addr_check_overlap(ip, fn, ips, *num_ips, 0))
-		return 0;
-	ips[(*num_ips)++] = *ip;
-	return 1;
-}
-
-/*
- * Construct a RFC 3779 2.2.3.8 range from its bit string.
- * Returns zero on failure, non-zero on success.
- */
-int
-sbgp_addr(const char *fn, struct cert_ip *ips, size_t *num_ips, enum afi afi,
-    const ASN1_BIT_STRING *bs)
-{
-	struct cert_ip	ip;
-
-	memset(&ip, 0, sizeof(struct cert_ip));
-
-	ip.afi = afi;
-	ip.type = CERT_IP_ADDR;
-
-	if (!ip_addr_parse(bs, afi, fn, &ip.ip)) {
-		warnx("%s: RFC 3779 section 2.2.3.8: IPAddress: "
-		    "invalid IP address", fn);
-		return 0;
-	}
-
-	if (!ip_cert_compose_ranges(&ip)) {
-		warnx("%s: RFC 3779 section 2.2.3.8: IPAddress: "
-		    "IP address range reversed", fn);
-		return 0;
-	}
-
-	return append_ip(fn, ips, num_ips, &ip);
-}
-
-/*
- * Parse RFC 3779 2.2.3.9 range of addresses.
- * Returns zero on failure, non-zero on success.
- */
-int
-sbgp_addr_range(const char *fn, struct cert_ip *ips, size_t *num_ips,
-    enum afi afi, const IPAddressRange *range)
-{
-	struct cert_ip	ip;
-
-	memset(&ip, 0, sizeof(struct cert_ip));
-
-	ip.afi = afi;
-	ip.type = CERT_IP_RANGE;
-
-	if (!ip_addr_parse(range->min, afi, fn, &ip.range.min)) {
-		warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: "
-		    "invalid IP address", fn);
-		return 0;
-	}
-
-	if (!ip_addr_parse(range->max, afi, fn, &ip.range.max)) {
-		warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: "
-		    "invalid IP address", fn);
-		return 0;
-	}
-
-	if (!ip_cert_compose_ranges(&ip)) {
-		warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: "
-		    "IP address range reversed", fn);
-		return 0;
-	}
-
-	return append_ip(fn, ips, num_ips, &ip);
-}
-
-static int
-sbgp_addr_inherit(const char *fn, struct cert_ip *ips, size_t *num_ips,
-    enum afi afi)
-{
-	struct cert_ip	ip;
-
-	memset(&ip, 0, sizeof(struct cert_ip));
-
-	ip.afi = afi;
-	ip.type = CERT_IP_INHERIT;
-
-	return append_ip(fn, ips, num_ips, &ip);
-}
-
-int
-sbgp_parse_ipaddrblocks(const char *fn, const IPAddrBlocks *addrs,
-    struct cert_ip **out_ips, size_t *out_num_ips)
-{
-	const IPAddressFamily	*af;
-	const IPAddressOrRanges	*aors;
-	const IPAddressOrRange	*aor;
-	enum afi		 afi;
-	struct cert_ip		*ips = NULL;
-	size_t			 num_ips = 0, num;
-	int			 ipv4_seen = 0, ipv6_seen = 0;
-	int			 i, j, addrsz;
-
-	assert(*out_ips == NULL && *out_num_ips == 0);
-
-	addrsz = sk_IPAddressFamily_num(addrs);
-	if (addrsz != 1 && addrsz != 2) {
-		warnx("%s: RFC 6487 section 4.8.10: unexpected number of "
-		    "ipAddrBlocks (got %d, expected 1 or 2)", fn, addrsz);
-		goto out;
-	}
-
-	for (i = 0; i < addrsz; i++) {
-		af = sk_IPAddressFamily_value(addrs, i);
-
-		switch (af->ipAddressChoice->type) {
-		case IPAddressChoice_inherit:
-			aors = NULL;
-			num = num_ips + 1;
-			break;
-		case IPAddressChoice_addressesOrRanges:
-			aors = af->ipAddressChoice->u.addressesOrRanges;
-			num = num_ips + sk_IPAddressOrRange_num(aors);
-			break;
-		default:
-			warnx("%s: RFC 3779: IPAddressChoice: unknown type %d",
-			    fn, af->ipAddressChoice->type);
-			goto out;
-		}
-		if (num == num_ips) {
-			warnx("%s: RFC 6487 section 4.8.10: "
-			    "empty ipAddressesOrRanges", fn);
-			goto out;
-		}
-
-		if (num >= MAX_IP_SIZE)
-			goto out;
-		ips = recallocarray(ips, num_ips, num, sizeof(struct cert_ip));
-		if (ips == NULL)
-			err(1, NULL);
-
-		if (!ip_addr_afi_parse(fn, af->addressFamily, &afi)) {
-			warnx("%s: RFC 3779: invalid AFI", fn);
-			goto out;
-		}
-
-		switch (afi) {
-		case AFI_IPV4:
-			if (ipv4_seen++ > 0) {
-				warnx("%s: RFC 6487 section 4.8.10: "
-				    "IPv4 appears twice", fn);
-				goto out;
-			}
-			break;
-		case AFI_IPV6:
-			if (ipv6_seen++ > 0) {
-				warnx("%s: RFC 6487 section 4.8.10: "
-				    "IPv6 appears twice", fn);
-				goto out;
-			}
-			break;
-		}
-
-		if (aors == NULL) {
-			if (!sbgp_addr_inherit(fn, ips, &num_ips, afi))
-				goto out;
-			continue;
-		}
-
-		for (j = 0; j < sk_IPAddressOrRange_num(aors); j++) {
-			aor = sk_IPAddressOrRange_value(aors, j);
-			switch (aor->type) {
-			case IPAddressOrRange_addressPrefix:
-				if (!sbgp_addr(fn, ips, &num_ips, afi,
-				    aor->u.addressPrefix))
-					goto out;
-				break;
-			case IPAddressOrRange_addressRange:
-				if (!sbgp_addr_range(fn, ips, &num_ips, afi,
-				    aor->u.addressRange))
-					goto out;
-				break;
-			default:
-				warnx("%s: RFC 3779: IPAddressOrRange: "
-				    "unknown type %d", fn, aor->type);
-				goto out;
-			}
-		}
-	}
-
-	*out_ips = ips;
-	*out_num_ips = num_ips;
-
-	return 1;
-
- out:
-	free(ips);
-
-	return 0;
-}
-
-/*
  * Parse an IP Resources X.509v3 extension, RFC 6487 4.8.10, with
  * syntax documented in RFC 3779 starting in section 2.2.
  * Returns zero on failure, non-zero on success.
  */
 static int
-sbgp_ipaddrblocks(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
+cert_ipaddrblocks(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
 {
 	IPAddrBlocks	*addrs = NULL;
 	int		 rc = 0;
@@ -1266,96 +1061,6 @@ sbgp_ipaddrblocks(const char *fn, struct
 	return rc;
 }
 
-/*
- * Append an AS identifier structure to our list of results.
- * Makes sure that the identifiers do not overlap or improperly inherit
- * as defined by RFC 3779 section 3.3.
- */
-static int
-append_as(const char *fn, struct cert_as *ases, size_t *num_ases,
-    const struct cert_as *as)
-{
-	if (!as_check_overlap(as, fn, ases, *num_ases, 0))
-		return 0;
-	ases[(*num_ases)++] = *as;
-	return 1;
-}
-
-/*
- * Parse a range of AS identifiers as in 3.2.3.8.
- * Returns zero on failure, non-zero on success.
- */
-int
-sbgp_as_range(const char *fn, struct cert_as *ases, size_t *num_ases,
-    const ASRange *range)
-{
-	struct cert_as		 as;
-
-	memset(&as, 0, sizeof(struct cert_as));
-	as.type = CERT_AS_RANGE;
-
-	if (!as_id_parse(range->min, &as.range.min)) {
-		warnx("%s: RFC 3779 section 3.2.3.8 (via RFC 1930): "
-		    "malformed AS identifier", fn);
-		return 0;
-	}
-
-	if (!as_id_parse(range->max, &as.range.max)) {
-		warnx("%s: RFC 3779 section 3.2.3.8 (via RFC 1930): "
-		    "malformed AS identifier", fn);
-		return 0;
-	}
-
-	if (as.range.max == as.range.min) {
-		warnx("%s: RFC 3379 section 3.2.3.8: ASRange: "
-		    "range is singular", fn);
-		return 0;
-	} else if (as.range.max < as.range.min) {
-		warnx("%s: RFC 3379 section 3.2.3.8: ASRange: "
-		    "range is out of order", fn);
-		return 0;
-	}
-
-	return append_as(fn, ases, num_ases, &as);
-}
-
-/*
- * Parse an entire 3.2.3.10 integer type.
- */
-int
-sbgp_as_id(const char *fn, struct cert_as *ases, size_t *num_ases,
-    const ASN1_INTEGER *i)
-{
-	struct cert_as	 as;
-
-	memset(&as, 0, sizeof(struct cert_as));
-	as.type = CERT_AS_ID;
-
-	if (!as_id_parse(i, &as.id)) {
-		warnx("%s: RFC 3779 section 3.2.3.10 (via RFC 1930): "
-		    "malformed AS identifier", fn);
-		return 0;
-	}
-	if (as.id == 0) {
-		warnx("%s: RFC 3779 section 3.2.3.10 (via RFC 1930): "
-		    "AS identifier zero is reserved", fn);
-		return 0;
-	}
-
-	return append_as(fn, ases, num_ases, &as);
-}
-
-static int
-sbgp_as_inherit(const char *fn, struct cert_as *ases, size_t *num_ases)
-{
-	struct cert_as as;
-
-	memset(&as, 0, sizeof(struct cert_as));
-	as.type = CERT_AS_INHERIT;
-
-	return append_as(fn, ases, num_ases, &as);
-}
-
 static int
 cert_as_inherit(const struct cert *cert)
 {
@@ -1374,99 +1079,13 @@ cert_has_one_as(const struct cert *cert)
 	return cert->ases[0].type == CERT_AS_ID;
 }
 
-int
-sbgp_parse_asids(const char *fn, const ASIdentifiers *asidentifiers,
-    struct cert_as **out_as, size_t *out_num_ases)
-{
-	const ASIdOrRanges	*aors = NULL;
-	struct cert_as		*as = NULL;
-	size_t			 num_ases = 0, num;
-	int			 i;
-
-	assert(*out_as == NULL && *out_num_ases == 0);
-
-	if (asidentifiers->rdi != NULL) {
-		warnx("%s: RFC 6487 section 4.8.11: autonomousSysIds: "
-		    "should not have RDI values", fn);
-		goto out;
-	}
-
-	if (asidentifiers->asnum == NULL) {
-		warnx("%s: RFC 6487 section 4.8.11: autonomousSysIds: "
-		    "no AS number resource set", fn);
-		goto out;
-	}
-
-	switch (asidentifiers->asnum->type) {
-	case ASIdentifierChoice_inherit:
-		num = 1;
-		break;
-	case ASIdentifierChoice_asIdsOrRanges:
-		aors = asidentifiers->asnum->u.asIdsOrRanges;
-		num = sk_ASIdOrRange_num(aors);
-		break;
-	default:
-		warnx("%s: RFC 3779 section 3.2.3.2: ASIdentifierChoice: "
-		    "unknown type %d", fn, asidentifiers->asnum->type);
-		goto out;
-	}
-
-	if (num == 0) {
-		warnx("%s: RFC 6487 section 4.8.11: empty asIdsOrRanges", fn);
-		goto out;
-	}
-	if (num >= MAX_AS_SIZE) {
-		warnx("%s: too many AS number entries: limit %d",
-		    fn, MAX_AS_SIZE);
-		goto out;
-	}
-	as = calloc(num, sizeof(struct cert_as));
-	if (as == NULL)
-		err(1, NULL);
-
-	if (aors == NULL) {
-		if (!sbgp_as_inherit(fn, as, &num_ases))
-			goto out;
-	}
-
-	for (i = 0; i < sk_ASIdOrRange_num(aors); i++) {
-		const ASIdOrRange *aor;
-
-		aor = sk_ASIdOrRange_value(aors, i);
-		switch (aor->type) {
-		case ASIdOrRange_id:
-			if (!sbgp_as_id(fn, as, &num_ases, aor->u.id))
-				goto out;
-			break;
-		case ASIdOrRange_range:
-			if (!sbgp_as_range(fn, as, &num_ases, aor->u.range))
-				goto out;
-			break;
-		default:
-			warnx("%s: RFC 3779 section 3.2.3.5: ASIdOrRange: "
-			    "unknown type %d", fn, aor->type);
-			goto out;
-		}
-	}
-
-	*out_as = as;
-	*out_num_ases = num_ases;
-
-	return 1;
-
- out:
-	free(as);
-
-	return 0;
-}
-
 /*
  * Parse an AS Resources X.509v3 extension, RFC 6487 4.8.11, with
  * syntax documented in RFC 3779 starting in section 3.2.
  * Returns zero on failure, non-zero on success.
  */
 static int
-sbgp_asids(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
+cert_asids(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
 {
 	ASIdentifiers		*asidentifiers = NULL;
 	int			 rc = 0;
@@ -1587,13 +1206,13 @@ cert_parse_extensions(const char *fn, st
 		case NID_sbgp_ipAddrBlock:
 			if (ip++ > 0)
 				goto dup;
-			if (!sbgp_ipaddrblocks(fn, cert, ext))
+			if (!cert_ipaddrblocks(fn, cert, ext))
 				goto out;
 			break;
 		case NID_sbgp_autonomousSysNum:
 			if (as++ > 0)
 				goto dup;
-			if (!sbgp_asids(fn, cert, ext))
+			if (!cert_asids(fn, cert, ext))
 				goto out;
 			break;
 		default:
Index: ip.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/ip.c,v
diff -u -p -r1.37 ip.c
--- ip.c	4 Dec 2025 06:11:44 -0000	1.37
+++ ip.c	29 Apr 2026 06:06:27 -0000
@@ -1,6 +1,7 @@
 /*	$OpenBSD: ip.c,v 1.37 2025/12/04 06:11:44 tb Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2022 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
@@ -18,6 +19,7 @@
 #include <sys/socket.h>
 #include <arpa/inet.h>
 
+#include <assert.h>
 #include <err.h>
 #include <stdlib.h>
 #include <string.h>
@@ -366,4 +368,209 @@ ip_warn(const char *fn, const char *msg,
 		warnx("%s: corrupt cert", fn);
 		break;
 	}
+}
+
+/*
+ * Append an IP address structure to our list of results, ensuring there is
+ * at most one inheritance marker per AFI and no overlapping ranges.
+ */
+static int
+append_ip(const char *fn, struct cert_ip *ips, size_t *num_ips,
+    const struct cert_ip *ip)
+{
+	if (!ip_addr_check_overlap(ip, fn, ips, *num_ips, 0))
+		return 0;
+	ips[(*num_ips)++] = *ip;
+	return 1;
+}
+
+/*
+ * Construct a RFC 3779 2.2.3.8 range from its bit string.
+ * Returns zero on failure, non-zero on success.
+ */
+int
+sbgp_addr(const char *fn, struct cert_ip *ips, size_t *num_ips, enum afi afi,
+    const ASN1_BIT_STRING *bs)
+{
+	struct cert_ip	ip;
+
+	memset(&ip, 0, sizeof(struct cert_ip));
+
+	ip.afi = afi;
+	ip.type = CERT_IP_ADDR;
+
+	if (!ip_addr_parse(bs, afi, fn, &ip.ip)) {
+		warnx("%s: RFC 3779 section 2.2.3.8: IPAddress: "
+		    "invalid IP address", fn);
+		return 0;
+	}
+
+	if (!ip_cert_compose_ranges(&ip)) {
+		warnx("%s: RFC 3779 section 2.2.3.8: IPAddress: "
+		    "IP address range reversed", fn);
+		return 0;
+	}
+
+	return append_ip(fn, ips, num_ips, &ip);
+}
+
+/*
+ * Parse RFC 3779 2.2.3.9 range of addresses.
+ * Returns zero on failure, non-zero on success.
+ */
+int
+sbgp_addr_range(const char *fn, struct cert_ip *ips, size_t *num_ips,
+    enum afi afi, const IPAddressRange *range)
+{
+	struct cert_ip	ip;
+
+	memset(&ip, 0, sizeof(struct cert_ip));
+
+	ip.afi = afi;
+	ip.type = CERT_IP_RANGE;
+
+	if (!ip_addr_parse(range->min, afi, fn, &ip.range.min)) {
+		warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: "
+		    "invalid IP address", fn);
+		return 0;
+	}
+
+	if (!ip_addr_parse(range->max, afi, fn, &ip.range.max)) {
+		warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: "
+		    "invalid IP address", fn);
+		return 0;
+	}
+
+	if (!ip_cert_compose_ranges(&ip)) {
+		warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: "
+		    "IP address range reversed", fn);
+		return 0;
+	}
+
+	return append_ip(fn, ips, num_ips, &ip);
+}
+
+static int
+sbgp_addr_inherit(const char *fn, struct cert_ip *ips, size_t *num_ips,
+    enum afi afi)
+{
+	struct cert_ip	ip;
+
+	memset(&ip, 0, sizeof(struct cert_ip));
+
+	ip.afi = afi;
+	ip.type = CERT_IP_INHERIT;
+
+	return append_ip(fn, ips, num_ips, &ip);
+}
+
+int
+sbgp_parse_ipaddrblocks(const char *fn, const IPAddrBlocks *addrs,
+    struct cert_ip **out_ips, size_t *out_num_ips)
+{
+	const IPAddressFamily	*af;
+	const IPAddressOrRanges	*aors;
+	const IPAddressOrRange	*aor;
+	enum afi		 afi;
+	struct cert_ip		*ips = NULL;
+	size_t			 num_ips = 0, num;
+	int			 ipv4_seen = 0, ipv6_seen = 0;
+	int			 i, j, addrsz;
+
+	assert(*out_ips == NULL && *out_num_ips == 0);
+
+	addrsz = sk_IPAddressFamily_num(addrs);
+	if (addrsz != 1 && addrsz != 2) {
+		warnx("%s: RFC 6487 section 4.8.10: unexpected number of "
+		    "ipAddrBlocks (got %d, expected 1 or 2)", fn, addrsz);
+		goto out;
+	}
+
+	for (i = 0; i < addrsz; i++) {
+		af = sk_IPAddressFamily_value(addrs, i);
+
+		switch (af->ipAddressChoice->type) {
+		case IPAddressChoice_inherit:
+			aors = NULL;
+			num = num_ips + 1;
+			break;
+		case IPAddressChoice_addressesOrRanges:
+			aors = af->ipAddressChoice->u.addressesOrRanges;
+			num = num_ips + sk_IPAddressOrRange_num(aors);
+			break;
+		default:
+			warnx("%s: RFC 3779: IPAddressChoice: unknown type %d",
+			    fn, af->ipAddressChoice->type);
+			goto out;
+		}
+		if (num == num_ips) {
+			warnx("%s: RFC 6487 section 4.8.10: "
+			    "empty ipAddressesOrRanges", fn);
+			goto out;
+		}
+
+		if (num >= MAX_IP_SIZE)
+			goto out;
+		ips = recallocarray(ips, num_ips, num, sizeof(struct cert_ip));
+		if (ips == NULL)
+			err(1, NULL);
+
+		if (!ip_addr_afi_parse(fn, af->addressFamily, &afi)) {
+			warnx("%s: RFC 3779: invalid AFI", fn);
+			goto out;
+		}
+
+		switch (afi) {
+		case AFI_IPV4:
+			if (ipv4_seen++ > 0) {
+				warnx("%s: RFC 6487 section 4.8.10: "
+				    "IPv4 appears twice", fn);
+				goto out;
+			}
+			break;
+		case AFI_IPV6:
+			if (ipv6_seen++ > 0) {
+				warnx("%s: RFC 6487 section 4.8.10: "
+				    "IPv6 appears twice", fn);
+				goto out;
+			}
+			break;
+		}
+
+		if (aors == NULL) {
+			if (!sbgp_addr_inherit(fn, ips, &num_ips, afi))
+				goto out;
+			continue;
+		}
+
+		for (j = 0; j < sk_IPAddressOrRange_num(aors); j++) {
+			aor = sk_IPAddressOrRange_value(aors, j);
+			switch (aor->type) {
+			case IPAddressOrRange_addressPrefix:
+				if (!sbgp_addr(fn, ips, &num_ips, afi,
+				    aor->u.addressPrefix))
+					goto out;
+				break;
+			case IPAddressOrRange_addressRange:
+				if (!sbgp_addr_range(fn, ips, &num_ips, afi,
+				    aor->u.addressRange))
+					goto out;
+				break;
+			default:
+				warnx("%s: RFC 3779: IPAddressOrRange: "
+				    "unknown type %d", fn, aor->type);
+				goto out;
+			}
+		}
+	}
+
+	*out_ips = ips;
+	*out_num_ips = num_ips;
+
+	return 1;
+
+ out:
+	free(ips);
+
+	return 0;
 }