Allow imposing constraints on RPKI trust anchors
authorjob <job@openbsd.org>
Fri, 13 Oct 2023 12:06:49 +0000 (12:06 +0000)
committerjob <job@openbsd.org>
Fri, 13 Oct 2023 12:06:49 +0000 (12:06 +0000)
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@

18 files changed:
usr.sbin/rpki-client/Makefile
usr.sbin/rpki-client/as.c
usr.sbin/rpki-client/aspa.c
usr.sbin/rpki-client/cert.c
usr.sbin/rpki-client/constraints.c [new file with mode: 0644]
usr.sbin/rpki-client/extern.h
usr.sbin/rpki-client/filemode.c
usr.sbin/rpki-client/gbr.c
usr.sbin/rpki-client/geofeed.c
usr.sbin/rpki-client/ip.c
usr.sbin/rpki-client/main.c
usr.sbin/rpki-client/mft.c
usr.sbin/rpki-client/parser.c
usr.sbin/rpki-client/rfc3779.c [new file with mode: 0644]
usr.sbin/rpki-client/roa.c
usr.sbin/rpki-client/rpki-client.8
usr.sbin/rpki-client/rsc.c
usr.sbin/rpki-client/tak.c

index 660d453..edb66b6 100644 (file)
@@ -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
index 2f4aabd..dd80395 100644 (file)
@@ -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;
+       }
+}
index fc4a292..8d81c16 100644 (file)
@@ -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);
index 7c8e07f..6e370bf 100644 (file)
@@ -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 (file)
index 0000000..226e249
--- /dev/null
@@ -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;
+}
index 53ff444..52df7f8 100644 (file)
@@ -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;
index 89844be..01fd1a2 100644 (file)
@@ -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");
index 10322b4..fab3c5a 100644 (file)
@@ -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;
index 4dbbe7c..4cde5ce 100644 (file)
@@ -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)) {
index f5ff23b..6d76861 100644 (file)
@@ -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;
+       }
+}
index 6c39865..f91a9d6 100644 (file)
@@ -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
index 86f8e4c..0e4af6e 100644 (file)
@@ -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) {
index 0c867da..8e4abdc 100644 (file)
@@ -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 (file)
index 0000000..071dae8
--- /dev/null
@@ -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);
+}
index 01f25bd..93838d1 100644 (file)
@@ -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) {
index 0644f33..8fd4754 100644 (file)
@@ -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
index 09ee0ee..c0fd9d8 100644 (file)
@@ -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);
index 6978934..a3f0934 100644 (file)
@@ -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) {