xref: /openbsd-src/usr.sbin/rpki-client/validate.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: validate.c,v 1.14 2021/04/19 17:04:35 deraadt 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 			continue;
168 		min = cert->as[i].type == CERT_AS_ID ?
169 		    cert->as[i].id : cert->as[i].range.min;
170 		max = cert->as[i].type == CERT_AS_ID ?
171 		    cert->as[i].id : cert->as[i].range.max;
172 		if (valid_as(a, min, max))
173 			continue;
174 		warnx("%s: RFC 6487: uncovered AS: "
175 		    "%u--%u", fn, min, max);
176 		tracewarn(a);
177 		return 0;
178 	}
179 
180 	for (i = 0; i < cert->ipsz; i++) {
181 		if (valid_ip(a, cert->ips[i].afi, cert->ips[i].min,
182 		    cert->ips[i].max))
183 			continue;
184 		switch (cert->ips[i].type) {
185 		case CERT_IP_RANGE:
186 			ip_addr_print(&cert->ips[i].range.min,
187 			    cert->ips[i].afi, buf1, sizeof(buf1));
188 			ip_addr_print(&cert->ips[i].range.max,
189 			    cert->ips[i].afi, buf2, sizeof(buf2));
190 			warnx("%s: RFC 6487: uncovered IP: "
191 			    "%s--%s", fn, buf1, buf2);
192 			break;
193 		case CERT_IP_ADDR:
194 			ip_addr_print(&cert->ips[i].ip,
195 			    cert->ips[i].afi, buf1, sizeof(buf1));
196 			warnx("%s: RFC 6487: uncovered IP: "
197 			    "%s", fn, buf1);
198 		case CERT_IP_INHERIT:
199 			warnx("%s: RFC 6487: uncovered IP: "
200 			    "(inherit)", fn);
201 			break;
202 		}
203 		tracewarn(a);
204 		return 0;
205 	}
206 
207 	return 1;
208 }
209 
210 /*
211  * Validate our ROA: check that the SKI is unique, the AKI exists, and
212  * the IP prefix is also contained.
213  * Returns 1 if valid, 0 otherwise.
214  */
215 int
216 valid_roa(const char *fn, struct auth_tree *auths, struct roa *roa)
217 {
218 	struct auth	*a;
219 	size_t	 i;
220 	char	 buf[64];
221 
222 	a = valid_ski_aki(fn, auths, roa->ski, roa->aki);
223 	if (a == NULL)
224 		return 0;
225 
226 	if ((roa->tal = strdup(a->tal)) == NULL)
227 		err(1, NULL);
228 
229 	for (i = 0; i < roa->ipsz; i++) {
230 		if (valid_ip(a, roa->ips[i].afi, roa->ips[i].min,
231 		    roa->ips[i].max))
232 			continue;
233 		ip_addr_print(&roa->ips[i].addr,
234 		    roa->ips[i].afi, buf, sizeof(buf));
235 		warnx("%s: RFC 6482: uncovered IP: "
236 		    "%s", fn, buf);
237 		tracewarn(a);
238 		return 0;
239 	}
240 
241 	return 1;
242 }
243 
244 /*
245  * Validate a file by verifying the SHA256 hash of that file.
246  * Returns 1 if valid, 0 otherwise.
247  */
248 int
249 valid_filehash(const char *fn, const char *hash, size_t hlen)
250 {
251 	SHA256_CTX ctx;
252 	char	filehash[SHA256_DIGEST_LENGTH];
253 	char	buffer[8192];
254 	ssize_t	nr;
255 	int	fd;
256 
257 	if (hlen != sizeof(filehash))
258 		errx(1, "bad hash size");
259 
260 	if ((fd = open(fn, O_RDONLY)) == -1)
261 		return 0;
262 
263 	SHA256_Init(&ctx);
264 	while ((nr = read(fd, buffer, sizeof(buffer))) > 0)
265 		SHA256_Update(&ctx, buffer, nr);
266 	close(fd);
267 
268 	SHA256_Final(filehash, &ctx);
269 	if (memcmp(hash, filehash, sizeof(filehash)) != 0)
270 		return 0;
271 
272 	return 1;
273 }
274 
275 /*
276  * Validate a URI to make sure it is pure ASCII and does not point backwards
277  * or doing some other silly tricks. To enforce the protocol pass either
278  * https:// or rsync:// as proto, if NULL is passed no protocol is enforced.
279  * Returns 1 if valid, 0 otherwise.
280  */
281 int
282 valid_uri(const char *uri, size_t usz, const char *proto)
283 {
284 	size_t s;
285 
286 	for (s = 0; s < usz; s++)
287 		if (!isalnum((unsigned char)uri[s]) &&
288 		    !ispunct((unsigned char)uri[s]))
289 			return 0;
290 
291 	if (proto != NULL) {
292 		s = strlen(proto);
293 		if (strncasecmp(uri, proto, s) != 0)
294 			return 0;
295 	}
296 
297 	/* do not allow files or directories to start with a '.' */
298 	if (strstr(uri, "/.") != NULL)
299 		return 0;
300 
301 	return 1;
302 }
303