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 	int j, failure=0;
336 	char *hostpart;
337 	for (j = 0; invaliduri[j] != NULL; j++) {
338 		if (x509_constraints_uri_host(invaliduri[j],
339 			strlen(invaliduri[j]), &hostpart) != 0) {
340 			FAIL("invalid URI '%s' accepted\n",
341 			    invaliduri[j]);
342 			failure = 1;
343 			goto done;
344 		}
345 	}
346  done:
347 	return failure;
348 }
349 
350 static int
351 test_constraints1(void)
352 {
353 	char *c; size_t cl;
354 	char *d; size_t dl;
355 	int failure = 0;
356 	int error = 0;
357 	int i, j;
358 	unsigned char *constraints[] = {
359 		".org",
360 		".openbsd.org",
361 		"www.openbsd.org",
362 		NULL,
363 	};
364 	unsigned char *failing[] = {
365 		".ca",
366 		"openbsd.ca",
367 		"org",
368 		NULL,
369 	};
370 	unsigned char *matching[] = {
371 		"www.openbsd.org",
372 		NULL,
373 	};
374 	unsigned char *matchinguri[] = {
375 		"https://www.openbsd.org",
376 		"https://www.openbsd.org/",
377 		"https://www.openbsd.org?",
378 		"https://www.openbsd.org#",
379 		"herp://beck@www.openbsd.org:",
380 		"spiffe://beck@www.openbsd.org/this/is/so/spiffe/",
381 		NULL,
382 	};
383 	unsigned char *failinguri[] = {
384 		"https://www.openbsd.ca",
385 		"https://www.freebsd.com/",
386 		"https://www.openbsd.net?",
387 		"https://org#",
388 		"herp://beck@org:",
389 		"///",
390 		"//",
391 		"/",
392 		"",
393 		NULL,
394 	};
395 	for (i = 0; constraints[i] != NULL; i++) {
396 		char *constraint = constraints[i];
397 		size_t clen = strlen(constraints[i]);
398 		for (j = 0; matching[j] != NULL; j++) {
399 			if (!x509_constraints_domain(matching[j],
400 			    strlen(matching[j]), constraint, clen)) {
401 				FAIL("constraint '%s' should have matched"
402 				    " '%s'\n",
403 				    constraint, matching[j]);
404 				failure = 1;
405 				goto done;
406 			}
407 		}
408 		for (j = 0; matchinguri[j] != NULL; j++) {
409 			error = 0;
410 			if (!x509_constraints_uri(matchinguri[j],
411 			    strlen(matchinguri[j]), constraint, clen, &error)) {
412 				FAIL("constraint '%s' should have matched URI"
413 				    " '%s' (error %d)\n",
414 				    constraint, matchinguri[j], error);
415 				failure = 1;
416 				goto done;
417 			}
418 		}
419 		for (j = 0; failing[j] != NULL; j++) {
420 			if (x509_constraints_domain(failing[j],
421 			    strlen(failing[j]), constraint, clen)) {
422 				FAIL("constraint '%s' should not have matched"
423 				    " '%s'\n",
424 				    constraint, failing[j]);
425 				failure = 1;
426 				goto done;
427 			}
428 		}
429 		for (j = 0; failinguri[j] != NULL; j++) {
430 			error = 0;
431 			if (x509_constraints_uri(failinguri[j],
432 			    strlen(failinguri[j]), constraint, clen, &error)) {
433 				FAIL("constraint '%s' should not have matched URI"
434 				    " '%s' (error %d)\n",
435 				    constraint, failinguri[j], error);
436 				failure = 1;
437 				goto done;
438 			}
439 		}
440 	}
441 	c = ".openbsd.org";
442 	cl = strlen(".openbsd.org");
443 	d = "*.openbsd.org";
444 	dl = strlen("*.openbsd.org");
445 	if (!x509_constraints_domain(d, dl, c, cl)) {
446 		FAIL("constraint '%s' should have matched '%s'\n",
447 		    c, d);
448 		failure = 1;
449 		goto done;
450 	}
451 	c = "www.openbsd.org";
452 	cl = strlen("www.openbsd.org");
453 	if (x509_constraints_domain(d, dl, c, cl)) {
454 		FAIL("constraint '%s' should not have matched '%s'\n",
455 		    c, d);
456 		failure = 1;
457 		goto done;
458 	}
459 	c = "";
460 	cl = 0;
461 	if (!x509_constraints_domain(d, dl, c, cl)) {
462 		FAIL("constraint '%s' should have matched '%s'\n",
463 		    c, d);
464 		failure = 1;
465 		goto done;
466 	}
467  done:
468 	return failure;
469 }
470 
471 int
472 main(int argc, char **argv)
473 {
474 	int failed = 0;
475 
476 	failed |= test_valid_hostnames();
477 	failed |= test_invalid_hostnames();
478 	failed |= test_valid_sandns_names();
479 	failed |= test_invalid_sandns_names();
480 	failed |= test_valid_mbox_names();
481 	failed |= test_invalid_mbox_names();
482 	failed |= test_valid_domain_constraints();
483 	failed |= test_invalid_domain_constraints();
484 	failed |= test_invalid_uri();
485 	failed |= test_constraints1();
486 
487 	return (failed);
488 }
489