summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJob Snijders <job@cvs.openbsd.org>2023-10-13 12:06:50 +0000
committerJob Snijders <job@cvs.openbsd.org>2023-10-13 12:06:50 +0000
commita5ed47bf0aedb7c2d81adf1f68f504ecdb3dea82 (patch)
treede0e309db2e58d0a3150280c625647dd9e6b1546
parent30cf777d52de1af041e49d9913f534b87d6c31d6 (diff)
Allow imposing constraints on RPKI trust anchors
The ability to constrain a RPKI Trust Anchor's effective signing authority to a limited set of Internet Number Resources allows Relying Parties to enjoy the potential benefits of assuming trust, within a bounded scope. Some examples: ARIN does not support inter-RIR IPv6 transfers, so it wouldn't make any sense to see a ROA subordinate to ARIN's trust anchor covering RIPE-managed IPv6 space. Conversely, it wouldn't make sense to observe a ROA covering ARIN-managed IPv6 space under APNIC's, LACNIC's, or RIPE's trust anchor - even if a derived trust arc (a cryptographically valid certificate path) existed. Along these same lines, AFRINIC doesn't support inter-RIR transfers of any kind, and none of the RIRs have authority over private resources like 10.0.0.0/8 and 2001:db8::/32. For more background see: https://datatracker.ietf.org/doc/draft-snijders-constraining-rpki-trust-anchors/ https://mailman.nanog.org/pipermail/nanog/2023-September/223354.html With and OK tb@, OK claudio@
-rw-r--r--usr.sbin/rpki-client/Makefile14
-rw-r--r--usr.sbin/rpki-client/as.c34
-rw-r--r--usr.sbin/rpki-client/aspa.c4
-rw-r--r--usr.sbin/rpki-client/cert.c19
-rw-r--r--usr.sbin/rpki-client/constraints.c600
-rw-r--r--usr.sbin/rpki-client/extern.h20
-rw-r--r--usr.sbin/rpki-client/filemode.c14
-rw-r--r--usr.sbin/rpki-client/gbr.c4
-rw-r--r--usr.sbin/rpki-client/geofeed.c4
-rw-r--r--usr.sbin/rpki-client/ip.c31
-rw-r--r--usr.sbin/rpki-client/main.c8
-rw-r--r--usr.sbin/rpki-client/mft.c4
-rw-r--r--usr.sbin/rpki-client/parser.c10
-rw-r--r--usr.sbin/rpki-client/rfc3779.c52
-rw-r--r--usr.sbin/rpki-client/roa.c4
-rw-r--r--usr.sbin/rpki-client/rpki-client.852
-rw-r--r--usr.sbin/rpki-client/rsc.c4
-rw-r--r--usr.sbin/rpki-client/tak.c4
18 files changed, 841 insertions, 41 deletions
diff --git a/usr.sbin/rpki-client/Makefile b/usr.sbin/rpki-client/Makefile
index 660d4533c3c..edb66b69757 100644
--- a/usr.sbin/rpki-client/Makefile
+++ b/usr.sbin/rpki-client/Makefile
@@ -1,12 +1,12 @@
-# $OpenBSD: Makefile,v 1.32 2023/06/29 10:28:25 tb Exp $
+# $OpenBSD: Makefile,v 1.33 2023/10/13 12:06:49 job Exp $
PROG= rpki-client
-SRCS= as.c aspa.c cert.c cms.c crl.c encoding.c filemode.c gbr.c geofeed.c \
- http.c io.c ip.c json.c main.c mft.c mkdir.c ometric.c output.c \
- output-bgpd.c output-bird.c output-csv.c output-json.c \
- output-ometric.c parser.c print.c repo.c roa.c rrdp.c rrdp_delta.c \
- rrdp_notification.c rrdp_snapshot.c rrdp_util.c rsc.c rsync.c tak.c \
- tal.c validate.c x509.c
+SRCS= as.c aspa.c cert.c cms.c constraints.c crl.c encoding.c filemode.c \
+ gbr.c geofeed.c http.c io.c ip.c json.c main.c mft.c mkdir.c ometric.c \
+ output.c output-bgpd.c output-bird.c output-csv.c output-json.c \
+ output-ometric.c parser.c print.c repo.c rfc3779.c roa.c \
+ rrdp.c rrdp_delta.c rrdp_notification.c rrdp_snapshot.c rrdp_util.c \
+ rsc.c rsync.c tak.c tal.c validate.c x509.c
MAN= rpki-client.8
LDADD+= -lexpat -ltls -lssl -lcrypto -lutil -lz
diff --git a/usr.sbin/rpki-client/as.c b/usr.sbin/rpki-client/as.c
index 2f4aabda0b9..dd8039521cc 100644
--- a/usr.sbin/rpki-client/as.c
+++ b/usr.sbin/rpki-client/as.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: as.c,v 1.12 2023/05/23 06:39:31 tb Exp $ */
+/* $OpenBSD: as.c,v 1.13 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
@@ -45,7 +45,7 @@ as_id_parse(const ASN1_INTEGER *v, uint32_t *out)
*/
int
as_check_overlap(const struct cert_as *a, const char *fn,
- const struct cert_as *as, size_t asz)
+ const struct cert_as *as, size_t asz, int quiet)
{
size_t i;
@@ -53,6 +53,8 @@ as_check_overlap(const struct cert_as *a, const char *fn,
if (asz &&
(a->type == CERT_AS_INHERIT || as[0].type == CERT_AS_INHERIT)) {
+ if (quiet)
+ return 0;
warnx("%s: RFC 3779 section 3.2.3.3: "
"cannot have inheritance and multiple ASnum or "
"multiple inheritance", fn);
@@ -68,6 +70,8 @@ as_check_overlap(const struct cert_as *a, const char *fn,
case CERT_AS_ID:
if (a->id != as[i].id)
break;
+ if (quiet)
+ return 0;
warnx("%s: RFC 3779 section 3.2.3.4: "
"cannot have overlapping ASnum", fn);
return 0;
@@ -75,6 +79,8 @@ as_check_overlap(const struct cert_as *a, const char *fn,
if (as->range.min > as[i].id ||
as->range.max < as[i].id)
break;
+ if (quiet)
+ return 0;
warnx("%s: RFC 3779 section 3.2.3.4: "
"cannot have overlapping ASnum", fn);
return 0;
@@ -88,6 +94,8 @@ as_check_overlap(const struct cert_as *a, const char *fn,
if (as[i].range.min > a->id ||
as[i].range.max < a->id)
break;
+ if (quiet)
+ return 0;
warnx("%s: RFC 3779 section 3.2.3.4: "
"cannot have overlapping ASnum", fn);
return 0;
@@ -95,6 +103,8 @@ as_check_overlap(const struct cert_as *a, const char *fn,
if (a->range.max < as[i].range.min ||
a->range.min > as[i].range.max)
break;
+ if (quiet)
+ return 0;
warnx("%s: RFC 3779 section 3.2.3.4: "
"cannot have overlapping ASnum", fn);
return 0;
@@ -135,3 +145,23 @@ as_check_covered(uint32_t min, uint32_t max,
return -1;
}
+
+void
+as_warn(const char *fn, const struct cert_as *cert, const char *msg)
+{
+ switch (cert->type) {
+ case CERT_AS_ID:
+ warnx("%s: AS %u: %s", fn, cert->id, msg);
+ break;
+ case CERT_AS_INHERIT:
+ warnx("%s: AS (inherit): %s", fn, msg);
+ break;
+ case CERT_AS_RANGE:
+ warnx("%s: AS range %u--%u: %s", fn, cert->range.min,
+ cert->range.max, msg);
+ break;
+ default:
+ warnx("%s: corrupt cert", fn);
+ break;
+ }
+}
diff --git a/usr.sbin/rpki-client/aspa.c b/usr.sbin/rpki-client/aspa.c
index fc4a292f858..8d81c161b0c 100644
--- a/usr.sbin/rpki-client/aspa.c
+++ b/usr.sbin/rpki-client/aspa.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: aspa.c,v 1.23 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: aspa.c,v 1.24 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2022 Job Snijders <job@fastly.com>
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
@@ -215,7 +215,7 @@ aspa_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
if (!aspa_parse_econtent(cms, cmsz, &p))
goto out;
- if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL)
+ if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
goto out;
p.res->valid = valid_aspa(fn, cert, p.res);
diff --git a/usr.sbin/rpki-client/cert.c b/usr.sbin/rpki-client/cert.c
index 7c8e07fa4b7..6e370bf1709 100644
--- a/usr.sbin/rpki-client/cert.c
+++ b/usr.sbin/rpki-client/cert.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: cert.c,v 1.117 2023/09/25 15:33:08 tb Exp $ */
+/* $OpenBSD: cert.c,v 1.118 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
* Copyright (c) 2021 Job Snijders <job@openbsd.org>
@@ -57,7 +57,7 @@ static int
append_ip(const char *fn, struct cert_ip *ips, size_t *ipsz,
const struct cert_ip *ip)
{
- if (!ip_addr_check_overlap(ip, fn, ips, *ipsz))
+ if (!ip_addr_check_overlap(ip, fn, ips, *ipsz, 0))
return 0;
ips[(*ipsz)++] = *ip;
return 1;
@@ -72,7 +72,7 @@ static int
append_as(const char *fn, struct cert_as *ases, size_t *asz,
const struct cert_as *as)
{
- if (!as_check_overlap(as, fn, ases, *asz))
+ if (!as_check_overlap(as, fn, ases, *asz, 0))
return 0;
ases[(*asz)++] = *as;
return 1;
@@ -446,8 +446,8 @@ sbgp_parse_ipaddrblk(const char *fn, const IPAddrBlocks *addrblk,
static int
sbgp_ipaddrblk(struct parse *p, X509_EXTENSION *ext)
{
- STACK_OF(IPAddressFamily) *addrblk = NULL;
- int rc = 0;
+ IPAddrBlocks *addrblk = NULL;
+ int rc = 0;
if (!X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: "
@@ -471,7 +471,7 @@ sbgp_ipaddrblk(struct parse *p, X509_EXTENSION *ext)
rc = 1;
out:
- sk_IPAddressFamily_pop_free(addrblk, IPAddressFamily_free);
+ IPAddrBlocks_free(addrblk);
return rc;
}
@@ -641,7 +641,7 @@ certificate_policies(struct parse *p, X509_EXTENSION *ext)
* Returns cert on success and NULL on failure.
*/
struct cert *
-cert_parse_ee_cert(const char *fn, X509 *x)
+cert_parse_ee_cert(const char *fn, int talid, X509 *x)
{
struct parse p;
X509_EXTENSION *ext;
@@ -690,6 +690,11 @@ cert_parse_ee_cert(const char *fn, X509 *x)
}
p.res->x509 = x;
+ p.res->talid = talid;
+
+ if (!constraints_validate(fn, p.res))
+ goto out;
+
return p.res;
out:
diff --git a/usr.sbin/rpki-client/constraints.c b/usr.sbin/rpki-client/constraints.c
new file mode 100644
index 00000000000..226e249b3f9
--- /dev/null
+++ b/usr.sbin/rpki-client/constraints.c
@@ -0,0 +1,600 @@
+/* $OpenBSD: constraints.c,v 1.1 2023/10/13 12:06:49 job Exp $ */
+/*
+ * Copyright (c) 2023 Job Snijders <job@openbsd.org>
+ * Copyright (c) 2023 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 <sys/socket.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/asn1.h>
+#include <openssl/x509v3.h>
+
+#include "extern.h"
+
+struct tal_constraints {
+ int fd; /* constraints file descriptor or -1. */
+ char *fn; /* constraints filename */
+ struct cert_ip *allow_ips; /* list of allowed IP address ranges */
+ size_t allow_ipsz; /* length of "allow_ips" */
+ struct cert_as *allow_as; /* allowed AS numbers and ranges */
+ size_t allow_asz; /* length of "allow_as" */
+ struct cert_ip *deny_ips; /* forbidden IP address ranges */
+ size_t deny_ipsz; /* length of "deny_ips" */
+ struct cert_as *deny_as; /* forbidden AS numbers and ranges */
+ size_t deny_asz; /* length of "deny_as" */
+} tal_constraints[TALSZ_MAX];
+
+/*
+ * If there is a .constraints file next to a .tal file, load its contents
+ * into into tal_constraints[talid]. The load function only opens the fd
+ * and stores the filename. The actual parsing happens in constraints_parse().
+ * Resources of EE certs can then be constrained using constraints_validate().
+ */
+
+static void
+constraints_load_talid(int talid)
+{
+ const char *tal = tals[talid];
+ char *constraints = NULL;
+ int fd;
+ size_t len;
+ int saved_errno;
+
+ tal_constraints[talid].fd = -1;
+
+ if (rtype_from_file_extension(tal) != RTYPE_TAL)
+ return;
+
+ /* Replace .tal suffix with .constraints. */
+ len = strlen(tal) - 4;
+ if (asprintf(&constraints, "%.*s.constraints", (int)len, tal) == -1)
+ errx(1, NULL);
+
+ saved_errno = errno;
+
+ fd = open(constraints, O_RDONLY);
+ if (fd == -1 && errno != ENOENT)
+ err(1, "failed to load constraints for %s", tal);
+
+ tal_constraints[talid].fn = constraints;
+ tal_constraints[talid].fd = fd;
+
+ errno = saved_errno;
+}
+
+/*
+ * Iterate over all TALs and load the corresponding constraints files.
+ */
+void
+constraints_load(void)
+{
+ int talid;
+
+ for (talid = 0; talid < talsz; talid++)
+ constraints_load_talid(talid);
+}
+
+void
+constraints_unload(void)
+{
+ int saved_errno, talid;
+
+ saved_errno = errno;
+ for (talid = 0; talid < talsz; talid++) {
+ if (tal_constraints[talid].fd != -1)
+ close(tal_constraints[talid].fd);
+ free(tal_constraints[talid].fn);
+ tal_constraints[talid].fd = -1;
+ tal_constraints[talid].fn = NULL;
+ }
+ errno = saved_errno;
+}
+
+/*
+ * Split a string at '-' and trim whitespace around the '-'.
+ * Assumes leading and trailing whitespace in p has already been trimmed.
+ */
+static int
+constraints_split_range(char *p, const char **min, const char **max)
+{
+ char *pp;
+
+ *min = p;
+ if ((*max = pp = strchr(p, '-')) == NULL)
+ return 0;
+
+ /* Trim whitespace before '-'. */
+ while (pp > *min && isspace((unsigned char)pp[-1]))
+ pp--;
+ *pp = '\0';
+
+ /* Skip past '-' and whitespace following it. */
+ (*max)++;
+ while (isspace((unsigned char)**max))
+ (*max)++;
+
+ return 1;
+}
+
+/*
+ * Helper functions to parse textual representations of IP prefixes or ranges.
+ * The RFC 3779 API has poor error reporting, so as a debugging aid, we call
+ * the prohibitively expensive X509v3_addr_canonize() in high verbosity mode.
+ */
+
+static void
+constraints_parse_ip_prefix(const char *fn, const char *prefix, enum afi afi,
+ IPAddrBlocks *addrs)
+{
+ unsigned char addr[16] = { 0 };
+ int af = afi == AFI_IPV4 ? AF_INET : AF_INET6;
+ int plen;
+
+ if ((plen = inet_net_pton(af, prefix, addr, sizeof(addr))) == -1)
+ errx(1, "%s: failed to parse %s", fn, prefix);
+
+ if (!X509v3_addr_add_prefix(addrs, afi, NULL, addr, plen))
+ errx(1, "%s: failed to add prefix %s", fn, prefix);
+
+ if (verbose < 3)
+ return;
+
+ if (!X509v3_addr_canonize(addrs))
+ errx(1, "%s: failed to canonize with prefix %s", fn, prefix);
+}
+
+static void
+constraints_parse_ip_range(const char *fn, const char *min, const char *max,
+ enum afi afi, IPAddrBlocks *addrs)
+{
+ unsigned char min_addr[16] = {0}, max_addr[16] = {0};
+ int af = afi == AFI_IPV4 ? AF_INET : AF_INET6;
+
+ if (inet_pton(af, min, min_addr) != 1)
+ errx(1, "%s: failed to parse %s", fn, min);
+ if (inet_pton(af, max, max_addr) != 1)
+ errx(1, "%s: failed to parse %s", fn, max);
+
+ if (!X509v3_addr_add_range(addrs, afi, NULL, min_addr, max_addr))
+ errx(1, "%s: failed to add range %s--%s", fn, min, max);
+
+ if (verbose < 3)
+ return;
+
+ if (!X509v3_addr_canonize(addrs))
+ errx(1, "%s: failed to canonize with range %s--%s", fn,
+ min, max);
+}
+
+static void
+constraints_parse_ip(const char *fn, char *p, enum afi afi, IPAddrBlocks *addrs)
+{
+ const char *min, *max;
+
+ if (strchr(p, '-') == NULL) {
+ constraints_parse_ip_prefix(fn, p, afi, addrs);
+ return;
+ }
+
+ if (!constraints_split_range(p, &min, &max))
+ errx(1, "%s: failed to split range: %s", fn, p);
+
+ constraints_parse_ip_range(fn, min, max, afi, addrs);
+}
+
+/*
+ * Helper functions to parse textual representations of AS numbers or ranges.
+ * The RFC 3779 API has poor error reporting, so as a debugging aid, we call
+ * the prohibitively expensive X509v3_asid_canonize() in high verbosity mode.
+ */
+
+static void
+constraints_parse_asn(const char *fn, const char *asn, ASIdentifiers *asids)
+{
+ ASN1_INTEGER *id;
+
+ if ((id = s2i_ASN1_INTEGER(NULL, asn)) == NULL)
+ errx(1, "%s: failed to parse AS %s", fn, asn);
+
+ if (!X509v3_asid_add_id_or_range(asids, V3_ASID_ASNUM, id, NULL))
+ errx(1, "%s: failed to add AS %s", fn, asn);
+
+ if (verbose < 3)
+ return;
+
+ if (!X509v3_asid_canonize(asids))
+ errx(1, "%s: failed to canonize with AS %s", fn, asn);
+}
+
+static void
+constraints_parse_asn_range(const char *fn, const char *min, const char *max,
+ ASIdentifiers *asids)
+{
+ ASN1_INTEGER *min_as, *max_as;
+
+ if ((min_as = s2i_ASN1_INTEGER(NULL, min)) == NULL)
+ errx(1, "%s: failed to parse AS %s", fn, min);
+ if ((max_as = s2i_ASN1_INTEGER(NULL, max)) == NULL)
+ errx(1, "%s: failed to parse AS %s", fn, max);
+
+ if (!X509v3_asid_add_id_or_range(asids, V3_ASID_ASNUM, min_as, max_as))
+ errx(1, "%s: failed to add AS range %s--%s", fn, min, max);
+
+ if (verbose < 3)
+ return;
+
+ if (!X509v3_asid_canonize(asids))
+ errx(1, "%s: failed to canonize with AS range %s--%s", fn,
+ min, max);
+}
+
+static void
+constraints_parse_as(const char *fn, char *p, ASIdentifiers *asids)
+{
+ const char *min, *max;
+
+ if (strchr(p, '-') == NULL) {
+ constraints_parse_asn(fn, p, asids);
+ return;
+ }
+
+ if (!constraints_split_range(p, &min, &max))
+ errx(1, "%s: failed to split range: %s", fn, p);
+
+ constraints_parse_asn_range(fn, min, max, asids);
+}
+
+/*
+ * Work around an annoying bug in X509v3_addr_add_range(). The upper bound
+ * of a range can have unused bits set in its ASN1_BIT_STRING representation.
+ * This triggers a check in ip_addr_parse(). A round trip through DER fixes
+ * this mess up. For extra special fun, {d2i,i2d}_IPAddrBlocks() isn't part
+ * of the API and implementing them for OpenSSL 3 is hairy, so do the round
+ * tripping once per address family.
+ */
+static void
+constraints_normalize_ip_addrblocks(const char *fn, IPAddrBlocks **addrs)
+{
+ IPAddrBlocks *new_addrs;
+ IPAddressFamily *af;
+ const unsigned char *p;
+ unsigned char *der;
+ int der_len, i;
+
+ if ((new_addrs = IPAddrBlocks_new()) == NULL)
+ err(1, NULL);
+
+ for (i = 0; i < sk_IPAddressFamily_num(*addrs); i++) {
+ af = sk_IPAddressFamily_value(*addrs, i);
+
+ der = NULL;
+ if ((der_len = i2d_IPAddressFamily(af, &der)) <= 0)
+ errx(1, "%s: failed to convert to DER", fn);
+ p = der;
+ if ((af = d2i_IPAddressFamily(NULL, &p, der_len)) == NULL)
+ errx(1, "%s: failed to convert from DER", fn);
+ free(der);
+
+ if (!sk_IPAddressFamily_push(new_addrs, af))
+ errx(1, "%s: failed to push constraints", fn);
+ }
+
+ IPAddrBlocks_free(*addrs);
+ *addrs = new_addrs;
+}
+
+/*
+ * If there is a constraints file for tals[talid], load it into a buffer
+ * and parse it line by line. Leverage the above parse helpers to build up
+ * IPAddrBlocks and ASIdentifiers. We use the RFC 3779 API to benefit from
+ * the limited abilities of X509v3_{addr,asid}_canonize() to sort and merge
+ * adjacent ranges. This doesn't deal with overlaps or duplicates, but it's
+ * better than nothing.
+ */
+
+static void
+constraints_parse_talid(int talid)
+{
+ IPAddrBlocks *allow_addrs, *deny_addrs;
+ ASIdentifiers *allow_asids, *deny_asids;
+ FILE *f;
+ char *fn, *p, *pp;
+ struct cert_as *allow_as = NULL, *deny_as = NULL;
+ struct cert_ip *allow_ips = NULL, *deny_ips = NULL;
+ size_t allow_asz = 0, allow_ipsz = 0,
+ deny_asz = 0, deny_ipsz = 0;
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t n;
+ int fd, have_allow_as = 0, have_allow_ips = 0,
+ have_deny_as = 0, have_deny_ips = 0;
+
+ fd = tal_constraints[talid].fd;
+ fn = tal_constraints[talid].fn;
+ tal_constraints[talid].fd = -1;
+ tal_constraints[talid].fn = NULL;
+
+ if (fd == -1) {
+ free(fn);
+ return;
+ }
+
+ if ((f = fdopen(fd, "r")) == NULL)
+ err(1, "fdopen");
+
+ if ((allow_addrs = IPAddrBlocks_new()) == NULL)
+ err(1, NULL);
+ if ((allow_asids = ASIdentifiers_new()) == NULL)
+ err(1, NULL);
+ if ((deny_addrs = IPAddrBlocks_new()) == NULL)
+ err(1, NULL);
+ if ((deny_asids = ASIdentifiers_new()) == NULL)
+ err(1, NULL);
+
+ while ((n = getline(&line, &len, f)) != -1) {
+ if (line[n - 1] == '\n')
+ line[n - 1] = '\0';
+
+ p = line;
+
+ /* Zap leading whitespace */
+ while (isspace((unsigned char)*p))
+ p++;
+
+ /* Zap comments */
+ if ((pp = strchr(p, '#')) != NULL)
+ *pp = '\0';
+
+ /* Zap trailing whitespace */
+ if (pp == NULL)
+ pp = p + strlen(p);
+ while (pp > p && isspace((unsigned char)pp[-1]))
+ pp--;
+ *pp = '\0';
+
+ if (strlen(p) == 0)
+ continue;
+
+ if (strncmp(p, "allow", strlen("allow")) == 0) {
+ p += strlen("allow");
+
+ /* Ensure there's whitespace and jump over it. */
+ if (!isspace((unsigned char)*p))
+ errx(1, "%s: failed to parse %s", fn, p);
+ while (isspace((unsigned char)*p))
+ p++;
+
+ if (strchr(p, '.') != NULL) {
+ constraints_parse_ip(fn, p, AFI_IPV4,
+ allow_addrs);
+ have_allow_ips = 1;
+ } else if (strchr(p, ':') != NULL) {
+ constraints_parse_ip(fn, p, AFI_IPV6,
+ allow_addrs);
+ have_allow_ips = 1;
+ } else {
+ constraints_parse_as(fn, p, allow_asids);
+ have_allow_as = 1;
+ }
+ } else if (strncmp(p, "deny", strlen("deny")) == 0) {
+ p += strlen("deny");
+
+ /* Ensure there's whitespace and jump over it. */
+ if (!isspace((unsigned char)*p))
+ errx(1, "%s: failed to parse %s", fn, p);
+ /* Zap leading whitespace */
+ while (isspace((unsigned char)*p))
+ p++;
+
+ if (strchr(p, '.') != NULL) {
+ constraints_parse_ip(fn, p, AFI_IPV4,
+ deny_addrs);
+ have_deny_ips = 1;
+ } else if (strchr(p, ':') != NULL) {
+ constraints_parse_ip(fn, p, AFI_IPV6,
+ deny_addrs);
+ have_deny_ips = 1;
+ } else {
+ constraints_parse_as(fn, p, deny_asids);
+ have_deny_as = 1;
+ }
+ } else
+ errx(1, "%s: failed to parse %s", fn, p);
+ }
+ free(line);
+
+ if (ferror(f))
+ err(1, "%s", fn);
+ fclose(f);
+
+ if (!X509v3_addr_canonize(allow_addrs))
+ errx(1, "%s: failed to canonize IP addresses allowlist", fn);
+ if (!X509v3_asid_canonize(allow_asids))
+ errx(1, "%s: failed to canonize AS numbers allowlist", fn);
+ if (!X509v3_addr_canonize(deny_addrs))
+ errx(1, "%s: failed to canonize IP addresses denylist", fn);
+ if (!X509v3_asid_canonize(deny_asids))
+ errx(1, "%s: failed to canonize AS numbers denylist", fn);
+
+ if (have_allow_as) {
+ if (!sbgp_parse_assysnum(fn, allow_asids, &allow_as,
+ &allow_asz))
+ errx(1, "%s: failed to parse AS identifiers allowlist",
+ fn);
+ }
+ if (have_deny_as) {
+ if (!sbgp_parse_assysnum(fn, deny_asids, &deny_as,
+ &deny_asz))
+ errx(1, "%s: failed to parse AS identifiers denylist",
+ fn);
+ }
+ if (have_allow_ips) {
+ constraints_normalize_ip_addrblocks(fn, &allow_addrs);
+
+ if (!sbgp_parse_ipaddrblk(fn, allow_addrs, &allow_ips,
+ &allow_ipsz))
+ errx(1, "%s: failed to parse IP addresses allowlist",
+ fn);
+ }
+ if (have_deny_ips) {
+ constraints_normalize_ip_addrblocks(fn, &deny_addrs);
+
+ if (!sbgp_parse_ipaddrblk(fn, deny_addrs, &deny_ips,
+ &deny_ipsz))
+ errx(1, "%s: failed to parse IP addresses denylist",
+ fn);
+ }
+
+ tal_constraints[talid].allow_as = allow_as;
+ tal_constraints[talid].allow_asz = allow_asz;
+ tal_constraints[talid].allow_ips = allow_ips;
+ tal_constraints[talid].allow_ipsz = allow_ipsz;
+ tal_constraints[talid].deny_as = deny_as;
+ tal_constraints[talid].deny_asz = deny_asz;
+ tal_constraints[talid].deny_ips = deny_ips;
+ tal_constraints[talid].deny_ipsz = deny_ipsz;
+
+ IPAddrBlocks_free(allow_addrs);
+ IPAddrBlocks_free(deny_addrs);
+ ASIdentifiers_free(allow_asids);
+ ASIdentifiers_free(deny_asids);
+
+ free(fn);
+}
+
+/*
+ * Iterate over all TALs and parse the constraints files loaded previously.
+ */
+void
+constraints_parse(void)
+{
+ int talid;
+
+ for (talid = 0; talid < talsz; talid++)
+ constraints_parse_talid(talid);
+}
+
+static int
+constraints_check_as(const char *fn, struct cert_as *cert,
+ const struct cert_as *allow_as, size_t allow_asz,
+ const struct cert_as *deny_as, size_t deny_asz)
+{
+ uint32_t min, max;
+
+ /* Inheriting EE resources are not to be constrained. */
+ if (cert->type == CERT_AS_INHERIT)
+ return 1;
+
+ if (cert->type == CERT_AS_ID) {
+ min = cert->id;
+ max = cert->id;
+ } else {
+ min = cert->range.min;
+ max = cert->range.max;
+ }
+
+ if (deny_as != NULL) {
+ if (!as_check_overlap(cert, fn, deny_as, deny_asz, 1))
+ return 0;
+ }
+ if (allow_as != NULL) {
+ if (as_check_covered(min, max, allow_as, allow_asz) <= 0)
+ return 0;
+ }
+ return 1;
+}
+
+static int
+constraints_check_ips(const char *fn, struct cert_ip *cert,
+ const struct cert_ip *allow_ips, size_t allow_ipsz,
+ const struct cert_ip *deny_ips, size_t deny_ipsz)
+{
+ /* Inheriting EE resources are not to be constrained. */
+ if (cert->type == CERT_IP_INHERIT)
+ return 1;
+
+ if (deny_ips != NULL) {
+ if (!ip_addr_check_overlap(cert, fn, deny_ips, deny_ipsz, 1))
+ return 0;
+ }
+ if (allow_ips != NULL) {
+ if (ip_addr_check_covered(cert->afi, cert->min, cert->max,
+ allow_ips, allow_ipsz) <= 0)
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Check whether an EE cert's resources are covered by its TAL's constraints.
+ * We accept certs with a negative talid as "unknown TAL" for filemode. The
+ * logic nearly duplicates valid_cert().
+ */
+int
+constraints_validate(const char *fn, const struct cert *cert)
+{
+ int talid = cert->talid;
+ struct cert_as *allow_as, *deny_as;
+ struct cert_ip *allow_ips, *deny_ips;
+ size_t i, allow_asz, allow_ipsz, deny_asz, deny_ipsz;
+
+ /* Accept negative talid to bypass validation. */
+ if (talid < 0)
+ return 1;
+ if (talid >= talsz)
+ errx(1, "%s: talid out of range %d", fn, talid);
+
+ allow_as = tal_constraints[talid].allow_as;
+ allow_asz = tal_constraints[talid].allow_asz;
+ deny_as = tal_constraints[talid].deny_as;
+ deny_asz = tal_constraints[talid].deny_asz;
+
+ for (i = 0; i < cert->asz; i++) {
+ if (constraints_check_as(fn, &cert->as[i], allow_as, allow_asz,
+ deny_as, deny_asz))
+ continue;
+
+ as_warn(fn, &cert->as[i], "violates trust anchor constraints");
+ return 0;
+ }
+
+ allow_ips = tal_constraints[talid].allow_ips;
+ allow_ipsz = tal_constraints[talid].allow_ipsz;
+ deny_ips = tal_constraints[talid].deny_ips;
+ deny_ipsz = tal_constraints[talid].deny_ipsz;
+
+ for (i = 0; i < cert->ipsz; i++) {
+ if (constraints_check_ips(fn, &cert->ips[i], allow_ips,
+ allow_ipsz, deny_ips, deny_ipsz))
+ continue;
+
+ ip_warn(fn, &cert->ips[i], "violates trust anchor constraints");
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h
index 53ff444e843..52df7f8e06d 100644
--- a/usr.sbin/rpki-client/extern.h
+++ b/usr.sbin/rpki-client/extern.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: extern.h,v 1.192 2023/09/25 14:56:20 tb Exp $ */
+/* $OpenBSD: extern.h,v 1.193 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
@@ -613,7 +613,7 @@ struct tal *tal_read(struct ibuf *);
void cert_buffer(struct ibuf *, const struct cert *);
void cert_free(struct cert *);
void auth_tree_free(struct auth_tree *);
-struct cert *cert_parse_ee_cert(const char *, X509 *);
+struct cert *cert_parse_ee_cert(const char *, int, X509 *);
struct cert *cert_parse_pre(const char *, const unsigned char *, size_t);
struct cert *cert_parse(const char *, struct cert *);
struct cert *ta_parse(const char *, struct cert *, const unsigned char *,
@@ -712,11 +712,12 @@ void ip_addr_range_print(const struct ip_addr_range *, enum afi,
char *, size_t);
int ip_addr_cmp(const struct ip_addr *, const struct ip_addr *);
int ip_addr_check_overlap(const struct cert_ip *,
- const char *, const struct cert_ip *, size_t);
+ const char *, const struct cert_ip *, size_t, int);
int ip_addr_check_covered(enum afi, const unsigned char *,
const unsigned char *, const struct cert_ip *, size_t);
int ip_cert_compose_ranges(struct cert_ip *);
void ip_roa_compose_ranges(struct roa_ip *);
+void ip_warn(const char *, const struct cert_ip *, const char *);
int sbgp_addr(const char *, struct cert_ip *, size_t *,
enum afi, const ASN1_BIT_STRING *);
@@ -730,9 +731,10 @@ int sbgp_parse_ipaddrblk(const char *, const IPAddrBlocks *,
int as_id_parse(const ASN1_INTEGER *, uint32_t *);
int as_check_overlap(const struct cert_as *, const char *,
- const struct cert_as *, size_t);
+ const struct cert_as *, size_t, int);
int as_check_covered(uint32_t, uint32_t,
const struct cert_as *, size_t);
+void as_warn(const char *, const struct cert_as *, const char *);
int sbgp_as_id(const char *, struct cert_as *, size_t *,
const ASN1_INTEGER *);
@@ -742,6 +744,12 @@ int sbgp_as_range(const char *, struct cert_as *, size_t *,
int sbgp_parse_assysnum(const char *, const ASIdentifiers *,
struct cert_as **, size_t *);
+/* Constraints-specific */
+void constraints_load(void);
+void constraints_unload(void);
+void constraints_parse(void);
+int constraints_validate(const char *, const struct cert *);
+
/* Parser-specific */
void entity_free(struct entity *);
void entity_read_req(struct ibuf *, struct entity *);
@@ -864,6 +872,10 @@ void aspa_print(const X509 *, const struct aspa *);
void tak_print(const X509 *, const struct tak *);
void geofeed_print(const X509 *, const struct geofeed *);
+/* Missing RFC 3779 API */
+IPAddrBlocks *IPAddrBlocks_new(void);
+void IPAddrBlocks_free(IPAddrBlocks *);
+
/* Output! */
extern int outformats;
diff --git a/usr.sbin/rpki-client/filemode.c b/usr.sbin/rpki-client/filemode.c
index 89844be6dc8..01fd1a26391 100644
--- a/usr.sbin/rpki-client/filemode.c
+++ b/usr.sbin/rpki-client/filemode.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: filemode.c,v 1.35 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: filemode.c,v 1.36 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -468,6 +468,17 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
break;
}
}
+ if (status && cert == NULL) {
+ struct cert *eecert;
+
+ eecert = cert_parse_ee_cert(file, a->cert->talid, x509);
+ if (eecert == NULL)
+ status = 0;
+ cert_free(eecert);
+ } else if (status) {
+ cert->talid = a->cert->talid;
+ status = constraints_validate(file, cert);
+ }
} else if (is_ta) {
if ((tal = find_tal(cert)) != NULL) {
cert = ta_parse(file, cert, tal->pkey, tal->pkeysz);
@@ -648,6 +659,7 @@ proc_filemode(int fd)
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
x509_init_oid();
+ constraints_parse();
if ((ctx = X509_STORE_CTX_new()) == NULL)
err(1, "X509_STORE_CTX_new");
diff --git a/usr.sbin/rpki-client/gbr.c b/usr.sbin/rpki-client/gbr.c
index 10322b40ebf..fab3c5a2562 100644
--- a/usr.sbin/rpki-client/gbr.c
+++ b/usr.sbin/rpki-client/gbr.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: gbr.c,v 1.28 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: gbr.c,v 1.29 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
*
@@ -88,7 +88,7 @@ gbr_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
goto out;
}
- if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL)
+ if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
goto out;
return p.res;
diff --git a/usr.sbin/rpki-client/geofeed.c b/usr.sbin/rpki-client/geofeed.c
index 4dbbe7c9dde..4cde5ceae19 100644
--- a/usr.sbin/rpki-client/geofeed.c
+++ b/usr.sbin/rpki-client/geofeed.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: geofeed.c,v 1.14 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: geofeed.c,v 1.15 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2022 Job Snijders <job@fastly.com>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -252,7 +252,7 @@ geofeed_parse(X509 **x509, const char *fn, int talid, char *buf, size_t len)
if (!x509_get_notafter(*x509, fn, &p.res->notafter))
goto out;
- if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL)
+ if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
goto out;
if (x509_any_inherits(*x509)) {
diff --git a/usr.sbin/rpki-client/ip.c b/usr.sbin/rpki-client/ip.c
index f5ff23b03f5..6d768610a08 100644
--- a/usr.sbin/rpki-client/ip.c
+++ b/usr.sbin/rpki-client/ip.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ip.c,v 1.28 2023/09/25 08:48:14 job Exp $ */
+/* $OpenBSD: ip.c,v 1.29 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
@@ -103,7 +103,7 @@ ip_addr_check_covered(enum afi afi,
*/
int
ip_addr_check_overlap(const struct cert_ip *ip, const char *fn,
- const struct cert_ip *ips, size_t ipsz)
+ const struct cert_ip *ips, size_t ipsz, int quiet)
{
size_t i, sz = ip->afi == AFI_IPV4 ? 4 : 16;
int inherit_v4 = 0, inherit_v6 = 0;
@@ -135,6 +135,8 @@ ip_addr_check_overlap(const struct cert_ip *ip, const char *fn,
ip->type == CERT_IP_INHERIT) ||
(has_v6 && ip->afi == AFI_IPV6 &&
ip->type == CERT_IP_INHERIT)) {
+ if (quiet)
+ return 0;
warnx("%s: RFC 3779 section 2.2.3.5: "
"cannot have multiple inheritance or inheritance and "
"addresses of the same class", fn);
@@ -151,6 +153,8 @@ ip_addr_check_overlap(const struct cert_ip *ip, const char *fn,
if (memcmp(ips[i].max, ip->min, sz) <= 0 ||
memcmp(ips[i].min, ip->max, sz) >= 0)
continue;
+ if (quiet)
+ return 0;
socktype = (ips[i].afi == AFI_IPV4) ? AF_INET : AF_INET6,
warnx("%s: RFC 3779 section 2.2.3.5: "
"cannot have overlapping IP addresses", fn);
@@ -342,3 +346,26 @@ ip_roa_compose_ranges(struct roa_ip *p)
if (sz > 0 && p->addr.prefixlen % 8 != 0)
p->max[sz - 1] |= (1 << (8 - p->addr.prefixlen % 8)) - 1;
}
+
+void
+ip_warn(const char *fn, const struct cert_ip *cert, const char *msg)
+{
+ char buf[128];
+
+ switch (cert->type) {
+ case CERT_IP_ADDR:
+ ip_addr_print(&cert->ip, cert->afi, buf, sizeof(buf));
+ warnx("%s: %s: %s", fn, buf, msg);
+ break;
+ case CERT_IP_INHERIT:
+ warnx("%s: (inherit): %s", fn, msg);
+ break;
+ case CERT_IP_RANGE:
+ ip_addr_range_print(&cert->range, cert->afi, buf, sizeof(buf));
+ warnx("%s: %s: %s", fn, buf, msg);
+ break;
+ default:
+ warnx("%s: corrupt cert", fn);
+ break;
+ }
+}
diff --git a/usr.sbin/rpki-client/main.c b/usr.sbin/rpki-client/main.c
index 6c39865e0b2..f91a9d69327 100644
--- a/usr.sbin/rpki-client/main.c
+++ b/usr.sbin/rpki-client/main.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: main.c,v 1.246 2023/08/30 10:02:28 job Exp $ */
+/* $OpenBSD: main.c,v 1.247 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -1094,6 +1094,9 @@ main(int argc, char *argv[])
if (talsz == 0)
err(1, "no TAL files found in %s", "/etc/rpki");
+ /* Load optional constraint files sitting next to the TALs. */
+ constraints_load();
+
/*
* Create the file reader as a jailed child process.
* It will be responsible for reading all of the files (ROAs,
@@ -1108,6 +1111,9 @@ main(int argc, char *argv[])
proc_filemode(proc);
}
+ /* Constraints are only needed in the filemode and parser processes. */
+ constraints_unload();
+
/*
* Create a process that will do the rsync'ing.
* This process is responsible for making sure that all the
diff --git a/usr.sbin/rpki-client/mft.c b/usr.sbin/rpki-client/mft.c
index 86f8e4c490a..0e4af6e1fdc 100644
--- a/usr.sbin/rpki-client/mft.c
+++ b/usr.sbin/rpki-client/mft.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mft.c,v 1.98 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: mft.c,v 1.99 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -428,7 +428,7 @@ mft_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
if (mft_parse_econtent(cms, cmsz, &p) == 0)
goto out;
- if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL)
+ if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
goto out;
if (p.res->signtime > p.res->nextupdate) {
diff --git a/usr.sbin/rpki-client/parser.c b/usr.sbin/rpki-client/parser.c
index 0c867da1aa5..8e4abdc1da7 100644
--- a/usr.sbin/rpki-client/parser.c
+++ b/usr.sbin/rpki-client/parser.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: parser.c,v 1.99 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: parser.c,v 1.100 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -441,6 +441,13 @@ proc_parser_cert(char *file, const unsigned char *der, size_t len,
cert->talid = a->cert->talid;
+ if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
+ if (!constraints_validate(file, cert)) {
+ cert_free(cert);
+ return NULL;
+ }
+ }
+
/*
* Add validated CA certs to the RPKI auth tree.
*/
@@ -813,6 +820,7 @@ proc_parser(int fd)
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
x509_init_oid();
+ constraints_parse();
if ((ctx = X509_STORE_CTX_new()) == NULL)
err(1, "X509_STORE_CTX_new");
diff --git a/usr.sbin/rpki-client/rfc3779.c b/usr.sbin/rpki-client/rfc3779.c
new file mode 100644
index 00000000000..071dae8e7f0
--- /dev/null
+++ b/usr.sbin/rpki-client/rfc3779.c
@@ -0,0 +1,52 @@
+/* $OpenBSD: rfc3779.c,v 1.1 2023/10/13 12:06:49 job 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 <err.h>
+#include <stddef.h>
+
+#include <openssl/x509v3.h>
+
+#include "extern.h"
+
+/*
+ * These should really have been part of the public OpenSSL RFC 3779 API...
+ */
+
+IPAddrBlocks *
+IPAddrBlocks_new(void)
+{
+ IPAddrBlocks *addrs;
+
+ /*
+ * XXX The comparison function IPAddressFamily_cmp() isn't public.
+ * Install it using a side effect of the lovely X509v3_addr_canonize().
+ */
+ if ((addrs = sk_IPAddressFamily_new_null()) == NULL)
+ return NULL;
+ if (!X509v3_addr_canonize(addrs)) {
+ IPAddrBlocks_free(addrs);
+ return NULL;
+ }
+
+ return addrs;
+}
+
+void
+IPAddrBlocks_free(IPAddrBlocks *addr)
+{
+ sk_IPAddressFamily_pop_free(addr, IPAddressFamily_free);
+}
diff --git a/usr.sbin/rpki-client/roa.c b/usr.sbin/rpki-client/roa.c
index 01f25bd8ee1..93838d120a0 100644
--- a/usr.sbin/rpki-client/roa.c
+++ b/usr.sbin/rpki-client/roa.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: roa.c,v 1.70 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: roa.c,v 1.71 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -257,7 +257,7 @@ roa_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
goto out;
}
- if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL)
+ if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
goto out;
if (cert->asz > 0) {
diff --git a/usr.sbin/rpki-client/rpki-client.8 b/usr.sbin/rpki-client/rpki-client.8
index 0644f33094b..8fd47548f51 100644
--- a/usr.sbin/rpki-client/rpki-client.8
+++ b/usr.sbin/rpki-client/rpki-client.8
@@ -1,4 +1,4 @@
-.\" $OpenBSD: rpki-client.8,v 1.97 2023/06/26 18:39:53 job Exp $
+.\" $OpenBSD: rpki-client.8,v 1.98 2023/10/13 12:06:49 job Exp $
.\"
.\" Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
.\"
@@ -14,7 +14,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: June 26 2023 $
+.Dd $Mdocdate: October 13 2023 $
.Dt RPKI-CLIENT 8
.Os
.Sh NAME
@@ -251,6 +251,44 @@ should be run hourly by
use
.Xr crontab 1
to uncomment the entry in root's crontab.
+.Sh TRUST ANCHOR CONSTRAINTS
+.Nm
+can impose locally configured
+.Em constraints
+on cryptographic products subordinate to publicly-trusted
+.Em Trust Anchors .
+.Pp
+Constraining a Trust Anchor's effective signing authority to a limited set of
+.Em Internet Number Resources
+allows Relying Parties to take advantage of the potential benefits of
+assuming trust, while deriving trust within a bounded scope.
+.Pp
+Each
+.Em .constraints
+file imposes constraints on the Trust Anchor reachable via the same-named
+.Em .tal
+file.
+One entry per line.
+Entries can be IP prefixes, IP address ranges, AS identifiers, or AS identifier ranges.
+Ranges are a minimum and maximum separated by a hyphen
+.Pq Sq - .
+Comments can be put anywhere in the file using a hash mark
+.Pq Sq # ,
+and extend to the end of the current line.
+.Em deny
+entries may not overlap with other
+.Em deny
+entries.
+.Em allow
+entries may not overlap with other
+.Em allow
+entries.
+.Pp
+A given EE certificate's resources may not overlap with any
+.Em deny
+entry, and must be fully contained within the
+.Em allow
+entries.
.Sh ENVIRONMENT
.Nm
utilizes the following environment variables:
@@ -264,6 +302,10 @@ URL of HTTP proxy to use.
default TAL files used unless
.Fl t Ar tal
is specified.
+.It Pa /etc/rpki/*.constraints
+files containing registry-specific constraints to restrict what IP addresses
+and AS identifiers may or may not appear in EE certificates subordinate to the
+same-named Trust Anchor.
.It Pa /etc/rpki/skiplist
default skiplist file, unless
.Fl S Ar skiplist
@@ -397,6 +439,12 @@ agreement regarding ARIN service restrictions.
.%U https://datatracker.ietf.org/doc/html/draft-spaghetti-sidrops-cms-signing-time
.%D June, 2023
.Re
+.Pp
+.Rs
+.%T Constraining RPKI Trust Anchors
+.%U https://datatracker.ietf.org/doc/html/draft-snijders-constraining-rpki-trust-anchors
+.%D September, 2023
+.Re
.Sh HISTORY
.Nm
first appeared in
diff --git a/usr.sbin/rpki-client/rsc.c b/usr.sbin/rpki-client/rsc.c
index 09ee0ee1ca5..c0fd9d8dfb0 100644
--- a/usr.sbin/rpki-client/rsc.c
+++ b/usr.sbin/rpki-client/rsc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: rsc.c,v 1.28 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: rsc.c,v 1.29 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
* Copyright (c) 2022 Job Snijders <job@fastly.com>
@@ -423,7 +423,7 @@ rsc_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
if (!rsc_parse_econtent(cms, cmsz, &p))
goto out;
- if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL)
+ if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
goto out;
p.res->valid = valid_rsc(fn, cert, p.res);
diff --git a/usr.sbin/rpki-client/tak.c b/usr.sbin/rpki-client/tak.c
index 6978934f09d..a3f0934e39e 100644
--- a/usr.sbin/rpki-client/tak.c
+++ b/usr.sbin/rpki-client/tak.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: tak.c,v 1.12 2023/09/25 11:08:45 tb Exp $ */
+/* $OpenBSD: tak.c,v 1.13 2023/10/13 12:06:49 job Exp $ */
/*
* Copyright (c) 2022 Job Snijders <job@fastly.com>
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
@@ -274,7 +274,7 @@ tak_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
if (!tak_parse_econtent(cms, cmsz, &p))
goto out;
- if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL)
+ if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
goto out;
if (strcmp(p.res->aki, p.res->current->ski) != 0) {