1 /* $OpenBSD: constraints.c,v 1.4 2024/03/15 05:14:16 tb Exp $ */
2 /*
3 * Copyright (c) 2023 Job Snijders <job@openbsd.org>
4 * Copyright (c) 2023 Theo Buehler <tb@openbsd.org>
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 <errno.h>
26 #include <fcntl.h>
27 #include <libgen.h>
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33
34 #include <openssl/asn1.h>
35 #include <openssl/x509v3.h>
36
37 #include "extern.h"
38
39 struct tal_constraints {
40 int fd; /* constraints file descriptor or -1. */
41 char *fn; /* constraints filename */
42 char *warn; /* warning msg used for violations */
43 struct cert_ip *allow_ips; /* list of allowed IP address ranges */
44 size_t allow_ipsz; /* length of "allow_ips" */
45 struct cert_as *allow_as; /* allowed AS numbers and ranges */
46 size_t allow_asz; /* length of "allow_as" */
47 struct cert_ip *deny_ips; /* forbidden IP address ranges */
48 size_t deny_ipsz; /* length of "deny_ips" */
49 struct cert_as *deny_as; /* forbidden AS numbers and ranges */
50 size_t deny_asz; /* length of "deny_as" */
51 } tal_constraints[TALSZ_MAX];
52
53 /*
54 * If there is a .constraints file next to a .tal file, load its contents
55 * into into tal_constraints[talid]. The load function only opens the fd
56 * and stores the filename. The actual parsing happens in constraints_parse().
57 * Resources of EE certs can then be constrained using constraints_validate().
58 */
59
60 static void
constraints_load_talid(int talid)61 constraints_load_talid(int talid)
62 {
63 const char *tal = tals[talid];
64 char *constraints = NULL, *warning = NULL, *cbn;
65 int fd;
66 size_t len;
67 int saved_errno;
68
69 tal_constraints[talid].fd = -1;
70
71 if (rtype_from_file_extension(tal) != RTYPE_TAL)
72 return;
73
74 /* Replace .tal suffix with .constraints. */
75 len = strlen(tal) - 4;
76 if (asprintf(&constraints, "%.*s.constraints", (int)len, tal) == -1)
77 err(1, NULL);
78
79 /* prepare warning message for when violations are detected */
80 if ((cbn = basename(constraints)) == NULL)
81 err(1, "basename");
82 if (asprintf(&warning, "resource violates %s", cbn) == -1)
83 err(1, NULL);
84
85 saved_errno = errno;
86
87 fd = open(constraints, O_RDONLY);
88 if (fd == -1 && errno != ENOENT)
89 err(1, "failed to load constraints for %s", tal);
90
91 tal_constraints[talid].fn = constraints;
92 tal_constraints[talid].fd = fd;
93 tal_constraints[talid].warn = warning;
94
95 errno = saved_errno;
96 }
97
98 /*
99 * Iterate over all TALs and load the corresponding constraints files.
100 */
101 void
constraints_load(void)102 constraints_load(void)
103 {
104 int talid;
105
106 for (talid = 0; talid < talsz; talid++)
107 constraints_load_talid(talid);
108 }
109
110 void
constraints_unload(void)111 constraints_unload(void)
112 {
113 int saved_errno, talid;
114
115 saved_errno = errno;
116 for (talid = 0; talid < talsz; talid++) {
117 if (tal_constraints[talid].fd != -1)
118 close(tal_constraints[talid].fd);
119 free(tal_constraints[talid].fn);
120 free(tal_constraints[talid].warn);
121 tal_constraints[talid].fd = -1;
122 tal_constraints[talid].fn = NULL;
123 tal_constraints[talid].warn = NULL;
124 }
125 errno = saved_errno;
126 }
127
128 /*
129 * Split a string at '-' and trim whitespace around the '-'.
130 * Assumes leading and trailing whitespace in p has already been trimmed.
131 */
132 static int
constraints_split_range(char * p,const char ** min,const char ** max)133 constraints_split_range(char *p, const char **min, const char **max)
134 {
135 char *pp;
136
137 *min = p;
138 if ((*max = pp = strchr(p, '-')) == NULL)
139 return 0;
140
141 /* Trim whitespace before '-'. */
142 while (pp > *min && isspace((unsigned char)pp[-1]))
143 pp--;
144 *pp = '\0';
145
146 /* Skip past '-' and whitespace following it. */
147 (*max)++;
148 while (isspace((unsigned char)**max))
149 (*max)++;
150
151 return 1;
152 }
153
154 /*
155 * Helper functions to parse textual representations of IP prefixes or ranges.
156 * The RFC 3779 API has poor error reporting, so as a debugging aid, we call
157 * the prohibitively expensive X509v3_addr_canonize() in high verbosity mode.
158 */
159
160 static void
constraints_parse_ip_prefix(const char * fn,const char * prefix,enum afi afi,IPAddrBlocks * addrs)161 constraints_parse_ip_prefix(const char *fn, const char *prefix, enum afi afi,
162 IPAddrBlocks *addrs)
163 {
164 unsigned char addr[16] = { 0 };
165 int af = afi == AFI_IPV4 ? AF_INET : AF_INET6;
166 int plen;
167
168 if ((plen = inet_net_pton(af, prefix, addr, sizeof(addr))) == -1)
169 errx(1, "%s: failed to parse %s", fn, prefix);
170
171 if (!X509v3_addr_add_prefix(addrs, afi, NULL, addr, plen))
172 errx(1, "%s: failed to add prefix %s", fn, prefix);
173
174 if (verbose < 3)
175 return;
176
177 if (!X509v3_addr_canonize(addrs))
178 errx(1, "%s: failed to canonize with prefix %s", fn, prefix);
179 }
180
181 static void
constraints_parse_ip_range(const char * fn,const char * min,const char * max,enum afi afi,IPAddrBlocks * addrs)182 constraints_parse_ip_range(const char *fn, const char *min, const char *max,
183 enum afi afi, IPAddrBlocks *addrs)
184 {
185 unsigned char min_addr[16] = {0}, max_addr[16] = {0};
186 int af = afi == AFI_IPV4 ? AF_INET : AF_INET6;
187
188 if (inet_pton(af, min, min_addr) != 1)
189 errx(1, "%s: failed to parse %s", fn, min);
190 if (inet_pton(af, max, max_addr) != 1)
191 errx(1, "%s: failed to parse %s", fn, max);
192
193 if (!X509v3_addr_add_range(addrs, afi, NULL, min_addr, max_addr))
194 errx(1, "%s: failed to add range %s--%s", fn, min, max);
195
196 if (verbose < 3)
197 return;
198
199 if (!X509v3_addr_canonize(addrs))
200 errx(1, "%s: failed to canonize with range %s--%s", fn,
201 min, max);
202 }
203
204 static void
constraints_parse_ip(const char * fn,char * p,enum afi afi,IPAddrBlocks * addrs)205 constraints_parse_ip(const char *fn, char *p, enum afi afi, IPAddrBlocks *addrs)
206 {
207 const char *min, *max;
208
209 if (strchr(p, '-') == NULL) {
210 constraints_parse_ip_prefix(fn, p, afi, addrs);
211 return;
212 }
213
214 if (!constraints_split_range(p, &min, &max))
215 errx(1, "%s: failed to split range: %s", fn, p);
216
217 constraints_parse_ip_range(fn, min, max, afi, addrs);
218 }
219
220 /*
221 * Helper functions to parse textual representations of AS numbers or ranges.
222 * The RFC 3779 API has poor error reporting, so as a debugging aid, we call
223 * the prohibitively expensive X509v3_asid_canonize() in high verbosity mode.
224 */
225
226 static void
constraints_parse_asn(const char * fn,const char * asn,ASIdentifiers * asids)227 constraints_parse_asn(const char *fn, const char *asn, ASIdentifiers *asids)
228 {
229 ASN1_INTEGER *id;
230
231 if ((id = s2i_ASN1_INTEGER(NULL, asn)) == NULL)
232 errx(1, "%s: failed to parse AS %s", fn, asn);
233
234 if (!X509v3_asid_add_id_or_range(asids, V3_ASID_ASNUM, id, NULL))
235 errx(1, "%s: failed to add AS %s", fn, asn);
236
237 if (verbose < 3)
238 return;
239
240 if (!X509v3_asid_canonize(asids))
241 errx(1, "%s: failed to canonize with AS %s", fn, asn);
242 }
243
244 static void
constraints_parse_asn_range(const char * fn,const char * min,const char * max,ASIdentifiers * asids)245 constraints_parse_asn_range(const char *fn, const char *min, const char *max,
246 ASIdentifiers *asids)
247 {
248 ASN1_INTEGER *min_as, *max_as;
249
250 if ((min_as = s2i_ASN1_INTEGER(NULL, min)) == NULL)
251 errx(1, "%s: failed to parse AS %s", fn, min);
252 if ((max_as = s2i_ASN1_INTEGER(NULL, max)) == NULL)
253 errx(1, "%s: failed to parse AS %s", fn, max);
254
255 if (!X509v3_asid_add_id_or_range(asids, V3_ASID_ASNUM, min_as, max_as))
256 errx(1, "%s: failed to add AS range %s--%s", fn, min, max);
257
258 if (verbose < 3)
259 return;
260
261 if (!X509v3_asid_canonize(asids))
262 errx(1, "%s: failed to canonize with AS range %s--%s", fn,
263 min, max);
264 }
265
266 static void
constraints_parse_as(const char * fn,char * p,ASIdentifiers * asids)267 constraints_parse_as(const char *fn, char *p, ASIdentifiers *asids)
268 {
269 const char *min, *max;
270
271 if (strchr(p, '-') == NULL) {
272 constraints_parse_asn(fn, p, asids);
273 return;
274 }
275
276 if (!constraints_split_range(p, &min, &max))
277 errx(1, "%s: failed to split range: %s", fn, p);
278
279 constraints_parse_asn_range(fn, min, max, asids);
280 }
281
282 /*
283 * Work around an annoying bug in X509v3_addr_add_range(). The upper bound
284 * of a range can have unused bits set in its ASN1_BIT_STRING representation.
285 * This triggers a check in ip_addr_parse(). A round trip through DER fixes
286 * this mess up. For extra special fun, {d2i,i2d}_IPAddrBlocks() isn't part
287 * of the API and implementing them for OpenSSL 3 is hairy, so do the round
288 * tripping once per address family.
289 */
290 static void
constraints_normalize_ip_addrblocks(const char * fn,IPAddrBlocks ** addrs)291 constraints_normalize_ip_addrblocks(const char *fn, IPAddrBlocks **addrs)
292 {
293 IPAddrBlocks *new_addrs;
294 IPAddressFamily *af;
295 const unsigned char *p;
296 unsigned char *der;
297 int der_len, i;
298
299 if ((new_addrs = IPAddrBlocks_new()) == NULL)
300 err(1, NULL);
301
302 for (i = 0; i < sk_IPAddressFamily_num(*addrs); i++) {
303 af = sk_IPAddressFamily_value(*addrs, i);
304
305 der = NULL;
306 if ((der_len = i2d_IPAddressFamily(af, &der)) <= 0)
307 errx(1, "%s: failed to convert to DER", fn);
308 p = der;
309 if ((af = d2i_IPAddressFamily(NULL, &p, der_len)) == NULL)
310 errx(1, "%s: failed to convert from DER", fn);
311 free(der);
312
313 if (!sk_IPAddressFamily_push(new_addrs, af))
314 errx(1, "%s: failed to push constraints", fn);
315 }
316
317 IPAddrBlocks_free(*addrs);
318 *addrs = new_addrs;
319 }
320
321 /*
322 * If there is a constraints file for tals[talid], load it into a buffer
323 * and parse it line by line. Leverage the above parse helpers to build up
324 * IPAddrBlocks and ASIdentifiers. We use the RFC 3779 API to benefit from
325 * the limited abilities of X509v3_{addr,asid}_canonize() to sort and merge
326 * adjacent ranges. This doesn't deal with overlaps or duplicates, but it's
327 * better than nothing.
328 */
329
330 static void
constraints_parse_talid(int talid)331 constraints_parse_talid(int talid)
332 {
333 IPAddrBlocks *allow_addrs, *deny_addrs;
334 ASIdentifiers *allow_asids, *deny_asids;
335 FILE *f;
336 char *fn, *p, *pp;
337 struct cert_as *allow_as = NULL, *deny_as = NULL;
338 struct cert_ip *allow_ips = NULL, *deny_ips = NULL;
339 size_t allow_asz = 0, allow_ipsz = 0,
340 deny_asz = 0, deny_ipsz = 0;
341 char *line = NULL;
342 size_t len = 0;
343 ssize_t n;
344 int fd, have_allow_as = 0, have_allow_ips = 0,
345 have_deny_as = 0, have_deny_ips = 0;
346
347 fd = tal_constraints[talid].fd;
348 fn = tal_constraints[talid].fn;
349 tal_constraints[talid].fd = -1;
350 tal_constraints[talid].fn = NULL;
351
352 if (fd == -1) {
353 free(fn);
354 return;
355 }
356
357 if ((f = fdopen(fd, "r")) == NULL)
358 err(1, "fdopen");
359
360 if ((allow_addrs = IPAddrBlocks_new()) == NULL)
361 err(1, NULL);
362 if ((allow_asids = ASIdentifiers_new()) == NULL)
363 err(1, NULL);
364 if ((deny_addrs = IPAddrBlocks_new()) == NULL)
365 err(1, NULL);
366 if ((deny_asids = ASIdentifiers_new()) == NULL)
367 err(1, NULL);
368
369 while ((n = getline(&line, &len, f)) != -1) {
370 if (line[n - 1] == '\n')
371 line[n - 1] = '\0';
372
373 p = line;
374
375 /* Zap leading whitespace */
376 while (isspace((unsigned char)*p))
377 p++;
378
379 /* Zap comments */
380 if ((pp = strchr(p, '#')) != NULL)
381 *pp = '\0';
382
383 /* Zap trailing whitespace */
384 if (pp == NULL)
385 pp = p + strlen(p);
386 while (pp > p && isspace((unsigned char)pp[-1]))
387 pp--;
388 *pp = '\0';
389
390 if (strlen(p) == 0)
391 continue;
392
393 if (strncmp(p, "allow", strlen("allow")) == 0) {
394 p += strlen("allow");
395
396 /* Ensure there's whitespace and jump over it. */
397 if (!isspace((unsigned char)*p))
398 errx(1, "%s: failed to parse %s", fn, p);
399 while (isspace((unsigned char)*p))
400 p++;
401
402 if (strchr(p, '.') != NULL) {
403 constraints_parse_ip(fn, p, AFI_IPV4,
404 allow_addrs);
405 have_allow_ips = 1;
406 } else if (strchr(p, ':') != NULL) {
407 constraints_parse_ip(fn, p, AFI_IPV6,
408 allow_addrs);
409 have_allow_ips = 1;
410 } else {
411 constraints_parse_as(fn, p, allow_asids);
412 have_allow_as = 1;
413 }
414 } else if (strncmp(p, "deny", strlen("deny")) == 0) {
415 p += strlen("deny");
416
417 /* Ensure there's whitespace and jump over it. */
418 if (!isspace((unsigned char)*p))
419 errx(1, "%s: failed to parse %s", fn, p);
420 /* Zap leading whitespace */
421 while (isspace((unsigned char)*p))
422 p++;
423
424 if (strchr(p, '.') != NULL) {
425 constraints_parse_ip(fn, p, AFI_IPV4,
426 deny_addrs);
427 have_deny_ips = 1;
428 } else if (strchr(p, ':') != NULL) {
429 constraints_parse_ip(fn, p, AFI_IPV6,
430 deny_addrs);
431 have_deny_ips = 1;
432 } else {
433 constraints_parse_as(fn, p, deny_asids);
434 have_deny_as = 1;
435 }
436 } else
437 errx(1, "%s: failed to parse %s", fn, p);
438 }
439 free(line);
440
441 if (ferror(f))
442 err(1, "%s", fn);
443 fclose(f);
444
445 if (!X509v3_addr_canonize(allow_addrs))
446 errx(1, "%s: failed to canonize IP addresses allowlist", fn);
447 if (!X509v3_asid_canonize(allow_asids))
448 errx(1, "%s: failed to canonize AS numbers allowlist", fn);
449 if (!X509v3_addr_canonize(deny_addrs))
450 errx(1, "%s: failed to canonize IP addresses denylist", fn);
451 if (!X509v3_asid_canonize(deny_asids))
452 errx(1, "%s: failed to canonize AS numbers denylist", fn);
453
454 if (have_allow_as) {
455 if (!sbgp_parse_assysnum(fn, allow_asids, &allow_as,
456 &allow_asz))
457 errx(1, "%s: failed to parse AS identifiers allowlist",
458 fn);
459 }
460 if (have_deny_as) {
461 if (!sbgp_parse_assysnum(fn, deny_asids, &deny_as,
462 &deny_asz))
463 errx(1, "%s: failed to parse AS identifiers denylist",
464 fn);
465 }
466 if (have_allow_ips) {
467 constraints_normalize_ip_addrblocks(fn, &allow_addrs);
468
469 if (!sbgp_parse_ipaddrblk(fn, allow_addrs, &allow_ips,
470 &allow_ipsz))
471 errx(1, "%s: failed to parse IP addresses allowlist",
472 fn);
473 }
474 if (have_deny_ips) {
475 constraints_normalize_ip_addrblocks(fn, &deny_addrs);
476
477 if (!sbgp_parse_ipaddrblk(fn, deny_addrs, &deny_ips,
478 &deny_ipsz))
479 errx(1, "%s: failed to parse IP addresses denylist",
480 fn);
481 }
482
483 tal_constraints[talid].allow_as = allow_as;
484 tal_constraints[talid].allow_asz = allow_asz;
485 tal_constraints[talid].allow_ips = allow_ips;
486 tal_constraints[talid].allow_ipsz = allow_ipsz;
487 tal_constraints[talid].deny_as = deny_as;
488 tal_constraints[talid].deny_asz = deny_asz;
489 tal_constraints[talid].deny_ips = deny_ips;
490 tal_constraints[talid].deny_ipsz = deny_ipsz;
491
492 IPAddrBlocks_free(allow_addrs);
493 IPAddrBlocks_free(deny_addrs);
494 ASIdentifiers_free(allow_asids);
495 ASIdentifiers_free(deny_asids);
496
497 free(fn);
498 }
499
500 /*
501 * Iterate over all TALs and parse the constraints files loaded previously.
502 */
503 void
constraints_parse(void)504 constraints_parse(void)
505 {
506 int talid;
507
508 for (talid = 0; talid < talsz; talid++)
509 constraints_parse_talid(talid);
510 }
511
512 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)513 constraints_check_as(const char *fn, struct cert_as *cert,
514 const struct cert_as *allow_as, size_t allow_asz,
515 const struct cert_as *deny_as, size_t deny_asz)
516 {
517 uint32_t min, max;
518
519 /* Inheriting EE resources are not to be constrained. */
520 if (cert->type == CERT_AS_INHERIT)
521 return 1;
522
523 if (cert->type == CERT_AS_ID) {
524 min = cert->id;
525 max = cert->id;
526 } else {
527 min = cert->range.min;
528 max = cert->range.max;
529 }
530
531 if (deny_as != NULL) {
532 if (!as_check_overlap(cert, fn, deny_as, deny_asz, 1))
533 return 0;
534 }
535 if (allow_as != NULL) {
536 if (as_check_covered(min, max, allow_as, allow_asz) <= 0)
537 return 0;
538 }
539 return 1;
540 }
541
542 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)543 constraints_check_ips(const char *fn, struct cert_ip *cert,
544 const struct cert_ip *allow_ips, size_t allow_ipsz,
545 const struct cert_ip *deny_ips, size_t deny_ipsz)
546 {
547 /* Inheriting EE resources are not to be constrained. */
548 if (cert->type == CERT_IP_INHERIT)
549 return 1;
550
551 if (deny_ips != NULL) {
552 if (!ip_addr_check_overlap(cert, fn, deny_ips, deny_ipsz, 1))
553 return 0;
554 }
555 if (allow_ips != NULL) {
556 if (ip_addr_check_covered(cert->afi, cert->min, cert->max,
557 allow_ips, allow_ipsz) <= 0)
558 return 0;
559 }
560 return 1;
561 }
562
563 /*
564 * Check whether an EE cert's resources are covered by its TAL's constraints.
565 * We accept certs with a negative talid as "unknown TAL" for filemode. The
566 * logic nearly duplicates valid_cert().
567 */
568 int
constraints_validate(const char * fn,const struct cert * cert)569 constraints_validate(const char *fn, const struct cert *cert)
570 {
571 int talid = cert->talid;
572 struct cert_as *allow_as, *deny_as;
573 struct cert_ip *allow_ips, *deny_ips;
574 size_t i, allow_asz, allow_ipsz, deny_asz, deny_ipsz;
575
576 /* Accept negative talid to bypass validation. */
577 if (talid < 0)
578 return 1;
579 if (talid >= talsz)
580 errx(1, "%s: talid out of range %d", fn, talid);
581
582 allow_as = tal_constraints[talid].allow_as;
583 allow_asz = tal_constraints[talid].allow_asz;
584 deny_as = tal_constraints[talid].deny_as;
585 deny_asz = tal_constraints[talid].deny_asz;
586
587 for (i = 0; i < cert->asz; i++) {
588 if (constraints_check_as(fn, &cert->as[i], allow_as, allow_asz,
589 deny_as, deny_asz))
590 continue;
591
592 as_warn(fn, tal_constraints[talid].warn, &cert->as[i]);
593 return 0;
594 }
595
596 allow_ips = tal_constraints[talid].allow_ips;
597 allow_ipsz = tal_constraints[talid].allow_ipsz;
598 deny_ips = tal_constraints[talid].deny_ips;
599 deny_ipsz = tal_constraints[talid].deny_ipsz;
600
601 for (i = 0; i < cert->ipsz; i++) {
602 if (constraints_check_ips(fn, &cert->ips[i], allow_ips,
603 allow_ipsz, deny_ips, deny_ipsz))
604 continue;
605
606 ip_warn(fn, tal_constraints[talid].warn, &cert->ips[i]);
607 return 0;
608 }
609
610 return 1;
611 }
612