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