1*381ee599Stb /* $OpenBSD: geofeed.c,v 1.17 2024/11/12 09:23:07 tb Exp $ */ 2ef3f6f56Sjob /* 3ef3f6f56Sjob * Copyright (c) 2022 Job Snijders <job@fastly.com> 4ef3f6f56Sjob * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 5ef3f6f56Sjob * 6ef3f6f56Sjob * Permission to use, copy, modify, and distribute this software for any 7ef3f6f56Sjob * purpose with or without fee is hereby granted, provided that the above 8ef3f6f56Sjob * copyright notice and this permission notice appear in all copies. 9ef3f6f56Sjob * 10ef3f6f56Sjob * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11ef3f6f56Sjob * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12ef3f6f56Sjob * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13ef3f6f56Sjob * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14ef3f6f56Sjob * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15ef3f6f56Sjob * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16ef3f6f56Sjob * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17ef3f6f56Sjob */ 18ef3f6f56Sjob 1931947c16Stb #include <sys/socket.h> 2031947c16Stb 2131947c16Stb #include <arpa/inet.h> 2231947c16Stb 23d313d8cdStb #include <ctype.h> 24ef3f6f56Sjob #include <err.h> 25ef3f6f56Sjob #include <stdlib.h> 26ef3f6f56Sjob #include <string.h> 27ef3f6f56Sjob #include <vis.h> 28ef3f6f56Sjob 29ef3f6f56Sjob #include <openssl/bio.h> 30ef3f6f56Sjob #include <openssl/x509.h> 31ef3f6f56Sjob 32ef3f6f56Sjob #include "extern.h" 33ef3f6f56Sjob 34ef3f6f56Sjob extern ASN1_OBJECT *geofeed_oid; 35ef3f6f56Sjob 36ef3f6f56Sjob /* 37ef3f6f56Sjob * Take a CIDR prefix (in presentation format) and add it to parse results. 38ef3f6f56Sjob * Returns 1 on success, 0 on failure. 39ef3f6f56Sjob */ 40ef3f6f56Sjob static int 41be6e5ad5Stb geofeed_parse_geoip(struct geofeed *geofeed, char *cidr, char *loc) 42ef3f6f56Sjob { 43ef3f6f56Sjob struct geoip *geoip; 44ef3f6f56Sjob struct ip_addr *ipaddr; 45ef3f6f56Sjob enum afi afi; 46ef3f6f56Sjob int plen; 47ef3f6f56Sjob 48ef3f6f56Sjob if ((ipaddr = calloc(1, sizeof(struct ip_addr))) == NULL) 49ef3f6f56Sjob err(1, NULL); 50ef3f6f56Sjob 51ef3f6f56Sjob if ((plen = inet_net_pton(AF_INET, cidr, ipaddr->addr, 52ef3f6f56Sjob sizeof(ipaddr->addr))) != -1) 53ef3f6f56Sjob afi = AFI_IPV4; 54ef3f6f56Sjob else if ((plen = inet_net_pton(AF_INET6, cidr, ipaddr->addr, 55ef3f6f56Sjob sizeof(ipaddr->addr))) != -1) 56ef3f6f56Sjob afi = AFI_IPV6; 57ef3f6f56Sjob else { 58ef3f6f56Sjob static char buf[80]; 59ef3f6f56Sjob 60ef3f6f56Sjob if (strnvis(buf, cidr, sizeof(buf), VIS_SAFE) 61ef3f6f56Sjob >= (int)sizeof(buf)) { 62ef3f6f56Sjob memcpy(buf + sizeof(buf) - 4, "...", 4); 63ef3f6f56Sjob } 64ef3f6f56Sjob warnx("invalid address: %s", buf); 65ef3f6f56Sjob free(ipaddr); 66ef3f6f56Sjob return 0; 67ef3f6f56Sjob } 68ef3f6f56Sjob 69ef3f6f56Sjob ipaddr->prefixlen = plen; 70ef3f6f56Sjob 71*381ee599Stb geofeed->geoips = recallocarray(geofeed->geoips, geofeed->num_geoips, 72*381ee599Stb geofeed->num_geoips + 1, sizeof(struct geoip)); 73be6e5ad5Stb if (geofeed->geoips == NULL) 74ef3f6f56Sjob err(1, NULL); 75*381ee599Stb geoip = &geofeed->geoips[geofeed->num_geoips++]; 76ef3f6f56Sjob 77ef3f6f56Sjob if ((geoip->ip = calloc(1, sizeof(struct cert_ip))) == NULL) 78ef3f6f56Sjob err(1, NULL); 79ef3f6f56Sjob 80ef3f6f56Sjob geoip->ip->type = CERT_IP_ADDR; 81ef3f6f56Sjob geoip->ip->ip = *ipaddr; 82ef3f6f56Sjob geoip->ip->afi = afi; 83ef3f6f56Sjob 84ef3f6f56Sjob if ((geoip->loc = strdup(loc)) == NULL) 85ef3f6f56Sjob err(1, NULL); 86ef3f6f56Sjob 87ef3f6f56Sjob if (!ip_cert_compose_ranges(geoip->ip)) 88ef3f6f56Sjob return 0; 89ef3f6f56Sjob 90ef3f6f56Sjob return 1; 91ef3f6f56Sjob } 92ef3f6f56Sjob 93ef3f6f56Sjob /* 94ef3f6f56Sjob * Parse a full RFC 9092 file. 95ef3f6f56Sjob * Returns the Geofeed, or NULL if the object was malformed. 96ef3f6f56Sjob */ 97ef3f6f56Sjob struct geofeed * 980636c4d0Stb geofeed_parse(X509 **x509, const char *fn, int talid, char *buf, size_t len) 99ef3f6f56Sjob { 100be6e5ad5Stb struct geofeed *geofeed; 101ef3f6f56Sjob char *delim, *line, *loc, *nl; 1028070ed0eStb ssize_t linelen; 103ef3f6f56Sjob BIO *bio; 104ef3f6f56Sjob char *b64 = NULL; 105b893fe89Sjob size_t b64sz = 0; 106ef3f6f56Sjob unsigned char *der = NULL; 107ef3f6f56Sjob size_t dersz; 108ef3f6f56Sjob struct cert *cert = NULL; 109ef3f6f56Sjob int rpki_signature_seen = 0, end_signature_seen = 0; 110ef3f6f56Sjob int rc = 0; 111ef3f6f56Sjob 112ef3f6f56Sjob bio = BIO_new(BIO_s_mem()); 1136eb35ad3Stb if (bio == NULL) 1146eb35ad3Stb errx(1, "BIO_new"); 115ef3f6f56Sjob 116be6e5ad5Stb if ((geofeed = calloc(1, sizeof(*geofeed))) == NULL) 117ef3f6f56Sjob err(1, NULL); 118ef3f6f56Sjob 119ef3f6f56Sjob while ((nl = memchr(buf, '\n', len)) != NULL) { 120ef3f6f56Sjob line = buf; 121ef3f6f56Sjob 122ef3f6f56Sjob /* advance buffer to next line */ 123ef3f6f56Sjob len -= nl + 1 - buf; 124ef3f6f56Sjob buf = nl + 1; 125ef3f6f56Sjob 126ef3f6f56Sjob /* replace LF and CR with NUL, point nl at first NUL */ 127ef3f6f56Sjob *nl = '\0'; 128ef3f6f56Sjob if (nl > line && nl[-1] == '\r') { 129ef3f6f56Sjob nl[-1] = '\0'; 130ef3f6f56Sjob nl--; 131ef3f6f56Sjob linelen = nl - line; 132ef3f6f56Sjob } else { 133ef3f6f56Sjob warnx("%s: malformed file, expected CRLF line" 134ef3f6f56Sjob " endings", fn); 135ef3f6f56Sjob goto out; 136ef3f6f56Sjob } 137ef3f6f56Sjob 138ef3f6f56Sjob if (end_signature_seen) { 139ef3f6f56Sjob warnx("%s: trailing data after signature section", fn); 140ef3f6f56Sjob goto out; 141ef3f6f56Sjob } 142ef3f6f56Sjob 143f3520e92Stb if (rpki_signature_seen) { 144ef3f6f56Sjob if (strncmp(line, "# End Signature:", 145ef3f6f56Sjob strlen("# End Signature:")) == 0) { 146ef3f6f56Sjob end_signature_seen = 1; 147ef3f6f56Sjob continue; 148ef3f6f56Sjob } 149ef3f6f56Sjob 150ef3f6f56Sjob if (linelen > 74) { 151ef3f6f56Sjob warnx("%s: line in signature section too long", 152ef3f6f56Sjob fn); 153ef3f6f56Sjob goto out; 154ef3f6f56Sjob } 155ef3f6f56Sjob if (strncmp(line, "# ", strlen("# ")) != 0) { 156ef3f6f56Sjob warnx("%s: line in signature section too " 157ef3f6f56Sjob "short", fn); 158ef3f6f56Sjob goto out; 159ef3f6f56Sjob } 160ef3f6f56Sjob 161ef3f6f56Sjob /* skip over "# " */ 162ef3f6f56Sjob line += 2; 163ef3f6f56Sjob strlcat(b64, line, b64sz); 164ef3f6f56Sjob continue; 165ef3f6f56Sjob } 166ef3f6f56Sjob 167ef3f6f56Sjob if (strncmp(line, "# RPKI Signature:", 168ef3f6f56Sjob strlen("# RPKI Signature:")) == 0) { 169ef3f6f56Sjob rpki_signature_seen = 1; 170f3520e92Stb 171f3520e92Stb if ((b64 = calloc(1, len)) == NULL) 172f3520e92Stb err(1, NULL); 173f3520e92Stb b64sz = len; 174f3520e92Stb 175ef3f6f56Sjob continue; 176ef3f6f56Sjob } 177ef3f6f56Sjob 178ef3f6f56Sjob /* 179ef3f6f56Sjob * Read the Geofeed CSV records into a BIO to later on 180ef3f6f56Sjob * calculate the message digest and compare with the one 181ef3f6f56Sjob * in the detached CMS signature. 182ef3f6f56Sjob */ 1835d47d88eSjob if (BIO_puts(bio, line) != linelen || 1845d47d88eSjob BIO_puts(bio, "\r\n") != 2) { 185ef3f6f56Sjob warnx("%s: BIO_puts failed", fn); 186ef3f6f56Sjob goto out; 187ef3f6f56Sjob } 188ef3f6f56Sjob 189d313d8cdStb /* Zap comments and whitespace before them. */ 190ef3f6f56Sjob delim = memchr(line, '#', linelen); 191d313d8cdStb if (delim != NULL) { 192d313d8cdStb while (delim > line && 193d313d8cdStb isspace((unsigned char)delim[-1])) 194d313d8cdStb delim--; 195ef3f6f56Sjob *delim = '\0'; 196d313d8cdStb linelen = delim - line; 197d313d8cdStb } 198d313d8cdStb 199d313d8cdStb /* Skip empty lines. */ 200d313d8cdStb if (linelen == 0) 201d313d8cdStb continue; 202ef3f6f56Sjob 203ef3f6f56Sjob /* Split prefix and location info */ 204ef3f6f56Sjob delim = memchr(line, ',', linelen); 205ef3f6f56Sjob if (delim != NULL) { 206ef3f6f56Sjob *delim = '\0'; 207ef3f6f56Sjob loc = delim + 1; 208ef3f6f56Sjob } else 209ef3f6f56Sjob loc = ""; 210ef3f6f56Sjob 211ef3f6f56Sjob /* read each prefix */ 212be6e5ad5Stb if (!geofeed_parse_geoip(geofeed, line, loc)) 213ef3f6f56Sjob goto out; 214ef3f6f56Sjob } 215ef3f6f56Sjob 216ef3f6f56Sjob if (!rpki_signature_seen || !end_signature_seen) { 217ef3f6f56Sjob warnx("%s: absent or invalid signature", fn); 218ef3f6f56Sjob goto out; 219ef3f6f56Sjob } 220ef3f6f56Sjob 221ef3f6f56Sjob if ((base64_decode(b64, strlen(b64), &der, &dersz)) == -1) { 222ef3f6f56Sjob warnx("%s: base64_decode failed", fn); 223ef3f6f56Sjob goto out; 224ef3f6f56Sjob } 225ef3f6f56Sjob 226ef3f6f56Sjob if (!cms_parse_validate_detached(x509, fn, der, dersz, geofeed_oid, 227be6e5ad5Stb bio, &geofeed->signtime)) 228ef3f6f56Sjob goto out; 229ef3f6f56Sjob 230be6e5ad5Stb if (!x509_get_aia(*x509, fn, &geofeed->aia)) 231ef3f6f56Sjob goto out; 232be6e5ad5Stb if (!x509_get_aki(*x509, fn, &geofeed->aki)) 233ef3f6f56Sjob goto out; 234be6e5ad5Stb if (!x509_get_ski(*x509, fn, &geofeed->ski)) 235ef3f6f56Sjob goto out; 236ef3f6f56Sjob 237be6e5ad5Stb if (geofeed->aia == NULL || geofeed->aki == NULL || 238be6e5ad5Stb geofeed->ski == NULL) { 23961b77220Sjob warnx("%s: missing AIA, AKI, or SKI X509 extension", fn); 240ef3f6f56Sjob goto out; 241ef3f6f56Sjob } 242ef3f6f56Sjob 243be6e5ad5Stb if (!x509_get_notbefore(*x509, fn, &geofeed->notbefore)) 244ef3f6f56Sjob goto out; 245be6e5ad5Stb if (!x509_get_notafter(*x509, fn, &geofeed->notafter)) 246ef3f6f56Sjob goto out; 247ef3f6f56Sjob 248891d6bceSjob if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL) 249ef3f6f56Sjob goto out; 250ef3f6f56Sjob 251b058bac1Sjob if (x509_any_inherits(*x509)) { 252b058bac1Sjob warnx("%s: inherit elements not allowed in EE cert", fn); 253b058bac1Sjob goto out; 254b058bac1Sjob } 255b058bac1Sjob 256*381ee599Stb if (cert->num_ases > 0) { 257ef3f6f56Sjob warnx("%s: superfluous AS Resources extension present", fn); 258ef3f6f56Sjob goto out; 259ef3f6f56Sjob } 260ef3f6f56Sjob 261be6e5ad5Stb geofeed->valid = valid_geofeed(fn, cert, geofeed); 262ef3f6f56Sjob 263ef3f6f56Sjob rc = 1; 264ef3f6f56Sjob out: 265ef3f6f56Sjob if (rc == 0) { 266be6e5ad5Stb geofeed_free(geofeed); 267be6e5ad5Stb geofeed = NULL; 268ef3f6f56Sjob X509_free(*x509); 269ef3f6f56Sjob *x509 = NULL; 270ef3f6f56Sjob } 271ef3f6f56Sjob cert_free(cert); 272ef3f6f56Sjob BIO_free(bio); 273ef3f6f56Sjob free(b64); 274ef3f6f56Sjob free(der); 275ef3f6f56Sjob 276be6e5ad5Stb return geofeed; 277ef3f6f56Sjob } 278ef3f6f56Sjob 279ef3f6f56Sjob /* 280ef3f6f56Sjob * Free what follows a pointer to a geofeed structure. 281ef3f6f56Sjob * Safe to call with NULL. 282ef3f6f56Sjob */ 283ef3f6f56Sjob void 284ef3f6f56Sjob geofeed_free(struct geofeed *p) 285ef3f6f56Sjob { 286ef3f6f56Sjob size_t i; 287ef3f6f56Sjob 288ef3f6f56Sjob if (p == NULL) 289ef3f6f56Sjob return; 290ef3f6f56Sjob 291*381ee599Stb for (i = 0; i < p->num_geoips; i++) { 292ef3f6f56Sjob free(p->geoips[i].ip); 293ef3f6f56Sjob free(p->geoips[i].loc); 294ef3f6f56Sjob } 295ef3f6f56Sjob 296ef3f6f56Sjob free(p->geoips); 297ef3f6f56Sjob free(p->aia); 298ef3f6f56Sjob free(p->aki); 299ef3f6f56Sjob free(p->ski); 300ef3f6f56Sjob free(p); 301ef3f6f56Sjob } 302