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
test_valid_hostnames(void)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
test_valid_sandns_names(void)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
test_valid_domain_constraints(void)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
test_valid_mbox_names(void)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
test_invalid_hostnames(void)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
test_invalid_sandns_names(void)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
test_invalid_mbox_names(void)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
test_invalid_domain_constraints(void)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
test_invalid_uri(void)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
test_valid_uri(void)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
test_constraints1(void)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
main(int argc,char ** argv)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