1 /*	$OpenBSD: constraints.c,v 1.18 2023/12/13 05:59:50 tb Exp $	*/
2 /*
3  * Copyright (c) 2020 Bob Beck <beck@openbsd.org>
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 <err.h>
19 #include <string.h>
20 
21 #include <openssl/safestack.h>
22 #include <openssl/x509.h>
23 #include <openssl/x509v3.h>
24 #include "x509_internal.h"
25 
26 #define FAIL(msg, ...)							\
27 do {									\
28 	fprintf(stderr, "[%s:%d] FAIL: ", __FILE__, __LINE__);		\
29 	fprintf(stderr, msg, ##__VA_ARGS__);				\
30 } while(0)
31 
32 unsigned char *valid_hostnames[] = {
33 	"openbsd.org",
34 	"op3nbsd.org",
35 	"org",
36 	"3openbsd.com",
37 	"3-0penb-d.c-m",
38 	"a",
39 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
40 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
41 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
42 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
43 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
44 	"open_bsd.org", /* because this is liberal */
45 	NULL,
46 };
47 
48 unsigned char *valid_sandns_names[] = {
49 	"*.ca",
50 	"*.op3nbsd.org",
51 	"c*.openbsd.org",
52 	"foo.*.d*.c*.openbsd.org",
53 	NULL,
54 };
55 
56 unsigned char *valid_domain_constraints[] = {
57 	"",
58 	".ca",
59 	".op3nbsd.org",
60 	".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
61 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
62 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
63 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
64 	"www.openbsd.org",
65 	NULL,
66 };
67 
68 unsigned char *valid_mbox_names[] = {
69 	"\"!#$%&\\\"*+-/=?\002^_`{|}~.\"@openbsd.org",
70 	"beck@openbsd.org",
71 	"beck@openbsd.org",
72 	"beck@op3nbsd.org",
73 	"beck@org",
74 	"beck@3openbsd.com",
75 	"beck@3-0penb-d.c-m",
76 	"bec@a",
77 	"beck@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
78 	"beck@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
79 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
80 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
81 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
82 	"beck@open_bsd.org", /* because this is liberal */
83 	NULL,
84 };
85 
86 unsigned char *invalid_hostnames[] = {
87 	"openbsd.org.",
88 	"openbsd..org",
89 	"openbsd.org-",
90 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
91 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
92 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
93 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
94 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a",
95 	"-p3nbsd.org",
96 	"openbs-.org",
97 	"openbsd\n.org",
98 	"open\177bsd.org",
99 	"open\255bsd.org",
100 	"*.openbsd.org",
101 	NULL,
102 };
103 
104 unsigned char *invalid_sandns_names[] = {
105 	"",
106 	".",
107 	"*.a",
108 	"*.",
109 	"*.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
110 	".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
111 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
112 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
113 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a",
114 	"*.-p3nbsd.org",
115 	"*.*..openbsd.org",
116 	"*..openbsd.org",
117 	".openbsd.org",
118 	"c*c.openbsd.org",
119 	NULL,
120 };
121 
122 unsigned char *invalid_mbox_names[] = {
123 	"beck@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
124 	"beck@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
125 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
126 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
127 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a",
128 	"beck@.-openbsd.org",
129 	"beck@.openbsd.org.",
130 	"beck@.a",
131 	"beck@.",
132 	"beck@",
133 	"beck@.ca",
134 	"@openbsd.org",
135 	NULL,
136 };
137 
138 unsigned char *invalid_domain_constraints[] = {
139 	".",
140 	".a",
141 	"..",
142 	".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
143 	".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
144 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
145 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
146 	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a",
147 	".-p3nbsd.org",
148 	"..openbsd.org",
149 	NULL,
150 };
151 
152 unsigned char *invaliduri[] = {
153 	"https://-www.openbsd.org",
154 	"https://.www.openbsd.org/",
155 	"https://www.ope|nbsd.org%",
156 	"https://www.openbsd.org.#",
157 	"https://192.168.1.1./",
158 	"https://192.168.1.1|/",
159 	"https://.192.168.1.1/",
160 	"https://192.168..1.1/",
161 	"https://.2001:0DB8:AC10:FE01::/",
162 	"https://.2001:0DB8:AC10:FE01::|/",
163 	"///",
164 	"//",
165 	"/",
166 	"",
167 	NULL,
168 };
169 
170 unsigned char *validuri[] = {
171 	"https://www.openbsd.org/meep/meep/meep/",
172 	"https://192.168.1.1/",
173 	"https://2001:0DB8:AC10:FE01::/",
174 	"https://192.168.1/",  /* Not an IP, but valid component */
175 	"https://999.999.999.999/", /* Not an IP, but valid component */
176 	NULL,
177 };
178 
179 static int
180 test_valid_hostnames(void)
181 {
182 	int i, failure = 0;
183 
184 	for (i = 0; valid_hostnames[i] != NULL; i++) {
185 		CBS cbs;
186 		CBS_init(&cbs, valid_hostnames[i], strlen(valid_hostnames[i]));
187 		if (!x509_constraints_valid_host(&cbs, 0)) {
188 			FAIL("Valid hostname '%s' rejected\n",
189 			    valid_hostnames[i]);
190 			failure = 1;
191 			goto done;
192 		}
193 		CBS_init(&cbs, valid_hostnames[i], strlen(valid_hostnames[i]));
194 		if (!x509_constraints_valid_sandns(&cbs)) {
195 			FAIL("Valid sandns '%s' rejected\n",
196 			    valid_hostnames[i]);
197 			failure = 1;
198 			goto done;
199 		}
200 	}
201 
202  done:
203 	return failure;
204 }
205 
206 static int
207 test_valid_sandns_names(void)
208 {
209 	int i, failure = 0;
210 	for (i = 0; valid_sandns_names[i] != NULL; i++) {
211 		CBS cbs;
212 		CBS_init(&cbs, valid_sandns_names[i],
213 		    strlen(valid_sandns_names[i]));
214 		if (!x509_constraints_valid_sandns(&cbs)) {
215 			FAIL("Valid dnsname '%s' rejected\n",
216 			    valid_sandns_names[i]);
217 			failure = 1;
218 			goto done;
219 		}
220 	}
221 
222  done:
223 	return failure;
224 }
225 
226 static int
227 test_valid_domain_constraints(void)
228 {
229 	int i, failure = 0;
230 	for (i = 0; valid_domain_constraints[i] != NULL; i++) {
231 		CBS cbs;
232 		CBS_init(&cbs, valid_domain_constraints[i],
233 		    strlen(valid_domain_constraints[i]));
234 		if (!x509_constraints_valid_domain_constraint(&cbs)) {
235 			FAIL("Valid dnsname '%s' rejected\n",
236 			    valid_domain_constraints[i]);
237 			failure = 1;
238 			goto done;
239 		}
240 	}
241 
242  done:
243 	return failure;
244 }
245 
246 static int
247 test_valid_mbox_names(void)
248 {
249 	struct x509_constraints_name name = {0};
250 	int i, failure = 0;
251 	for (i = 0; valid_mbox_names[i] != NULL; i++) {
252 		CBS cbs;
253 		CBS_init(&cbs, valid_mbox_names[i],
254 		    strlen(valid_mbox_names[i]));
255 		if (!x509_constraints_parse_mailbox(&cbs, &name)) {
256 			FAIL("Valid mailbox name '%s' rejected\n",
257 			    valid_mbox_names[i]);
258 			failure = 1;
259 			goto done;
260 		}
261 		free(name.name);
262 		name.name = NULL;
263 		free(name.local);
264 		name.local = NULL;
265 	}
266 
267  done:
268 	return failure;
269 }
270 
271 static int
272 test_invalid_hostnames(void)
273 {
274 	int i, failure = 0;
275 	char *nulhost = "www.openbsd.org\0";
276 	CBS cbs;
277 
278 	for (i = 0; invalid_hostnames[i] != NULL; i++) {
279 		CBS_init(&cbs, invalid_hostnames[i],
280 		    strlen(invalid_hostnames[i]));
281 		if (x509_constraints_valid_host(&cbs, 0)) {
282 			FAIL("Invalid hostname '%s' accepted\n",
283 			    invalid_hostnames[i]);
284 			failure = 1;
285 			goto done;
286 		}
287 	}
288 	CBS_init(&cbs, nulhost, strlen(nulhost) + 1);
289 	if (x509_constraints_valid_host(&cbs, 0)) {
290 		FAIL("hostname with NUL byte accepted\n");
291 		failure = 1;
292 		goto done;
293 	}
294 	CBS_init(&cbs, nulhost, strlen(nulhost) + 1);
295 	if (x509_constraints_valid_sandns(&cbs)) {
296 		FAIL("sandns with NUL byte accepted\n");
297 		failure = 1;
298 		goto done;
299 	}
300 
301  done:
302 	return failure;
303 }
304 
305 static int
306 test_invalid_sandns_names(void)
307 {
308 	int i, failure = 0;
309 	for (i = 0; invalid_sandns_names[i] != NULL; i++) {
310 		CBS cbs;
311 		CBS_init(&cbs, invalid_sandns_names[i],
312 		    strlen(invalid_sandns_names[i]));
313 		if (x509_constraints_valid_sandns(&cbs)) {
314 			FAIL("Valid dnsname '%s' rejected\n",
315 			    invalid_sandns_names[i]);
316 			failure = 1;
317 			goto done;
318 		}
319 	}
320 
321  done:
322 	return failure;
323 }
324 
325 static int
326 test_invalid_mbox_names(void)
327 {
328 	int i, failure = 0;
329 	struct x509_constraints_name name = {0};
330 	for (i = 0; invalid_mbox_names[i] != NULL; i++) {
331 		CBS cbs;
332 		CBS_init(&cbs, invalid_mbox_names[i],
333 		    strlen(invalid_mbox_names[i]));
334 		if (x509_constraints_parse_mailbox(&cbs, &name)) {
335 			FAIL("invalid mailbox name '%s' accepted\n",
336 			    invalid_mbox_names[i]);
337 			failure = 1;
338 			goto done;
339 		}
340 		free(name.name);
341 		name.name = NULL;
342 		free(name.local);
343 		name.local = NULL;
344 	}
345 
346  done:
347 	return failure;
348 }
349 
350 static int
351 test_invalid_domain_constraints(void)
352 {
353 	int i, failure = 0;
354 	for (i = 0; invalid_domain_constraints[i] != NULL; i++) {
355 		CBS cbs;
356 		CBS_init(&cbs, invalid_domain_constraints[i],
357 		    strlen(invalid_domain_constraints[i]));
358 		if (x509_constraints_valid_domain_constraint(&cbs)) {
359 			FAIL("invalid dnsname '%s' accepted\n",
360 			    invalid_domain_constraints[i]);
361 			failure = 1;
362 			goto done;
363 		}
364 	}
365 
366  done:
367 	return failure;
368 }
369 
370 static int
371 test_invalid_uri(void)
372 {
373 	int j, failure = 0;
374 	char *hostpart = NULL;
375 
376 	for (j = 0; invaliduri[j] != NULL; j++) {
377 		if (x509_constraints_uri_host(invaliduri[j],
378 		    strlen(invaliduri[j]), &hostpart) != 0) {
379 			FAIL("invalid URI '%s' accepted\n",
380 			    invaliduri[j]);
381 			failure = 1;
382 			goto done;
383 		}
384 		free(hostpart);
385 		hostpart = NULL;
386 	}
387 
388  done:
389 	return failure;
390 }
391 
392 static int
393 test_valid_uri(void)
394 {
395 	int j, failure = 0;
396 	char *hostpart = NULL;
397 
398 	for (j = 0; validuri[j] != NULL; j++) {
399 		if (x509_constraints_uri_host(validuri[j],
400 		    strlen(invaliduri[j]), &hostpart) == 0) {
401 			FAIL("Valid URI '%s' NOT accepted\n",
402 			    validuri[j]);
403 			failure = 1;
404 			goto done;
405 		}
406 		free(hostpart);
407 		hostpart = NULL;
408 	}
409 
410  done:
411 	return failure;
412 }
413 
414 static int
415 test_constraints1(void)
416 {
417 	char *c;
418 	size_t cl;
419 	char *d;
420 	size_t dl;
421 	int failure = 0;
422 	int error = 0;
423 	int i, j;
424 	unsigned char *constraints[] = {
425 		".org",
426 		".openbsd.org",
427 		"www.openbsd.org",
428 		NULL,
429 	};
430 	unsigned char *failing[] = {
431 		".ca",
432 		"openbsd.ca",
433 		"org",
434 		NULL,
435 	};
436 	unsigned char *matching[] = {
437 		"www.openbsd.org",
438 		NULL,
439 	};
440 	unsigned char *matchinguri[] = {
441 		"https://www.openbsd.org",
442 		"https://www.openbsd.org/",
443 		"https://www.openbsd.org?",
444 		"https://www.openbsd.org#",
445 		"herp://beck@www.openbsd.org:",
446 		"spiffe://beck@www.openbsd.org/this/is/so/spiffe/",
447 		NULL,
448 	};
449 	unsigned char *failinguri[] = {
450 		"https://www.openbsd.ca",
451 		"https://www.freebsd.com/",
452 		"https://www.openbsd.net?",
453 		"https://org#",
454 		"herp://beck@org:",
455 		"///",
456 		"//",
457 		"/",
458 		"",
459 		NULL,
460 	};
461 	unsigned char *noauthority[] = {
462 		"urn:open62541.server.application",
463 		NULL,
464 	};
465 	for (i = 0; constraints[i] != NULL; i++) {
466 		char *constraint = constraints[i];
467 		size_t clen = strlen(constraints[i]);
468 		for (j = 0; matching[j] != NULL; j++) {
469 			if (!x509_constraints_domain(matching[j],
470 			    strlen(matching[j]), constraint, clen)) {
471 				FAIL("constraint '%s' should have matched"
472 				    " '%s'\n",
473 				    constraint, matching[j]);
474 				failure = 1;
475 				goto done;
476 			}
477 		}
478 		for (j = 0; matchinguri[j] != NULL; j++) {
479 			error = 0;
480 			if (!x509_constraints_uri(matchinguri[j],
481 			    strlen(matchinguri[j]), constraint, clen, &error)) {
482 				FAIL("constraint '%s' should have matched URI"
483 				    " '%s' (error %d)\n",
484 				    constraint, matchinguri[j], error);
485 				failure = 1;
486 				goto done;
487 			}
488 		}
489 		for (j = 0; failing[j] != NULL; j++) {
490 			if (x509_constraints_domain(failing[j],
491 			    strlen(failing[j]), constraint, clen)) {
492 				FAIL("constraint '%s' should not have matched"
493 				    " '%s'\n",
494 				    constraint, failing[j]);
495 				failure = 1;
496 				goto done;
497 			}
498 		}
499 		for (j = 0; failinguri[j] != NULL; j++) {
500 			error = 0;
501 			if (x509_constraints_uri(failinguri[j],
502 			    strlen(failinguri[j]), constraint, clen, &error)) {
503 				FAIL("constraint '%s' should not have matched URI"
504 				    " '%s' (error %d)\n",
505 				    constraint, failinguri[j], error);
506 				failure = 1;
507 				goto done;
508 			}
509 		}
510 		for (j = 0; noauthority[j] != NULL; j++) {
511 			char *hostpart = NULL;
512 			error = 0;
513 			if (!x509_constraints_uri_host(noauthority[j],
514 			    strlen(noauthority[j]), NULL) ||
515 			    !x509_constraints_uri_host(noauthority[j],
516 			    strlen(noauthority[j]), &hostpart)) {
517 				FAIL("name '%s' should parse as a URI",
518 				    noauthority[j]);
519 				failure = 1;
520 				free(hostpart);
521 				goto done;
522 			}
523 			free(hostpart);
524 
525 			if (x509_constraints_uri(noauthority[j],
526 			    strlen(noauthority[j]), constraint, clen, &error)) {
527 				FAIL("constraint '%s' should not have matched URI"
528 				    " '%s' (error %d)\n",
529 				    constraint, failinguri[j], error);
530 				failure = 1;
531 				goto done;
532 			}
533 		}
534 	}
535 	c = ".openbsd.org";
536 	cl = strlen(".openbsd.org");
537 	d = "*.openbsd.org";
538 	dl = strlen("*.openbsd.org");
539 	if (!x509_constraints_domain(d, dl, c, cl)) {
540 		FAIL("constraint '%s' should have matched '%s'\n",
541 		    c, d);
542 		failure = 1;
543 		goto done;
544 	}
545 	c = "www.openbsd.org";
546 	cl = strlen("www.openbsd.org");
547 	if (x509_constraints_domain(d, dl, c, cl)) {
548 		FAIL("constraint '%s' should not have matched '%s'\n",
549 		    c, d);
550 		failure = 1;
551 		goto done;
552 	}
553 	c = "";
554 	cl = 0;
555 	if (!x509_constraints_domain(d, dl, c, cl)) {
556 		FAIL("constraint '%s' should have matched '%s'\n",
557 		    c, d);
558 		failure = 1;
559 		goto done;
560 	}
561 
562  done:
563 	return failure;
564 }
565 
566 int
567 main(int argc, char **argv)
568 {
569 	int failed = 0;
570 
571 	failed |= test_valid_hostnames();
572 	failed |= test_invalid_hostnames();
573 	failed |= test_valid_sandns_names();
574 	failed |= test_invalid_sandns_names();
575 	failed |= test_valid_mbox_names();
576 	failed |= test_invalid_mbox_names();
577 	failed |= test_valid_domain_constraints();
578 	failed |= test_invalid_domain_constraints();
579 	failed |= test_invalid_uri();
580 	failed |= test_valid_uri();
581 	failed |= test_constraints1();
582 
583 	return (failed);
584 }
585