xref: /openbsd-src/usr.sbin/rpki-client/constraints.c (revision 381ee5995072b060a824e758e1421220f03cdcc0)
1*381ee599Stb /*	$OpenBSD: constraints.c,v 1.5 2024/11/12 09:23:07 tb Exp $ */
2891d6bceSjob /*
3891d6bceSjob  * Copyright (c) 2023 Job Snijders <job@openbsd.org>
4891d6bceSjob  * Copyright (c) 2023 Theo Buehler <tb@openbsd.org>
5891d6bceSjob  *
6891d6bceSjob  * Permission to use, copy, modify, and distribute this software for any
7891d6bceSjob  * purpose with or without fee is hereby granted, provided that the above
8891d6bceSjob  * copyright notice and this permission notice appear in all copies.
9891d6bceSjob  *
10891d6bceSjob  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11891d6bceSjob  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12891d6bceSjob  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13891d6bceSjob  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14891d6bceSjob  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15891d6bceSjob  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16891d6bceSjob  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17891d6bceSjob  */
18891d6bceSjob 
19891d6bceSjob #include <sys/socket.h>
20891d6bceSjob 
21891d6bceSjob #include <arpa/inet.h>
22891d6bceSjob 
23891d6bceSjob #include <ctype.h>
24891d6bceSjob #include <err.h>
25891d6bceSjob #include <errno.h>
26891d6bceSjob #include <fcntl.h>
2789e02b0fSjob #include <libgen.h>
28891d6bceSjob #include <stdint.h>
29891d6bceSjob #include <stdio.h>
30891d6bceSjob #include <stdlib.h>
31891d6bceSjob #include <string.h>
32891d6bceSjob #include <unistd.h>
33891d6bceSjob 
34891d6bceSjob #include <openssl/asn1.h>
35891d6bceSjob #include <openssl/x509v3.h>
36891d6bceSjob 
37891d6bceSjob #include "extern.h"
38891d6bceSjob 
39891d6bceSjob struct tal_constraints {
40891d6bceSjob 	int		 fd;		/* constraints file descriptor or -1. */
41891d6bceSjob 	char		*fn;		/* constraints filename */
4289e02b0fSjob 	char		*warn;		/* warning msg used for violations */
43891d6bceSjob 	struct cert_ip	*allow_ips;	/* list of allowed IP address ranges */
44*381ee599Stb 	size_t		 num_allow_ips;
45*381ee599Stb 	struct cert_as	*allow_ases;	/* allowed AS numbers and ranges */
46*381ee599Stb 	size_t		 num_allow_ases;
47891d6bceSjob 	struct cert_ip	*deny_ips;	/* forbidden IP address ranges */
48*381ee599Stb 	size_t		 num_deny_ips;
49*381ee599Stb 	struct cert_as	*deny_ases;	/* forbidden AS numbers and ranges */
50*381ee599Stb 	size_t		 num_deny_ases;
51891d6bceSjob } tal_constraints[TALSZ_MAX];
52891d6bceSjob 
53891d6bceSjob /*
54891d6bceSjob  * If there is a .constraints file next to a .tal file, load its contents
55891d6bceSjob  * into into tal_constraints[talid]. The load function only opens the fd
56891d6bceSjob  * and stores the filename. The actual parsing happens in constraints_parse().
57891d6bceSjob  * Resources of EE certs can then be constrained using constraints_validate().
58891d6bceSjob  */
59891d6bceSjob 
60891d6bceSjob static void
61891d6bceSjob constraints_load_talid(int talid)
62891d6bceSjob {
63891d6bceSjob 	const char	*tal = tals[talid];
6489e02b0fSjob 	char		*constraints = NULL, *warning = NULL, *cbn;
65891d6bceSjob 	int		 fd;
66891d6bceSjob 	size_t		 len;
67891d6bceSjob 	int		 saved_errno;
68891d6bceSjob 
69891d6bceSjob 	tal_constraints[talid].fd = -1;
70891d6bceSjob 
71891d6bceSjob 	if (rtype_from_file_extension(tal) != RTYPE_TAL)
72891d6bceSjob 		return;
73891d6bceSjob 
74891d6bceSjob 	/* Replace .tal suffix with .constraints. */
75891d6bceSjob 	len = strlen(tal) - 4;
76891d6bceSjob 	if (asprintf(&constraints, "%.*s.constraints", (int)len, tal) == -1)
7789e02b0fSjob 		err(1, NULL);
7889e02b0fSjob 
7989e02b0fSjob 	/* prepare warning message for when violations are detected */
8089e02b0fSjob 	if ((cbn = basename(constraints)) == NULL)
8189e02b0fSjob 		err(1, "basename");
8289e02b0fSjob 	if (asprintf(&warning, "resource violates %s", cbn) == -1)
8389e02b0fSjob 		err(1, NULL);
84891d6bceSjob 
85891d6bceSjob 	saved_errno = errno;
86891d6bceSjob 
87891d6bceSjob 	fd = open(constraints, O_RDONLY);
88891d6bceSjob 	if (fd == -1 && errno != ENOENT)
89891d6bceSjob 		err(1, "failed to load constraints for %s", tal);
90891d6bceSjob 
91891d6bceSjob 	tal_constraints[talid].fn = constraints;
92891d6bceSjob 	tal_constraints[talid].fd = fd;
9389e02b0fSjob 	tal_constraints[talid].warn = warning;
94891d6bceSjob 
95891d6bceSjob 	errno = saved_errno;
96891d6bceSjob }
97891d6bceSjob 
98891d6bceSjob /*
99891d6bceSjob  * Iterate over all TALs and load the corresponding constraints files.
100891d6bceSjob  */
101891d6bceSjob void
102891d6bceSjob constraints_load(void)
103891d6bceSjob {
104891d6bceSjob 	int	 talid;
105891d6bceSjob 
106891d6bceSjob 	for (talid = 0; talid < talsz; talid++)
107891d6bceSjob 		constraints_load_talid(talid);
108891d6bceSjob }
109891d6bceSjob 
110891d6bceSjob void
111891d6bceSjob constraints_unload(void)
112891d6bceSjob {
113891d6bceSjob 	int	 saved_errno, talid;
114891d6bceSjob 
115891d6bceSjob 	saved_errno = errno;
116891d6bceSjob 	for (talid = 0; talid < talsz; talid++) {
117891d6bceSjob 		if (tal_constraints[talid].fd != -1)
118891d6bceSjob 			close(tal_constraints[talid].fd);
119891d6bceSjob 		free(tal_constraints[talid].fn);
12089e02b0fSjob 		free(tal_constraints[talid].warn);
121891d6bceSjob 		tal_constraints[talid].fd = -1;
122891d6bceSjob 		tal_constraints[talid].fn = NULL;
12389e02b0fSjob 		tal_constraints[talid].warn = NULL;
124891d6bceSjob 	}
125891d6bceSjob 	errno = saved_errno;
126891d6bceSjob }
127891d6bceSjob 
128891d6bceSjob /*
129891d6bceSjob  * Split a string at '-' and trim whitespace around the '-'.
130891d6bceSjob  * Assumes leading and trailing whitespace in p has already been trimmed.
131891d6bceSjob  */
132891d6bceSjob static int
133891d6bceSjob constraints_split_range(char *p, const char **min, const char **max)
134891d6bceSjob {
135891d6bceSjob 	char	*pp;
136891d6bceSjob 
137891d6bceSjob 	*min = p;
138891d6bceSjob 	if ((*max = pp = strchr(p, '-')) == NULL)
139891d6bceSjob 		return 0;
140891d6bceSjob 
141891d6bceSjob 	/* Trim whitespace before '-'. */
142891d6bceSjob 	while (pp > *min && isspace((unsigned char)pp[-1]))
143891d6bceSjob 		pp--;
144891d6bceSjob 	*pp = '\0';
145891d6bceSjob 
146891d6bceSjob 	/* Skip past '-' and whitespace following it. */
147891d6bceSjob 	(*max)++;
148891d6bceSjob 	while (isspace((unsigned char)**max))
149891d6bceSjob 		(*max)++;
150891d6bceSjob 
151891d6bceSjob 	return 1;
152891d6bceSjob }
153891d6bceSjob 
154891d6bceSjob /*
155891d6bceSjob  * Helper functions to parse textual representations of IP prefixes or ranges.
156891d6bceSjob  * The RFC 3779 API has poor error reporting, so as a debugging aid, we call
157891d6bceSjob  * the prohibitively expensive X509v3_addr_canonize() in high verbosity mode.
158891d6bceSjob  */
159891d6bceSjob 
160891d6bceSjob static void
161891d6bceSjob constraints_parse_ip_prefix(const char *fn, const char *prefix, enum afi afi,
162891d6bceSjob     IPAddrBlocks *addrs)
163891d6bceSjob {
164891d6bceSjob 	unsigned char	 addr[16] = { 0 };
165891d6bceSjob 	int		 af = afi == AFI_IPV4 ? AF_INET : AF_INET6;
166891d6bceSjob 	int		 plen;
167891d6bceSjob 
168891d6bceSjob 	if ((plen = inet_net_pton(af, prefix, addr, sizeof(addr))) == -1)
169891d6bceSjob 		errx(1, "%s: failed to parse %s", fn, prefix);
170891d6bceSjob 
171891d6bceSjob 	if (!X509v3_addr_add_prefix(addrs, afi, NULL, addr, plen))
172891d6bceSjob 		errx(1, "%s: failed to add prefix %s", fn, prefix);
173891d6bceSjob 
174891d6bceSjob 	if (verbose < 3)
175891d6bceSjob 		return;
176891d6bceSjob 
177891d6bceSjob 	if (!X509v3_addr_canonize(addrs))
178891d6bceSjob 		errx(1, "%s: failed to canonize with prefix %s", fn, prefix);
179891d6bceSjob }
180891d6bceSjob 
181891d6bceSjob static void
182891d6bceSjob constraints_parse_ip_range(const char *fn, const char *min, const char *max,
183891d6bceSjob     enum afi afi, IPAddrBlocks *addrs)
184891d6bceSjob {
185891d6bceSjob 	unsigned char	 min_addr[16] = {0}, max_addr[16] = {0};
186891d6bceSjob 	int		 af = afi == AFI_IPV4 ? AF_INET : AF_INET6;
187891d6bceSjob 
188891d6bceSjob 	if (inet_pton(af, min, min_addr) != 1)
189891d6bceSjob 		errx(1, "%s: failed to parse %s", fn, min);
190891d6bceSjob 	if (inet_pton(af, max, max_addr) != 1)
191891d6bceSjob 		errx(1, "%s: failed to parse %s", fn, max);
192891d6bceSjob 
193891d6bceSjob 	if (!X509v3_addr_add_range(addrs, afi, NULL, min_addr, max_addr))
194891d6bceSjob 		errx(1, "%s: failed to add range %s--%s", fn, min, max);
195891d6bceSjob 
196891d6bceSjob 	if (verbose < 3)
197891d6bceSjob 		return;
198891d6bceSjob 
199891d6bceSjob 	if (!X509v3_addr_canonize(addrs))
200891d6bceSjob 		errx(1, "%s: failed to canonize with range %s--%s", fn,
201891d6bceSjob 		    min, max);
202891d6bceSjob }
203891d6bceSjob 
204891d6bceSjob static void
205891d6bceSjob constraints_parse_ip(const char *fn, char *p, enum afi afi, IPAddrBlocks *addrs)
206891d6bceSjob {
207891d6bceSjob 	const char	*min, *max;
208891d6bceSjob 
209891d6bceSjob 	if (strchr(p, '-') == NULL) {
210891d6bceSjob 		constraints_parse_ip_prefix(fn, p, afi, addrs);
211891d6bceSjob 		return;
212891d6bceSjob 	}
213891d6bceSjob 
214891d6bceSjob 	if (!constraints_split_range(p, &min, &max))
215891d6bceSjob 		errx(1, "%s: failed to split range: %s", fn, p);
216891d6bceSjob 
217891d6bceSjob 	constraints_parse_ip_range(fn, min, max, afi, addrs);
218891d6bceSjob }
219891d6bceSjob 
220891d6bceSjob /*
221891d6bceSjob  * Helper functions to parse textual representations of AS numbers or ranges.
222891d6bceSjob  * The RFC 3779 API has poor error reporting, so as a debugging aid, we call
223891d6bceSjob  * the prohibitively expensive X509v3_asid_canonize() in high verbosity mode.
224891d6bceSjob  */
225891d6bceSjob 
226891d6bceSjob static void
227891d6bceSjob constraints_parse_asn(const char *fn, const char *asn, ASIdentifiers *asids)
228891d6bceSjob {
229891d6bceSjob 	ASN1_INTEGER	*id;
230891d6bceSjob 
231891d6bceSjob 	if ((id = s2i_ASN1_INTEGER(NULL, asn)) == NULL)
232891d6bceSjob 		errx(1, "%s: failed to parse AS %s", fn, asn);
233891d6bceSjob 
234891d6bceSjob 	if (!X509v3_asid_add_id_or_range(asids, V3_ASID_ASNUM, id, NULL))
235891d6bceSjob 		errx(1, "%s: failed to add AS %s", fn, asn);
236891d6bceSjob 
237891d6bceSjob 	if (verbose < 3)
238891d6bceSjob 		return;
239891d6bceSjob 
240891d6bceSjob 	if (!X509v3_asid_canonize(asids))
241891d6bceSjob 		errx(1, "%s: failed to canonize with AS %s", fn, asn);
242891d6bceSjob }
243891d6bceSjob 
244891d6bceSjob static void
245891d6bceSjob constraints_parse_asn_range(const char *fn, const char *min, const char *max,
246891d6bceSjob     ASIdentifiers *asids)
247891d6bceSjob {
248891d6bceSjob 	ASN1_INTEGER	*min_as, *max_as;
249891d6bceSjob 
250891d6bceSjob 	if ((min_as = s2i_ASN1_INTEGER(NULL, min)) == NULL)
251891d6bceSjob 		errx(1, "%s: failed to parse AS %s", fn, min);
252891d6bceSjob 	if ((max_as = s2i_ASN1_INTEGER(NULL, max)) == NULL)
253891d6bceSjob 		errx(1, "%s: failed to parse AS %s", fn, max);
254891d6bceSjob 
255891d6bceSjob 	if (!X509v3_asid_add_id_or_range(asids, V3_ASID_ASNUM, min_as, max_as))
256891d6bceSjob 		errx(1, "%s: failed to add AS range %s--%s", fn, min, max);
257891d6bceSjob 
258891d6bceSjob 	if (verbose < 3)
259891d6bceSjob 		return;
260891d6bceSjob 
261891d6bceSjob 	if (!X509v3_asid_canonize(asids))
262891d6bceSjob 		errx(1, "%s: failed to canonize with AS range %s--%s", fn,
263891d6bceSjob 		    min, max);
264891d6bceSjob }
265891d6bceSjob 
266891d6bceSjob static void
267891d6bceSjob constraints_parse_as(const char *fn, char *p, ASIdentifiers *asids)
268891d6bceSjob {
269891d6bceSjob 	const char	*min, *max;
270891d6bceSjob 
271891d6bceSjob 	if (strchr(p, '-') == NULL) {
272891d6bceSjob 		constraints_parse_asn(fn, p, asids);
273891d6bceSjob 		return;
274891d6bceSjob 	}
275891d6bceSjob 
276891d6bceSjob 	if (!constraints_split_range(p, &min, &max))
277891d6bceSjob 		errx(1, "%s: failed to split range: %s", fn, p);
278891d6bceSjob 
279891d6bceSjob 	constraints_parse_asn_range(fn, min, max, asids);
280891d6bceSjob }
281891d6bceSjob 
282891d6bceSjob /*
283891d6bceSjob  * Work around an annoying bug in X509v3_addr_add_range(). The upper bound
284891d6bceSjob  * of a range can have unused bits set in its ASN1_BIT_STRING representation.
285891d6bceSjob  * This triggers a check in ip_addr_parse(). A round trip through DER fixes
286891d6bceSjob  * this mess up. For extra special fun, {d2i,i2d}_IPAddrBlocks() isn't part
287891d6bceSjob  * of the API and implementing them for OpenSSL 3 is hairy, so do the round
288891d6bceSjob  * tripping once per address family.
289891d6bceSjob  */
290891d6bceSjob static void
291891d6bceSjob constraints_normalize_ip_addrblocks(const char *fn, IPAddrBlocks **addrs)
292891d6bceSjob {
293891d6bceSjob 	IPAddrBlocks		*new_addrs;
294891d6bceSjob 	IPAddressFamily		*af;
295891d6bceSjob 	const unsigned char	*p;
296891d6bceSjob 	unsigned char		*der;
297891d6bceSjob 	int			 der_len, i;
298891d6bceSjob 
299891d6bceSjob 	if ((new_addrs = IPAddrBlocks_new()) == NULL)
300891d6bceSjob 		err(1, NULL);
301891d6bceSjob 
302891d6bceSjob 	for (i = 0; i < sk_IPAddressFamily_num(*addrs); i++) {
303891d6bceSjob 		af = sk_IPAddressFamily_value(*addrs, i);
304891d6bceSjob 
305891d6bceSjob 		der = NULL;
306891d6bceSjob 		if ((der_len = i2d_IPAddressFamily(af, &der)) <= 0)
307891d6bceSjob 			errx(1, "%s: failed to convert to DER", fn);
308891d6bceSjob 		p = der;
309891d6bceSjob 		if ((af = d2i_IPAddressFamily(NULL, &p, der_len)) == NULL)
310891d6bceSjob 			errx(1, "%s: failed to convert from DER", fn);
311891d6bceSjob 		free(der);
312891d6bceSjob 
313891d6bceSjob 		if (!sk_IPAddressFamily_push(new_addrs, af))
314891d6bceSjob 			errx(1, "%s: failed to push constraints", fn);
315891d6bceSjob 	}
316891d6bceSjob 
317891d6bceSjob 	IPAddrBlocks_free(*addrs);
318891d6bceSjob 	*addrs = new_addrs;
319891d6bceSjob }
320891d6bceSjob 
321891d6bceSjob /*
322891d6bceSjob  * If there is a constraints file for tals[talid], load it into a buffer
323891d6bceSjob  * and parse it line by line. Leverage the above parse helpers to build up
324891d6bceSjob  * IPAddrBlocks and ASIdentifiers. We use the RFC 3779 API to benefit from
325891d6bceSjob  * the limited abilities of X509v3_{addr,asid}_canonize() to sort and merge
326891d6bceSjob  * adjacent ranges. This doesn't deal with overlaps or duplicates, but it's
327891d6bceSjob  * better than nothing.
328891d6bceSjob  */
329891d6bceSjob 
330891d6bceSjob static void
331891d6bceSjob constraints_parse_talid(int talid)
332891d6bceSjob {
333891d6bceSjob 	IPAddrBlocks	*allow_addrs, *deny_addrs;
334891d6bceSjob 	ASIdentifiers	*allow_asids, *deny_asids;
335891d6bceSjob 	FILE		*f;
336891d6bceSjob 	char		*fn, *p, *pp;
337*381ee599Stb 	struct cert_as	*allow_ases = NULL, *deny_ases = NULL;
338891d6bceSjob 	struct cert_ip	*allow_ips = NULL, *deny_ips = NULL;
339*381ee599Stb 	size_t		 num_allow_ases = 0, num_allow_ips = 0,
340*381ee599Stb 			 num_deny_as = 0, num_deny_ips = 0;
341891d6bceSjob 	char		*line = NULL;
342891d6bceSjob 	size_t		 len = 0;
343891d6bceSjob 	ssize_t		 n;
344891d6bceSjob 	int		 fd, have_allow_as = 0, have_allow_ips = 0,
345891d6bceSjob 			 have_deny_as = 0, have_deny_ips = 0;
346891d6bceSjob 
347891d6bceSjob 	fd = tal_constraints[talid].fd;
348891d6bceSjob 	fn = tal_constraints[talid].fn;
349891d6bceSjob 	tal_constraints[talid].fd = -1;
350891d6bceSjob 	tal_constraints[talid].fn = NULL;
351891d6bceSjob 
352891d6bceSjob 	if (fd == -1) {
353891d6bceSjob 		free(fn);
354891d6bceSjob 		return;
355891d6bceSjob 	}
356891d6bceSjob 
357891d6bceSjob 	if ((f = fdopen(fd, "r")) == NULL)
358891d6bceSjob 		err(1, "fdopen");
359891d6bceSjob 
360891d6bceSjob 	if ((allow_addrs = IPAddrBlocks_new()) == NULL)
361891d6bceSjob 		err(1, NULL);
362891d6bceSjob 	if ((allow_asids = ASIdentifiers_new()) == NULL)
363891d6bceSjob 		err(1, NULL);
364891d6bceSjob 	if ((deny_addrs = IPAddrBlocks_new()) == NULL)
365891d6bceSjob 		err(1, NULL);
366891d6bceSjob 	if ((deny_asids = ASIdentifiers_new()) == NULL)
367891d6bceSjob 		err(1, NULL);
368891d6bceSjob 
369891d6bceSjob 	while ((n = getline(&line, &len, f)) != -1) {
370891d6bceSjob 		if (line[n - 1] == '\n')
371891d6bceSjob 			line[n - 1] = '\0';
372891d6bceSjob 
373891d6bceSjob 		p = line;
374891d6bceSjob 
375891d6bceSjob 		/* Zap leading whitespace */
376891d6bceSjob 		while (isspace((unsigned char)*p))
377891d6bceSjob 			p++;
378891d6bceSjob 
379891d6bceSjob 		/* Zap comments */
380891d6bceSjob 		if ((pp = strchr(p, '#')) != NULL)
381891d6bceSjob 			*pp = '\0';
382891d6bceSjob 
383891d6bceSjob 		/* Zap trailing whitespace */
384891d6bceSjob 		if (pp == NULL)
385891d6bceSjob 			pp = p + strlen(p);
386891d6bceSjob 		while (pp > p && isspace((unsigned char)pp[-1]))
387891d6bceSjob 			pp--;
388891d6bceSjob 		*pp = '\0';
389891d6bceSjob 
390891d6bceSjob 		if (strlen(p) == 0)
391891d6bceSjob 			continue;
392891d6bceSjob 
393891d6bceSjob 		if (strncmp(p, "allow", strlen("allow")) == 0) {
394891d6bceSjob 			p += strlen("allow");
395891d6bceSjob 
396891d6bceSjob 			/* Ensure there's whitespace and jump over it. */
397891d6bceSjob 			if (!isspace((unsigned char)*p))
398891d6bceSjob 				errx(1, "%s: failed to parse %s", fn, p);
399891d6bceSjob 			while (isspace((unsigned char)*p))
400891d6bceSjob 				p++;
401891d6bceSjob 
402891d6bceSjob 			if (strchr(p, '.') != NULL) {
403891d6bceSjob 				constraints_parse_ip(fn, p, AFI_IPV4,
404891d6bceSjob 				    allow_addrs);
405891d6bceSjob 				have_allow_ips = 1;
406891d6bceSjob 			} else if (strchr(p, ':') != NULL) {
407891d6bceSjob 				constraints_parse_ip(fn, p, AFI_IPV6,
408891d6bceSjob 				    allow_addrs);
409891d6bceSjob 				have_allow_ips = 1;
410891d6bceSjob 			} else {
411891d6bceSjob 				constraints_parse_as(fn, p, allow_asids);
412891d6bceSjob 				have_allow_as = 1;
413891d6bceSjob 			}
414891d6bceSjob 		} else if (strncmp(p, "deny", strlen("deny")) == 0) {
415891d6bceSjob 			p += strlen("deny");
416891d6bceSjob 
417891d6bceSjob 			/* Ensure there's whitespace and jump over it. */
418891d6bceSjob 			if (!isspace((unsigned char)*p))
419891d6bceSjob 				errx(1, "%s: failed to parse %s", fn, p);
420891d6bceSjob 			/* Zap leading whitespace */
421891d6bceSjob 			while (isspace((unsigned char)*p))
422891d6bceSjob 				p++;
423891d6bceSjob 
424891d6bceSjob 			if (strchr(p, '.') != NULL) {
425891d6bceSjob 				constraints_parse_ip(fn, p, AFI_IPV4,
426891d6bceSjob 				    deny_addrs);
427891d6bceSjob 				have_deny_ips = 1;
428891d6bceSjob 			} else if (strchr(p, ':') != NULL) {
429891d6bceSjob 				constraints_parse_ip(fn, p, AFI_IPV6,
430891d6bceSjob 				    deny_addrs);
431891d6bceSjob 				have_deny_ips = 1;
432891d6bceSjob 			} else {
433891d6bceSjob 				constraints_parse_as(fn, p, deny_asids);
434891d6bceSjob 				have_deny_as = 1;
435891d6bceSjob 			}
436891d6bceSjob 		} else
437891d6bceSjob 			errx(1, "%s: failed to parse %s", fn, p);
438891d6bceSjob 	}
439891d6bceSjob 	free(line);
440891d6bceSjob 
441891d6bceSjob 	if (ferror(f))
442891d6bceSjob 		err(1, "%s", fn);
443891d6bceSjob 	fclose(f);
444891d6bceSjob 
445891d6bceSjob 	if (!X509v3_addr_canonize(allow_addrs))
446891d6bceSjob 		errx(1, "%s: failed to canonize IP addresses allowlist", fn);
447891d6bceSjob 	if (!X509v3_asid_canonize(allow_asids))
448891d6bceSjob 		errx(1, "%s: failed to canonize AS numbers allowlist", fn);
449891d6bceSjob 	if (!X509v3_addr_canonize(deny_addrs))
450891d6bceSjob 		errx(1, "%s: failed to canonize IP addresses denylist", fn);
451891d6bceSjob 	if (!X509v3_asid_canonize(deny_asids))
452891d6bceSjob 		errx(1, "%s: failed to canonize AS numbers denylist", fn);
453891d6bceSjob 
454891d6bceSjob 	if (have_allow_as) {
455*381ee599Stb 		if (!sbgp_parse_assysnum(fn, allow_asids, &allow_ases,
456*381ee599Stb 		    &num_allow_ases))
457891d6bceSjob 			errx(1, "%s: failed to parse AS identifiers allowlist",
458891d6bceSjob 			    fn);
459891d6bceSjob 	}
460891d6bceSjob 	if (have_deny_as) {
461*381ee599Stb 		if (!sbgp_parse_assysnum(fn, deny_asids, &deny_ases,
462*381ee599Stb 		    &num_deny_as))
463891d6bceSjob 			errx(1, "%s: failed to parse AS identifiers denylist",
464891d6bceSjob 			    fn);
465891d6bceSjob 	}
466891d6bceSjob 	if (have_allow_ips) {
467891d6bceSjob 		constraints_normalize_ip_addrblocks(fn, &allow_addrs);
468891d6bceSjob 
469891d6bceSjob 		if (!sbgp_parse_ipaddrblk(fn, allow_addrs, &allow_ips,
470*381ee599Stb 		    &num_allow_ips))
471891d6bceSjob 			errx(1, "%s: failed to parse IP addresses allowlist",
472891d6bceSjob 			    fn);
473891d6bceSjob 	}
474891d6bceSjob 	if (have_deny_ips) {
475891d6bceSjob 		constraints_normalize_ip_addrblocks(fn, &deny_addrs);
476891d6bceSjob 
477891d6bceSjob 		if (!sbgp_parse_ipaddrblk(fn, deny_addrs, &deny_ips,
478*381ee599Stb 		    &num_deny_ips))
479891d6bceSjob 			errx(1, "%s: failed to parse IP addresses denylist",
480891d6bceSjob 			    fn);
481891d6bceSjob 	}
482891d6bceSjob 
483*381ee599Stb 	tal_constraints[talid].allow_ases = allow_ases;
484*381ee599Stb 	tal_constraints[talid].num_allow_ases = num_allow_ases;
485891d6bceSjob 	tal_constraints[talid].allow_ips = allow_ips;
486*381ee599Stb 	tal_constraints[talid].num_allow_ips = num_allow_ips;
487*381ee599Stb 	tal_constraints[talid].deny_ases = deny_ases;
488*381ee599Stb 	tal_constraints[talid].num_deny_ases = num_deny_as;
489891d6bceSjob 	tal_constraints[talid].deny_ips = deny_ips;
490*381ee599Stb 	tal_constraints[talid].num_deny_ips = num_deny_ips;
491891d6bceSjob 
492891d6bceSjob 	IPAddrBlocks_free(allow_addrs);
493891d6bceSjob 	IPAddrBlocks_free(deny_addrs);
494891d6bceSjob 	ASIdentifiers_free(allow_asids);
495891d6bceSjob 	ASIdentifiers_free(deny_asids);
496891d6bceSjob 
497891d6bceSjob 	free(fn);
498891d6bceSjob }
499891d6bceSjob 
500891d6bceSjob /*
501891d6bceSjob  * Iterate over all TALs and parse the constraints files loaded previously.
502891d6bceSjob  */
503891d6bceSjob void
504891d6bceSjob constraints_parse(void)
505891d6bceSjob {
506891d6bceSjob 	int	 talid;
507891d6bceSjob 
508891d6bceSjob 	for (talid = 0; talid < talsz; talid++)
509891d6bceSjob 		constraints_parse_talid(talid);
510891d6bceSjob }
511891d6bceSjob 
512891d6bceSjob static int
513891d6bceSjob constraints_check_as(const char *fn, struct cert_as *cert,
514*381ee599Stb     const struct cert_as *allow_ases, size_t num_allow_ases,
515*381ee599Stb     const struct cert_as *deny_ases, size_t num_deny_ases)
516891d6bceSjob {
517891d6bceSjob 	uint32_t min, max;
518891d6bceSjob 
519891d6bceSjob 	/* Inheriting EE resources are not to be constrained. */
520891d6bceSjob 	if (cert->type == CERT_AS_INHERIT)
521891d6bceSjob 		return 1;
522891d6bceSjob 
523891d6bceSjob 	if (cert->type == CERT_AS_ID) {
524891d6bceSjob 		min = cert->id;
525891d6bceSjob 		max = cert->id;
526891d6bceSjob 	} else {
527891d6bceSjob 		min = cert->range.min;
528891d6bceSjob 		max = cert->range.max;
529891d6bceSjob 	}
530891d6bceSjob 
531*381ee599Stb 	if (deny_ases != NULL) {
532*381ee599Stb 		if (!as_check_overlap(cert, fn, deny_ases, num_deny_ases, 1))
533891d6bceSjob 			return 0;
534891d6bceSjob 	}
535*381ee599Stb 	if (allow_ases != NULL) {
536*381ee599Stb 		if (as_check_covered(min, max, allow_ases, num_allow_ases) <= 0)
537891d6bceSjob 			return 0;
538891d6bceSjob 	}
539891d6bceSjob 	return 1;
540891d6bceSjob }
541891d6bceSjob 
542891d6bceSjob static int
543891d6bceSjob constraints_check_ips(const char *fn, struct cert_ip *cert,
544*381ee599Stb     const struct cert_ip *allow_ips, size_t num_allow_ips,
545*381ee599Stb     const struct cert_ip *deny_ips, size_t num_deny_ips)
546891d6bceSjob {
547891d6bceSjob 	/* Inheriting EE resources are not to be constrained. */
548891d6bceSjob 	if (cert->type == CERT_IP_INHERIT)
549891d6bceSjob 		return 1;
550891d6bceSjob 
551891d6bceSjob 	if (deny_ips != NULL) {
552*381ee599Stb 		if (!ip_addr_check_overlap(cert, fn, deny_ips, num_deny_ips, 1))
553891d6bceSjob 			return 0;
554891d6bceSjob 	}
555891d6bceSjob 	if (allow_ips != NULL) {
556891d6bceSjob 		if (ip_addr_check_covered(cert->afi, cert->min, cert->max,
557*381ee599Stb 		    allow_ips, num_allow_ips) <= 0)
558891d6bceSjob 			return 0;
559891d6bceSjob 	}
560891d6bceSjob 	return 1;
561891d6bceSjob }
562891d6bceSjob 
563891d6bceSjob /*
564891d6bceSjob  * Check whether an EE cert's resources are covered by its TAL's constraints.
565891d6bceSjob  * We accept certs with a negative talid as "unknown TAL" for filemode. The
566891d6bceSjob  * logic nearly duplicates valid_cert().
567891d6bceSjob  */
568891d6bceSjob int
569891d6bceSjob constraints_validate(const char *fn, const struct cert *cert)
570891d6bceSjob {
571891d6bceSjob 	int		 talid = cert->talid;
572*381ee599Stb 	struct cert_as	*allow_ases, *deny_ases;
573891d6bceSjob 	struct cert_ip	*allow_ips, *deny_ips;
574*381ee599Stb 	size_t		 num_allow_ases, num_allow_ips;
575*381ee599Stb 	size_t		 num_deny_ases, num_deny_ips;
576*381ee599Stb 	size_t		 i;
577891d6bceSjob 
578891d6bceSjob 	/* Accept negative talid to bypass validation. */
579891d6bceSjob 	if (talid < 0)
580891d6bceSjob 		return 1;
581891d6bceSjob 	if (talid >= talsz)
582891d6bceSjob 		errx(1, "%s: talid out of range %d", fn, talid);
583891d6bceSjob 
584*381ee599Stb 	allow_ases = tal_constraints[talid].allow_ases;
585*381ee599Stb 	num_allow_ases = tal_constraints[talid].num_allow_ases;
586*381ee599Stb 	deny_ases = tal_constraints[talid].deny_ases;
587*381ee599Stb 	num_deny_ases = tal_constraints[talid].num_deny_ases;
588891d6bceSjob 
589*381ee599Stb 	for (i = 0; i < cert->num_ases; i++) {
590*381ee599Stb 		if (constraints_check_as(fn, &cert->ases[i],
591*381ee599Stb 		    allow_ases, num_allow_ases, deny_ases, num_deny_ases))
592891d6bceSjob 			continue;
593891d6bceSjob 
594*381ee599Stb 		as_warn(fn, tal_constraints[talid].warn, &cert->ases[i]);
595891d6bceSjob 		return 0;
596891d6bceSjob 	}
597891d6bceSjob 
598891d6bceSjob 	allow_ips = tal_constraints[talid].allow_ips;
599*381ee599Stb 	num_allow_ips = tal_constraints[talid].num_allow_ips;
600891d6bceSjob 	deny_ips = tal_constraints[talid].deny_ips;
601*381ee599Stb 	num_deny_ips = tal_constraints[talid].num_deny_ips;
602891d6bceSjob 
603*381ee599Stb 	for (i = 0; i < cert->num_ips; i++) {
604891d6bceSjob 		if (constraints_check_ips(fn, &cert->ips[i], allow_ips,
605*381ee599Stb 		    num_allow_ips, deny_ips, num_deny_ips))
606891d6bceSjob 			continue;
607891d6bceSjob 
60889e02b0fSjob 		ip_warn(fn, tal_constraints[talid].warn, &cert->ips[i]);
609891d6bceSjob 		return 0;
610891d6bceSjob 	}
611891d6bceSjob 
612891d6bceSjob 	return 1;
613891d6bceSjob }
614