xref: /openbsd-src/usr.sbin/rpki-client/validate.c (revision 4e1ee0786f11cc571bd0be17d38e46f635c719fc)
1 /*	$OpenBSD: validate.c,v 1.16 2021/10/11 16:50:03 job Exp $ */
2 /*
3  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/socket.h>
19 
20 #include <arpa/inet.h>
21 #include <assert.h>
22 #include <ctype.h>
23 #include <err.h>
24 #include <fcntl.h>
25 #include <inttypes.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "extern.h"
32 
33 static void
34 tracewarn(const struct auth *a)
35 {
36 
37 	for (; a != NULL; a = a->parent)
38 		warnx(" ...inheriting from: %s", a->fn);
39 }
40 
41 /*
42  * Walk up the chain of certificates trying to match our AS number to
43  * one of the allocations in that chain.
44  * Returns 1 if covered or 0 if not.
45  */
46 static int
47 valid_as(struct auth *a, uint32_t min, uint32_t max)
48 {
49 	int	 c;
50 
51 	if (a == NULL)
52 		return 0;
53 
54 	/* Does this certificate cover our AS number? */
55 	if (a->cert->asz) {
56 		c = as_check_covered(min, max, a->cert->as, a->cert->asz);
57 		if (c > 0)
58 			return 1;
59 		else if (c < 0)
60 			return 0;
61 	}
62 
63 	/* If it doesn't, walk up the chain. */
64 	return valid_as(a->parent, min, max);
65 }
66 
67 /*
68  * Walk up the chain of certificates (really just the last one, but in
69  * the case of inheritence, the ones before) making sure that our IP
70  * prefix is covered in the first non-inheriting specification.
71  * Returns 1 if covered or 0 if not.
72  */
73 static int
74 valid_ip(struct auth *a, enum afi afi,
75     const unsigned char *min, const unsigned char *max)
76 {
77 	int	 c;
78 
79 	if (a == NULL)
80 		return 0;
81 
82 	/* Does this certificate cover our IP prefix? */
83 	c = ip_addr_check_covered(afi, min, max, a->cert->ips, a->cert->ipsz);
84 	if (c > 0)
85 		return 1;
86 	else if (c < 0)
87 		return 0;
88 
89 	/* If it doesn't, walk up the chain. */
90 	return valid_ip(a->parent, afi, min, max);
91 }
92 
93 /*
94  * Make sure that the SKI doesn't already exist and return the parent by
95  * its AKI.
96  * Returns the parent auth or NULL on failure.
97  */
98 struct auth *
99 valid_ski_aki(const char *fn, struct auth_tree *auths,
100     const char *ski, const char *aki)
101 {
102 	struct auth *a;
103 
104 	if (auth_find(auths, ski) != NULL) {
105 		warnx("%s: RFC 6487: duplicate SKI", fn);
106 		return NULL;
107 	}
108 
109 	a = auth_find(auths, aki);
110 	if (a == NULL)
111 		warnx("%s: RFC 6487: unknown AKI", fn);
112 
113 	return a;
114 }
115 
116 /*
117  * Authenticate a trust anchor by making sure its resources are not
118  * inheriting and that the SKI is unique.
119  * Returns 1 if valid, 0 otherwise.
120  */
121 int
122 valid_ta(const char *fn, struct auth_tree *auths, const struct cert *cert)
123 {
124 	size_t	 i;
125 
126 	/* AS and IP resources must not inherit. */
127 	if (cert->asz && cert->as[0].type == CERT_AS_INHERIT) {
128 		warnx("%s: RFC 6487 (trust anchor): "
129 		    "inheriting AS resources", fn);
130 		return 0;
131 	}
132 	for (i = 0; i < cert->ipsz; i++)
133 		if (cert->ips[i].type == CERT_IP_INHERIT) {
134 			warnx("%s: RFC 6487 (trust anchor): "
135 			    "inheriting IP resources", fn);
136 			return 0;
137 		}
138 
139 	/* SKI must not be a dupe. */
140 	if (auth_find(auths, cert->ski) != NULL) {
141 		warnx("%s: RFC 6487: duplicate SKI", fn);
142 		return 0;
143 	}
144 
145 	return 1;
146 }
147 
148 /*
149  * Validate a non-TA certificate: make sure its IP and AS resources are
150  * fully covered by those in the authority key (which must exist).
151  * Returns 1 if valid, 0 otherwise.
152  */
153 int
154 valid_cert(const char *fn, struct auth_tree *auths, const struct cert *cert)
155 {
156 	struct auth	*a;
157 	size_t		 i;
158 	uint32_t	 min, max;
159 	char		 buf1[64], buf2[64];
160 
161 	a = valid_ski_aki(fn, auths, cert->ski, cert->aki);
162 	if (a == NULL)
163 		return 0;
164 
165 	for (i = 0; i < cert->asz; i++) {
166 		if (cert->as[i].type == CERT_AS_INHERIT) {
167 			if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER)
168 				return 0; /* BGPsec doesn't permit inheriting */
169 			continue;
170 		}
171 		min = cert->as[i].type == CERT_AS_ID ?
172 		    cert->as[i].id : cert->as[i].range.min;
173 		max = cert->as[i].type == CERT_AS_ID ?
174 		    cert->as[i].id : cert->as[i].range.max;
175 		if (valid_as(a, min, max))
176 			continue;
177 		warnx("%s: RFC 6487: uncovered AS: "
178 		    "%u--%u", fn, min, max);
179 		tracewarn(a);
180 		return 0;
181 	}
182 
183 	for (i = 0; i < cert->ipsz; i++) {
184 		if (valid_ip(a, cert->ips[i].afi, cert->ips[i].min,
185 		    cert->ips[i].max))
186 			continue;
187 		switch (cert->ips[i].type) {
188 		case CERT_IP_RANGE:
189 			ip_addr_print(&cert->ips[i].range.min,
190 			    cert->ips[i].afi, buf1, sizeof(buf1));
191 			ip_addr_print(&cert->ips[i].range.max,
192 			    cert->ips[i].afi, buf2, sizeof(buf2));
193 			warnx("%s: RFC 6487: uncovered IP: "
194 			    "%s--%s", fn, buf1, buf2);
195 			break;
196 		case CERT_IP_ADDR:
197 			ip_addr_print(&cert->ips[i].ip,
198 			    cert->ips[i].afi, buf1, sizeof(buf1));
199 			warnx("%s: RFC 6487: uncovered IP: "
200 			    "%s", fn, buf1);
201 			break;
202 		case CERT_IP_INHERIT:
203 			warnx("%s: RFC 6487: uncovered IP: "
204 			    "(inherit)", fn);
205 			break;
206 		}
207 		tracewarn(a);
208 		return 0;
209 	}
210 
211 	return 1;
212 }
213 
214 /*
215  * Validate our ROA: check that the SKI is unique, the AKI exists, and
216  * the IP prefix is also contained.
217  * Returns 1 if valid, 0 otherwise.
218  */
219 int
220 valid_roa(const char *fn, struct auth_tree *auths, struct roa *roa)
221 {
222 	struct auth	*a;
223 	size_t	 i;
224 	char	 buf[64];
225 
226 	a = valid_ski_aki(fn, auths, roa->ski, roa->aki);
227 	if (a == NULL)
228 		return 0;
229 
230 	if ((roa->tal = strdup(a->tal)) == NULL)
231 		err(1, NULL);
232 
233 	for (i = 0; i < roa->ipsz; i++) {
234 		if (valid_ip(a, roa->ips[i].afi, roa->ips[i].min,
235 		    roa->ips[i].max))
236 			continue;
237 		ip_addr_print(&roa->ips[i].addr,
238 		    roa->ips[i].afi, buf, sizeof(buf));
239 		warnx("%s: RFC 6482: uncovered IP: "
240 		    "%s", fn, buf);
241 		tracewarn(a);
242 		return 0;
243 	}
244 
245 	return 1;
246 }
247 
248 /*
249  * Validate a file by verifying the SHA256 hash of that file.
250  * Returns 1 if valid, 0 otherwise.
251  */
252 int
253 valid_filehash(const char *fn, const char *hash, size_t hlen)
254 {
255 	SHA256_CTX ctx;
256 	char	filehash[SHA256_DIGEST_LENGTH];
257 	char	buffer[8192];
258 	ssize_t	nr;
259 	int	fd;
260 
261 	if (hlen != sizeof(filehash))
262 		errx(1, "bad hash size");
263 
264 	if ((fd = open(fn, O_RDONLY)) == -1)
265 		return 0;
266 
267 	SHA256_Init(&ctx);
268 	while ((nr = read(fd, buffer, sizeof(buffer))) > 0)
269 		SHA256_Update(&ctx, buffer, nr);
270 	close(fd);
271 
272 	SHA256_Final(filehash, &ctx);
273 	if (memcmp(hash, filehash, sizeof(filehash)) != 0)
274 		return 0;
275 
276 	return 1;
277 }
278 
279 /*
280  * Validate a URI to make sure it is pure ASCII and does not point backwards
281  * or doing some other silly tricks. To enforce the protocol pass either
282  * https:// or rsync:// as proto, if NULL is passed no protocol is enforced.
283  * Returns 1 if valid, 0 otherwise.
284  */
285 int
286 valid_uri(const char *uri, size_t usz, const char *proto)
287 {
288 	size_t s;
289 
290 	for (s = 0; s < usz; s++)
291 		if (!isalnum((unsigned char)uri[s]) &&
292 		    !ispunct((unsigned char)uri[s]))
293 			return 0;
294 
295 	if (proto != NULL) {
296 		s = strlen(proto);
297 		if (strncasecmp(uri, proto, s) != 0)
298 			return 0;
299 	}
300 
301 	/* do not allow files or directories to start with a '.' */
302 	if (strstr(uri, "/.") != NULL)
303 		return 0;
304 
305 	return 1;
306 }
307