diff options
author | Job Snijders <job@cvs.openbsd.org> | 2023-10-13 12:06:50 +0000 |
---|---|---|
committer | Job Snijders <job@cvs.openbsd.org> | 2023-10-13 12:06:50 +0000 |
commit | a5ed47bf0aedb7c2d81adf1f68f504ecdb3dea82 (patch) | |
tree | de0e309db2e58d0a3150280c625647dd9e6b1546 | |
parent | 30cf777d52de1af041e49d9913f534b87d6c31d6 (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/Makefile | 14 | ||||
-rw-r--r-- | usr.sbin/rpki-client/as.c | 34 | ||||
-rw-r--r-- | usr.sbin/rpki-client/aspa.c | 4 | ||||
-rw-r--r-- | usr.sbin/rpki-client/cert.c | 19 | ||||
-rw-r--r-- | usr.sbin/rpki-client/constraints.c | 600 | ||||
-rw-r--r-- | usr.sbin/rpki-client/extern.h | 20 | ||||
-rw-r--r-- | usr.sbin/rpki-client/filemode.c | 14 | ||||
-rw-r--r-- | usr.sbin/rpki-client/gbr.c | 4 | ||||
-rw-r--r-- | usr.sbin/rpki-client/geofeed.c | 4 | ||||
-rw-r--r-- | usr.sbin/rpki-client/ip.c | 31 | ||||
-rw-r--r-- | usr.sbin/rpki-client/main.c | 8 | ||||
-rw-r--r-- | usr.sbin/rpki-client/mft.c | 4 | ||||
-rw-r--r-- | usr.sbin/rpki-client/parser.c | 10 | ||||
-rw-r--r-- | usr.sbin/rpki-client/rfc3779.c | 52 | ||||
-rw-r--r-- | usr.sbin/rpki-client/roa.c | 4 | ||||
-rw-r--r-- | usr.sbin/rpki-client/rpki-client.8 | 52 | ||||
-rw-r--r-- | usr.sbin/rpki-client/rsc.c | 4 | ||||
-rw-r--r-- | usr.sbin/rpki-client/tak.c | 4 |
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) { |