xref: /openbsd/usr.sbin/rpki-client/geofeed.c (revision 381ee599)
1 /*	$OpenBSD: geofeed.c,v 1.17 2024/11/12 09:23:07 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 
34 extern 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  */
40 static int
geofeed_parse_geoip(struct geofeed * geofeed,char * cidr,char * loc)41 geofeed_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->num_geoips,
72 	    geofeed->num_geoips + 1, sizeof(struct geoip));
73 	if (geofeed->geoips == NULL)
74 		err(1, NULL);
75 	geoip = &geofeed->geoips[geofeed->num_geoips++];
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  */
97 struct geofeed *
geofeed_parse(X509 ** x509,const char * fn,int talid,char * buf,size_t len)98 geofeed_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->num_ases > 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  */
283 void
geofeed_free(struct geofeed * p)284 geofeed_free(struct geofeed *p)
285 {
286 	size_t i;
287 
288 	if (p == NULL)
289 		return;
290 
291 	for (i = 0; i < p->num_geoips; 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