summaryrefslogtreecommitdiff
path: root/usr.sbin/rpki-client
diff options
context:
space:
mode:
authorJob Snijders <job@cvs.openbsd.org>2022-11-26 12:02:38 +0000
committerJob Snijders <job@cvs.openbsd.org>2022-11-26 12:02:38 +0000
commit4ea2a921404cdda1ff1bad4f43ab63d1285d0dc9 (patch)
tree210c0772e4c9c9829a112f97740091a5379f46a5 /usr.sbin/rpki-client
parentd1d229ade7b80ff97c085161ad43e70f187f28bd (diff)
Add support for authenticating geofeed data CSV files in filemode
RFC 9092 describes a scheme in which an authenticator is appended to a geofeed (RFC 8805) file. It is a digest of the main body of the file signed by the private key of the relevant RPKI certificate for a covering address range. The authenticator is a detached CMS signature. with and OK tb@
Diffstat (limited to 'usr.sbin/rpki-client')
-rw-r--r--usr.sbin/rpki-client/Makefile12
-rw-r--r--usr.sbin/rpki-client/cms.c82
-rw-r--r--usr.sbin/rpki-client/extern.h32
-rw-r--r--usr.sbin/rpki-client/filemode.c15
-rw-r--r--usr.sbin/rpki-client/geofeed.c297
-rw-r--r--usr.sbin/rpki-client/mft.c4
-rw-r--r--usr.sbin/rpki-client/print.c48
-rw-r--r--usr.sbin/rpki-client/rpki-client.86
-rw-r--r--usr.sbin/rpki-client/validate.c27
-rw-r--r--usr.sbin/rpki-client/x509.c7
10 files changed, 490 insertions, 40 deletions
diff --git a/usr.sbin/rpki-client/Makefile b/usr.sbin/rpki-client/Makefile
index 37393fbb3f4..052ee49d035 100644
--- a/usr.sbin/rpki-client/Makefile
+++ b/usr.sbin/rpki-client/Makefile
@@ -1,11 +1,11 @@
-# $OpenBSD: Makefile,v 1.27 2022/11/02 12:43:02 job Exp $
+# $OpenBSD: Makefile,v 1.28 2022/11/26 12:02:36 job Exp $
PROG= rpki-client
-SRCS= as.c aspa.c cert.c cms.c crl.c encoding.c filemode.c gbr.c http.c io.c \
- ip.c log.c main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \
- output-csv.c output-json.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 crl.c encoding.c filemode.c gbr.c geofeed.c \
+ http.c io.c ip.c log.c main.c mft.c mkdir.c output.c output-bgpd.c \
+ output-bird.c output-csv.c output-json.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
MAN= rpki-client.8
LDADD+= -lexpat -ltls -lssl -lcrypto -lutil
diff --git a/usr.sbin/rpki-client/cms.c b/usr.sbin/rpki-client/cms.c
index ed5e2bf8b72..6e0b334e326 100644
--- a/usr.sbin/rpki-client/cms.c
+++ b/usr.sbin/rpki-client/cms.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: cms.c,v 1.21 2022/08/12 13:19:02 tb Exp $ */
+/* $OpenBSD: cms.c,v 1.22 2022/11/26 12:02:36 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
@@ -23,6 +23,7 @@
#include <string.h>
#include <unistd.h>
+#include <openssl/bio.h>
#include <openssl/cms.h>
#include "extern.h"
@@ -32,38 +33,32 @@ extern ASN1_OBJECT *msg_dgst_oid;
extern ASN1_OBJECT *sign_time_oid;
extern ASN1_OBJECT *bin_sign_time_oid;
-/*
- * Parse and validate a self-signed CMS message, where the signing X509
- * certificate has been hashed to dgst (optional).
- * Conforms to RFC 6488.
- * The eContentType of the message must be an oid object.
- * Return the eContent as a string and set "rsz" to be its length.
- */
-unsigned char *
-cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
- size_t derlen, const ASN1_OBJECT *oid, size_t *rsz)
+static int
+cms_parse_validate_internal(X509 **xp, const char *fn, const unsigned char *der,
+ size_t derlen, const ASN1_OBJECT *oid, BIO *bio, unsigned char **res,
+ size_t *rsz)
{
char buf[128], obuf[128];
const ASN1_OBJECT *obj, *octype;
ASN1_OCTET_STRING **os = NULL, *kid = NULL;
CMS_ContentInfo *cms;
- int rc = 0;
STACK_OF(X509) *certs = NULL;
STACK_OF(X509_CRL) *crls;
STACK_OF(CMS_SignerInfo) *sinfos;
CMS_SignerInfo *si;
X509_ALGOR *pdig, *psig;
- unsigned char *res = NULL;
int i, nattrs, nid;
int has_ct = 0, has_md = 0, has_st = 0,
has_bst = 0;
+ int rc = 0;
- *rsz = 0;
*xp = NULL;
+ if (rsz != NULL)
+ *rsz = 0;
/* just fail for empty buffers, the warning was printed elsewhere */
if (der == NULL)
- return NULL;
+ return 0;
if ((cms = d2i_CMS_ContentInfo(NULL, &der, derlen)) == NULL) {
cryptowarnx("%s: RFC 6488: failed CMS parse", fn);
@@ -74,10 +69,9 @@ cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
* The CMS is self-signed with a signing certifiate.
* Verify that the self-signage is correct.
*/
-
- if (!CMS_verify(cms, NULL, NULL, NULL, NULL,
+ if (!CMS_verify(cms, NULL, NULL, bio, NULL,
CMS_NO_SIGNER_CERT_VERIFY)) {
- cryptowarnx("%s: RFC 6488: CMS not self-signed", fn);
+ cryptowarnx("%s: CMS verification error", fn);
goto out;
}
@@ -244,7 +238,14 @@ cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
goto out;
}
- /* Verify that we have eContent to disseminate. */
+ /*
+ * In the detached sig case: there won't be eContent to extract, so
+ * jump to out.
+ */
+ if (res == NULL) {
+ rc = 1;
+ goto out;
+ }
if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) {
warnx("%s: RFC 6488 section 2.1.4: "
@@ -258,21 +259,50 @@ cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
* this information; and since we're going to d2i it anyway,
* simply pass it as the desired underlying types.
*/
-
- if ((res = malloc((*os)->length)) == NULL)
+ if ((*res = malloc((*os)->length)) == NULL)
err(1, NULL);
- memcpy(res, (*os)->data, (*os)->length);
+ memcpy(*res, (*os)->data, (*os)->length);
*rsz = (*os)->length;
rc = 1;
-out:
- sk_X509_free(certs);
- CMS_ContentInfo_free(cms);
-
+ out:
if (rc == 0) {
X509_free(*xp);
*xp = NULL;
}
+ sk_X509_free(certs);
+ CMS_ContentInfo_free(cms);
+ return rc;
+}
+
+/*
+ * Parse and validate a self-signed CMS message.
+ * Conforms to RFC 6488.
+ * The eContentType of the message must be an oid object.
+ * Return the eContent as a string and set "rsz" to be its length.
+ */
+unsigned char *
+cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
+ size_t derlen, const ASN1_OBJECT *oid, size_t *rsz)
+{
+ unsigned char *res = NULL;
+
+ if (!cms_parse_validate_internal(xp, fn, der, derlen, oid, NULL, &res,
+ rsz))
+ return NULL;
return res;
}
+
+/*
+ * Parse and validate a detached CMS signature.
+ * bio must contain the original message, der must contain the CMS.
+ * Return the 1 on success, 0 on failure.
+ */
+int
+cms_parse_validate_detached(X509 **xp, const char *fn, const unsigned char *der,
+ size_t derlen, const ASN1_OBJECT *oid, BIO *bio)
+{
+ return cms_parse_validate_internal(xp, fn, der, derlen, oid, bio, NULL,
+ NULL);
+}
diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h
index df65e490a19..d303bdd9092 100644
--- a/usr.sbin/rpki-client/extern.h
+++ b/usr.sbin/rpki-client/extern.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: extern.h,v 1.160 2022/11/18 14:38:34 tb Exp $ */
+/* $OpenBSD: extern.h,v 1.161 2022/11/26 12:02:36 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
@@ -175,6 +175,7 @@ enum rtype {
RTYPE_RSC,
RTYPE_ASPA,
RTYPE_TAK,
+ RTYPE_GEOFEED,
};
enum location {
@@ -297,6 +298,27 @@ struct tak {
};
/*
+ * A single geofeed record
+ */
+struct geoip {
+ struct cert_ip *ip;
+ char *loc;
+};
+
+/*
+ * A geofeed file
+ */
+struct geofeed {
+ struct geoip *geoips; /* Prefix + location entry in the CSV */
+ size_t geoipsz; /* number of IPs */
+ char *aia; /* AIA */
+ char *aki; /* AKI */
+ char *ski; /* SKI */
+ time_t expires; /* Not After of the Geofeed EE */
+ int valid; /* all resources covered */
+};
+
+/*
* A single Ghostbuster record
*/
struct gbr {
@@ -565,6 +587,9 @@ void gbr_free(struct gbr *);
struct gbr *gbr_parse(X509 **, const char *, const unsigned char *,
size_t);
+void geofeed_free(struct geofeed *);
+struct geofeed *geofeed_parse(X509 **, const char *, char *, size_t);
+
void rsc_free(struct rsc *);
struct rsc *rsc_parse(X509 **, const char *, const unsigned char *,
size_t);
@@ -608,11 +633,15 @@ int valid_x509(char *, X509_STORE_CTX *, X509 *, struct auth *,
int valid_rsc(const char *, struct cert *, struct rsc *);
int valid_econtent_version(const char *, const ASN1_INTEGER *);
int valid_aspa(const char *, struct cert *, struct aspa *);
+int valid_geofeed(const char *, struct cert *, struct geofeed *);
/* Working with CMS. */
unsigned char *cms_parse_validate(X509 **, const char *,
const unsigned char *, size_t,
const ASN1_OBJECT *, size_t *);
+int cms_parse_validate_detached(X509 **, const char *,
+ const unsigned char *, size_t,
+ const ASN1_OBJECT *, BIO *);
/* Work with RFC 3779 IP addresses, prefixes, ranges. */
@@ -759,6 +788,7 @@ void gbr_print(const X509 *, const struct gbr *);
void rsc_print(const X509 *, const struct rsc *);
void aspa_print(const X509 *, const struct aspa *);
void tak_print(const X509 *, const struct tak *);
+void geofeed_print(const X509 *, const struct geofeed *);
/* Output! */
diff --git a/usr.sbin/rpki-client/filemode.c b/usr.sbin/rpki-client/filemode.c
index 99ac65d7d3b..2b3042163ff 100644
--- a/usr.sbin/rpki-client/filemode.c
+++ b/usr.sbin/rpki-client/filemode.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: filemode.c,v 1.16 2022/11/04 17:39:36 job Exp $ */
+/* $OpenBSD: filemode.c,v 1.17 2022/11/26 12:02:37 job Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -270,6 +270,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
struct rsc *rsc = NULL;
struct aspa *aspa = NULL;
struct tak *tak = NULL;
+ struct geofeed *geofeed = NULL;
char *aia = NULL, *aki = NULL;
char filehash[SHA256_DIGEST_LENGTH];
char *hash;
@@ -385,6 +386,14 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
aia = tak->aia;
aki = tak->aki;
break;
+ case RTYPE_GEOFEED:
+ geofeed = geofeed_parse(&x509, file, buf, len);
+ if (geofeed == NULL)
+ break;
+ geofeed_print(x509, geofeed);
+ aia = geofeed->aia;
+ aki = geofeed->aki;
+ break;
default:
printf("%s: unsupported file type\n", file);
break;
@@ -420,6 +429,9 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
case RTYPE_ASPA:
status = aspa->valid;
break;
+ case RTYPE_GEOFEED:
+ status = geofeed->valid;
+ break;
default:
break;
}
@@ -479,6 +491,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
rsc_free(rsc);
aspa_free(aspa);
tak_free(tak);
+ geofeed_free(geofeed);
}
/*
diff --git a/usr.sbin/rpki-client/geofeed.c b/usr.sbin/rpki-client/geofeed.c
new file mode 100644
index 00000000000..b5838919b98
--- /dev/null
+++ b/usr.sbin/rpki-client/geofeed.c
@@ -0,0 +1,297 @@
+/* $OpenBSD: geofeed.c,v 1.1 2022/11/26 12:02:37 job Exp $ */
+/*
+ * Copyright (c) 2022 Job Snijders <job@fastly.com>
+ * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <vis.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <openssl/bio.h>
+#include <openssl/x509.h>
+
+#include "extern.h"
+
+struct parse {
+ const char *fn;
+ struct geofeed *res;
+};
+
+extern ASN1_OBJECT *geofeed_oid;
+
+/*
+ * Take a CIDR prefix (in presentation format) and add it to parse results.
+ * Returns 1 on success, 0 on failure.
+ */
+static int
+geofeed_parse_geoip(struct geofeed *res, char *cidr, char *loc)
+{
+ struct geoip *geoip;
+ struct ip_addr *ipaddr;
+ enum afi afi;
+ int plen;
+
+ if ((ipaddr = calloc(1, sizeof(struct ip_addr))) == NULL)
+ err(1, NULL);
+
+ if ((plen = inet_net_pton(AF_INET, cidr, ipaddr->addr,
+ sizeof(ipaddr->addr))) != -1)
+ afi = AFI_IPV4;
+ else if ((plen = inet_net_pton(AF_INET6, cidr, ipaddr->addr,
+ sizeof(ipaddr->addr))) != -1)
+ afi = AFI_IPV6;
+ else {
+ static char buf[80];
+
+ if (strnvis(buf, cidr, sizeof(buf), VIS_SAFE)
+ >= (int)sizeof(buf)) {
+ memcpy(buf + sizeof(buf) - 4, "...", 4);
+ }
+ warnx("invalid address: %s", buf);
+ free(ipaddr);
+ return 0;
+ }
+
+ ipaddr->prefixlen = plen;
+
+ res->geoips = recallocarray(res->geoips, res->geoipsz,
+ res->geoipsz + 1, sizeof(struct geoip));
+ if (res->geoips == NULL)
+ err(1, NULL);
+ geoip = &res->geoips[res->geoipsz++];
+
+ if ((geoip->ip = calloc(1, sizeof(struct cert_ip))) == NULL)
+ err(1, NULL);
+
+ geoip->ip->type = CERT_IP_ADDR;
+ geoip->ip->ip = *ipaddr;
+ geoip->ip->afi = afi;
+
+ if ((geoip->loc = strdup(loc)) == NULL)
+ err(1, NULL);
+
+ if (!ip_cert_compose_ranges(geoip->ip))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Parse a full RFC 9092 file.
+ * Returns the Geofeed, or NULL if the object was malformed.
+ */
+struct geofeed *
+geofeed_parse(X509 **x509, const char *fn, char *buf, size_t len)
+{
+ struct parse p;
+ char *delim, *line, *loc, *nl;
+ size_t linelen;
+ BIO *bio;
+ char *b64 = NULL;
+ size_t b64sz;
+ unsigned char *der = NULL;
+ size_t dersz;
+ const ASN1_TIME *at;
+ struct cert *cert = NULL;
+ int rpki_signature_seen = 0, end_signature_seen = 0;
+ int rc = 0;
+
+ bio = BIO_new(BIO_s_mem());
+
+ memset(&p, 0, sizeof(struct parse));
+ p.fn = fn;
+
+ if ((p.res = calloc(1, sizeof(struct geofeed))) == NULL)
+ err(1, NULL);
+
+ if ((b64 = calloc(1, len)) == NULL)
+ err(1, NULL);
+ b64sz = len;
+
+ while ((nl = memchr(buf, '\n', len)) != NULL) {
+ line = buf;
+
+ /* advance buffer to next line */
+ len -= nl + 1 - buf;
+ buf = nl + 1;
+
+ /* replace LF and CR with NUL, point nl at first NUL */
+ *nl = '\0';
+ if (nl > line && nl[-1] == '\r') {
+ nl[-1] = '\0';
+ nl--;
+ linelen = nl - line;
+ } else {
+ warnx("%s: malformed file, expected CRLF line"
+ " endings", fn);
+ goto out;
+ }
+
+ if (end_signature_seen) {
+ warnx("%s: trailing data after signature section", fn);
+ goto out;
+ }
+
+ if (strncmp(line, "# End Signature:",
+ strlen("# End Signature:")) == 0) {
+ end_signature_seen = 1;
+ continue;
+ }
+
+ if (rpki_signature_seen) {
+ if (linelen > 74) {
+ warnx("%s: line in signature section too long",
+ fn);
+ goto out;
+ }
+ if (strncmp(line, "# ", strlen("# ")) != 0) {
+ warnx("%s: line in signature section too "
+ "short", fn);
+ goto out;
+ }
+
+ /* skip over "# " */
+ line += 2;
+ strlcat(b64, line, b64sz);
+ continue;
+ }
+
+ if (strncmp(line, "# RPKI Signature:",
+ strlen("# RPKI Signature:")) == 0) {
+ rpki_signature_seen = 1;
+ continue;
+ }
+
+ /*
+ * Read the Geofeed CSV records into a BIO to later on
+ * calculate the message digest and compare with the one
+ * in the detached CMS signature.
+ */
+ if (BIO_puts(bio, line) <= 0 || BIO_puts(bio, "\r\n") <= 0) {
+ warnx("%s: BIO_puts failed", fn);
+ goto out;
+ }
+
+ /* Skip empty lines or commented lines. */
+ if (linelen == 0 || line[0] == '#')
+ continue;
+
+ /* zap comments */
+ delim = memchr(line, '#', linelen);
+ if (delim != NULL)
+ *delim = '\0';
+
+ /* Split prefix and location info */
+ delim = memchr(line, ',', linelen);
+ if (delim != NULL) {
+ *delim = '\0';
+ loc = delim + 1;
+ } else
+ loc = "";
+
+ /* read each prefix */
+ if (!geofeed_parse_geoip(p.res, line, loc))
+ goto out;
+ }
+
+ if (!rpki_signature_seen || !end_signature_seen) {
+ warnx("%s: absent or invalid signature", fn);
+ goto out;
+ }
+
+ if ((base64_decode(b64, strlen(b64), &der, &dersz)) == -1) {
+ warnx("%s: base64_decode failed", fn);
+ goto out;
+ }
+
+ if (!cms_parse_validate_detached(x509, fn, der, dersz, geofeed_oid,
+ bio))
+ goto out;
+
+ if (!x509_get_aia(*x509, fn, &p.res->aia))
+ goto out;
+ if (!x509_get_aki(*x509, fn, &p.res->aki))
+ goto out;
+ if (!x509_get_ski(*x509, fn, &p.res->ski))
+ goto out;
+
+ if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) {
+ warnx("%s: missing AIA, AKI, SIA, or SKI X509 extension", fn);
+ goto out;
+ }
+
+ at = X509_get0_notAfter(*x509);
+ if (at == NULL) {
+ warnx("%s: X509_get0_notAfter failed", fn);
+ goto out;
+ }
+ if (!x509_get_time(at, &p.res->expires)) {
+ warnx("%s: ASN1_time_parse failed", fn);
+ goto out;
+ }
+
+ if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL)
+ goto out;
+
+ if (cert->asz > 0) {
+ warnx("%s: superfluous AS Resources extension present", fn);
+ goto out;
+ }
+
+ p.res->valid = valid_geofeed(fn, cert, p.res);
+
+ rc = 1;
+ out:
+ if (rc == 0) {
+ geofeed_free(p.res);
+ p.res = NULL;
+ X509_free(*x509);
+ *x509 = NULL;
+ }
+ cert_free(cert);
+ BIO_free(bio);
+ free(b64);
+ free(der);
+
+ return p.res;
+}
+
+/*
+ * Free what follows a pointer to a geofeed structure.
+ * Safe to call with NULL.
+ */
+void
+geofeed_free(struct geofeed *p)
+{
+ size_t i;
+
+ if (p == NULL)
+ return;
+
+ for (i = 0; i < p->geoipsz; i++) {
+ free(p->geoips[i].ip);
+ free(p->geoips[i].loc);
+ }
+
+ free(p->geoips);
+ free(p->aia);
+ free(p->aki);
+ free(p->ski);
+ free(p);
+}
diff --git a/usr.sbin/rpki-client/mft.c b/usr.sbin/rpki-client/mft.c
index 097ec7a6691..ed9cd187120 100644
--- a/usr.sbin/rpki-client/mft.c
+++ b/usr.sbin/rpki-client/mft.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mft.c,v 1.78 2022/11/07 16:23:32 job Exp $ */
+/* $OpenBSD: mft.c,v 1.79 2022/11/26 12:02:37 job Exp $ */
/*
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -170,6 +170,8 @@ rtype_from_file_extension(const char *fn)
return RTYPE_ASPA;
if (strcasecmp(fn + sz - 4, ".tak") == 0)
return RTYPE_TAK;
+ if (strcasecmp(fn + sz - 4, ".csv") == 0)
+ return RTYPE_GEOFEED;
return RTYPE_INVALID;
}
diff --git a/usr.sbin/rpki-client/print.c b/usr.sbin/rpki-client/print.c
index 8848b819b13..64fbd302179 100644
--- a/usr.sbin/rpki-client/print.c
+++ b/usr.sbin/rpki-client/print.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: print.c,v 1.20 2022/11/16 08:57:38 job Exp $ */
+/* $OpenBSD: print.c,v 1.21 2022/11/26 12:02:37 job Exp $ */
/*
* Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -723,3 +723,49 @@ tak_print(const X509 *x, const struct tak *p)
if (outformats & FORMAT_JSON)
printf("\n\t],\n");
}
+
+void
+geofeed_print(const X509 *x, const struct geofeed *p)
+{
+ char buf[128];
+ size_t i;
+
+ if (outformats & FORMAT_JSON) {
+ printf("\t\"type\": \"geofeed\",\n");
+ printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
+ x509_print(x);
+ printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
+ printf("\t\"aia\": \"%s\",\n", p->aia);
+ printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
+ printf("\t\"records\": [\n");
+ } else {
+ printf("Subject key identifier: %s\n", pretty_key_id(p->ski));
+ x509_print(x);
+ printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
+ printf("Authority info access: %s\n", p->aia);
+ printf("Geofeed valid until: %s\n", time2str(p->expires));
+ printf("Geofeed CSV records:\n");
+ }
+
+ for (i = 0; i < p->geoipsz; i++) {
+ if (p->geoips[i].ip->type != CERT_IP_ADDR)
+ continue;
+
+ ip_addr_print(&p->geoips[i].ip->ip, p->geoips[i].ip->afi, buf,
+ sizeof(buf));
+ if (outformats & FORMAT_JSON)
+ printf("\t\t{ \"prefix\": \"%s\", \"location\": \"%s\""
+ "}", buf, p->geoips[i].loc);
+ else
+ printf("%5zu: IP: %s (%s)", i + 1, buf,
+ p->geoips[i].loc);
+
+ if (outformats & FORMAT_JSON && i + 1 < p->geoipsz)
+ printf(",\n");
+ else
+ printf("\n");
+ }
+
+ if (outformats & FORMAT_JSON)
+ printf("\t],\n");
+}
diff --git a/usr.sbin/rpki-client/rpki-client.8 b/usr.sbin/rpki-client/rpki-client.8
index 39a970df370..ccdbb114ee1 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.80 2022/11/17 20:49:38 job Exp $
+.\" $OpenBSD: rpki-client.8,v 1.81 2022/11/26 12:02:37 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: November 17 2022 $
+.Dd $Mdocdate: November 26 2022 $
.Dt RPKI-CLIENT 8
.Os
.Sh NAME
@@ -313,6 +313,8 @@ A Profile for BGPsec Router Certificates, Certificate Revocation Lists, and
Certification Requests.
.It RFC 8630
Resource Public Key Infrastructure (RPKI) Trust Anchor Locator.
+.It RFC 9092
+Finding and Using Geofeed Data.
.It RFC 9323
A Profile for RPKI Signed Checklists (RSCs).
.It draft-ietf-sidrops-aspa-profile-10
diff --git a/usr.sbin/rpki-client/validate.c b/usr.sbin/rpki-client/validate.c
index 71d08236084..d7623808704 100644
--- a/usr.sbin/rpki-client/validate.c
+++ b/usr.sbin/rpki-client/validate.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: validate.c,v 1.46 2022/11/02 11:28:36 tb Exp $ */
+/* $OpenBSD: validate.c,v 1.47 2022/11/26 12:02:37 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
@@ -523,3 +523,28 @@ valid_aspa(const char *fn, struct cert *cert, struct aspa *aspa)
return 0;
}
+
+/*
+ * Validate Geofeed prefixes: check that the prefixes are contained.
+ * Returns 1 if valid, 0 otherwise.
+ */
+int
+valid_geofeed(const char *fn, struct cert *cert, struct geofeed *g)
+{
+ size_t i;
+ char buf[64];
+
+ for (i = 0; i < g->geoipsz; i++) {
+ if (ip_addr_check_covered(g->geoips[i].ip->afi,
+ g->geoips[i].ip->min, g->geoips[i].ip->max, cert->ips,
+ cert->ipsz) > 0)
+ continue;
+
+ ip_addr_print(&g->geoips[i].ip->ip, g->geoips[i].ip->afi, buf,
+ sizeof(buf));
+ warnx("%s: Geofeed: uncovered IP: %s", fn, buf);
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/usr.sbin/rpki-client/x509.c b/usr.sbin/rpki-client/x509.c
index 8cb01f116e0..c23b69dfb17 100644
--- a/usr.sbin/rpki-client/x509.c
+++ b/usr.sbin/rpki-client/x509.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: x509.c,v 1.58 2022/11/07 09:18:14 job Exp $ */
+/* $OpenBSD: x509.c,v 1.59 2022/11/26 12:02:37 job Exp $ */
/*
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
* Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
@@ -47,6 +47,7 @@ ASN1_OBJECT *bin_sign_time_oid; /* pkcs-9 id-aa-binarySigningTime */
ASN1_OBJECT *rsc_oid; /* id-ct-signedChecklist */
ASN1_OBJECT *aspa_oid; /* id-ct-ASPA */
ASN1_OBJECT *tak_oid; /* id-ct-SignedTAL */
+ASN1_OBJECT *geofeed_oid; /* id-ct-geofeedCSVwithCRLF */
static const struct {
const char *oid;
@@ -105,6 +106,10 @@ static const struct {
.ptr = &bin_sign_time_oid,
},
{
+ .oid = "1.2.840.113549.1.9.16.1.47",
+ .ptr = &geofeed_oid,
+ },
+ {
.oid = "1.2.840.113549.1.9.16.1.48",
.ptr = &rsc_oid,
},