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