summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorbeck <>2020-09-11 18:34:29 +0000
committerbeck <>2020-09-11 18:34:29 +0000
commit77c3247aa0b565ea6bf2032c2d2d20413a0d5af4 (patch)
tree72fbaf173cde91943a60047f07ba0ce99e8116ab /src/lib
parent188f2a73ec9cc4314b9998227079cccb89e8677a (diff)
downloadopenbsd-77c3247aa0b565ea6bf2032c2d2d20413a0d5af4.tar.gz
openbsd-77c3247aa0b565ea6bf2032c2d2d20413a0d5af4.tar.bz2
openbsd-77c3247aa0b565ea6bf2032c2d2d20413a0d5af4.zip
Add x509_constraints.c - a new implementation of x509 name constraints, with
regression tests. The use of the new name constraints is not yet activated in x509_vfy.c and will be activated in a follow on commit ok jsing@
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/libcrypto/Makefile4
-rw-r--r--src/lib/libcrypto/x509/x509_constraints.c1180
-rw-r--r--src/lib/libcrypto/x509/x509_internal.h90
3 files changed, 1272 insertions, 2 deletions
diff --git a/src/lib/libcrypto/Makefile b/src/lib/libcrypto/Makefile
index 2e778a5b23..8f11313d58 100644
--- a/src/lib/libcrypto/Makefile
+++ b/src/lib/libcrypto/Makefile
@@ -1,4 +1,4 @@
1# $OpenBSD: Makefile,v 1.43 2020/09/11 14:30:51 beck Exp $ 1# $OpenBSD: Makefile,v 1.44 2020/09/11 18:34:29 beck Exp $
2 2
3LIB= crypto 3LIB= crypto
4LIBREBUILD=y 4LIBREBUILD=y
@@ -277,7 +277,7 @@ SRCS+= x509_bcons.c x509_bitst.c x509_conf.c x509_extku.c x509_ia5.c x509_lib.c
277SRCS+= x509_prn.c x509_utl.c x509_genn.c x509_alt.c x509_skey.c x509_akey.c x509_pku.c 277SRCS+= x509_prn.c x509_utl.c x509_genn.c x509_alt.c x509_skey.c x509_akey.c x509_pku.c
278SRCS+= x509_int.c x509_enum.c x509_sxnet.c x509_cpols.c x509_crld.c x509_purp.c x509_info.c 278SRCS+= x509_int.c x509_enum.c x509_sxnet.c x509_cpols.c x509_crld.c x509_purp.c x509_info.c
279SRCS+= x509_ocsp.c x509_akeya.c x509_pmaps.c x509_pcons.c x509_ncons.c x509_pcia.c x509_pci.c 279SRCS+= x509_ocsp.c x509_akeya.c x509_pmaps.c x509_pcons.c x509_ncons.c x509_pcia.c x509_pci.c
280SRCS+= x509_issuer_cache.c 280SRCS+= x509_issuer_cache.c x509_constraints.c
281SRCS+= pcy_cache.c pcy_node.c pcy_data.c pcy_map.c pcy_tree.c pcy_lib.c 281SRCS+= pcy_cache.c pcy_node.c pcy_data.c pcy_map.c pcy_tree.c pcy_lib.c
282 282
283.PATH: ${.CURDIR}/arch/${MACHINE_CPU} \ 283.PATH: ${.CURDIR}/arch/${MACHINE_CPU} \
diff --git a/src/lib/libcrypto/x509/x509_constraints.c b/src/lib/libcrypto/x509/x509_constraints.c
new file mode 100644
index 0000000000..ab22cbf812
--- /dev/null
+++ b/src/lib/libcrypto/x509/x509_constraints.c
@@ -0,0 +1,1180 @@
1/* $OpenBSD: x509_constraints.c,v 1.1 2020/09/11 18:34:29 beck 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 <ctype.h>
19#include <errno.h>
20#include <stdio.h>
21#include <string.h>
22#include <time.h>
23#include <unistd.h>
24
25#include <sys/socket.h>
26#include <arpa/inet.h>
27
28#include <openssl/safestack.h>
29#include <openssl/x509.h>
30#include <openssl/x509v3.h>
31
32#include "x509_internal.h"
33
34/* RFC 2821 section 4.5.3.1 */
35#define LOCAL_PART_MAX_LEN 64
36#define DOMAIN_PART_MAX_LEN 255
37
38struct x509_constraints_name *
39x509_constraints_name_new()
40{
41 return (calloc(1, sizeof(struct x509_constraints_name)));
42}
43
44void
45x509_constraints_name_clear(struct x509_constraints_name *name)
46{
47 free(name->name);
48 free(name->local);
49 free(name->der);
50 memset(name, 0, sizeof(*name));
51}
52
53void
54x509_constraints_name_free(struct x509_constraints_name *name)
55{
56 if (name == NULL)
57 return;
58 x509_constraints_name_clear(name);
59 free(name);
60}
61
62struct x509_constraints_name *
63x509_constraints_name_dup(struct x509_constraints_name *name)
64{
65 struct x509_constraints_name *new;
66
67 if ((new = x509_constraints_name_new()) == NULL)
68 goto err;
69 new->type = name->type;
70 new->af = name->af;
71 new->der_len = name->der_len;
72 if (name->der_len > 0 && (new->der = malloc(name->der_len)) == NULL)
73 goto err;
74 memcpy(new->der, name->der, name->der_len);
75 if (name->name != NULL && (new->name = strdup(name->name)) == NULL)
76 goto err;
77 if (name->local != NULL && (new->local = strdup(name->local)) == NULL)
78 goto err;
79 memcpy(new->address, name->address, sizeof(name->address));
80 return new;
81 err:
82 x509_constraints_name_free(new);
83 return NULL;
84}
85
86struct x509_constraints_names *
87x509_constraints_names_new()
88{
89 return (calloc(1, sizeof(struct x509_constraints_names)));
90}
91
92void
93x509_constraints_names_clear(struct x509_constraints_names *names)
94{
95 size_t i;
96
97 for (i = 0; i < names->names_count; i++)
98 x509_constraints_name_free(names->names[i]);
99 free(names->names);
100 memset(names, 0, sizeof(*names));
101}
102
103void
104x509_constraints_names_free(struct x509_constraints_names *names)
105{
106 if (names == NULL)
107 return;
108
109 x509_constraints_names_clear(names);
110 free(names);
111}
112
113int
114x509_constraints_names_add(struct x509_constraints_names *names,
115 struct x509_constraints_name *name)
116{
117 size_t i = names->names_count;
118
119 if (names->names_count == names->names_len) {
120 struct x509_constraints_name **tmp;
121 if ((tmp = recallocarray(names->names, names->names_len,
122 names->names_len + 32, sizeof(*tmp))) == NULL)
123 return 0;
124 names->names_len += 32;
125 names->names = tmp;
126 }
127 names->names[i] = name;
128 names->names_count++;
129 return 1;
130}
131
132struct x509_constraints_names *
133x509_constraints_names_dup(struct x509_constraints_names *names)
134{
135 struct x509_constraints_names *new = NULL;
136 struct x509_constraints_name *name = NULL;
137 size_t i;
138
139 if (names == NULL)
140 return NULL;
141
142 if ((new = x509_constraints_names_new()) == NULL)
143 goto err;
144 for (i = 0; i < names->names_count; i++) {
145 if ((name = x509_constraints_name_dup(names->names[i])) == NULL)
146 goto err;
147 if (!x509_constraints_names_add(new, name))
148 goto err;
149 }
150 return new;
151 err:
152 x509_constraints_names_free(new);
153 x509_constraints_name_free(name);
154 return NULL;
155}
156
157
158/*
159 * Validate that the name contains only a hostname consisting of RFC
160 * 5890 compliant A-labels (see RFC 6066 section 3). This is more
161 * permissive to allow for a leading '*' for a SAN DNSname wildcard,
162 * or a leading '.' for a subdomain based constraint, as well as
163 * allowing for '_' which is commonly accepted by nonconformant
164 * DNS implementaitons.
165 */
166static int
167x509_constraints_valid_domain_internal(uint8_t *name, size_t len)
168{
169 uint8_t prev, c = 0;
170 int component = 0;
171 int first;
172 size_t i;
173
174 if (len > DOMAIN_PART_MAX_LEN)
175 return 0;
176
177 for (i = 0; i < len; i++) {
178 prev = c;
179 c = name[i];
180
181 first = (i == 0);
182
183 /* Everything has to be ASCII, with no NUL byte */
184 if (!isascii(c) || c == '\0')
185 return 0;
186 /* It must be alphanumeric, a '-', '.', '_' or '*' */
187 if (!isalnum(c) && c != '-' && c != '.' && c != '_' &&
188 c != '*')
189 return 0;
190
191 /* '*' can only be the first thing. */
192 if (c == '*' && !first)
193 return 0;
194
195 /* '-' must not start a component or be at the end. */
196 if (c == '-' && (component == 0 || i == len - 1))
197 return 0;
198
199 /*
200 * '.' must not be at the end. It may be first overall
201 * but must not otherwise start a component.
202 */
203 if (c == '.' && ((component == 0 && !first) || i == len - 1))
204 return 0;
205
206 if (c == '.') {
207 /* Components can not end with a dash. */
208 if (prev == '-')
209 return 0;
210 /* Start new component */
211 component = 0;
212 continue;
213 }
214 /* Components must be 63 chars or less. */
215 if (++component > 63)
216 return 0;
217 }
218 return 1;
219}
220
221int
222x509_constraints_valid_domain(uint8_t *name, size_t len)
223{
224 if (len == 0)
225 return 0;
226 if (name[0] == '*') /* wildcard not allowed in a domain name */
227 return 0;
228 /*
229 * A domain may not be less than two characters, so you can't
230 * have a require subdomain name with less than that.
231 */
232 if (len < 3 && name[0] == '.')
233 return 0;
234 return x509_constraints_valid_domain_internal(name, len);
235}
236
237int
238x509_constraints_valid_host(uint8_t *name, size_t len)
239{
240 struct sockaddr_in sin4;
241 struct sockaddr_in6 sin6;
242
243 if (len == 0)
244 return 0;
245 if (name[0] == '*') /* wildcard not allowed in a host name */
246 return 0;
247 if (name[0] == '.') /* leading . not allowed in a host name*/
248 return 0;
249 if (inet_pton(AF_INET, name, &sin4) == 1)
250 return 0;
251 if (inet_pton(AF_INET6, name, &sin6) == 1)
252 return 0;
253 return x509_constraints_valid_domain_internal(name, len);
254}
255
256int
257x509_constraints_valid_sandns(uint8_t *name, size_t len)
258{
259 if (len == 0)
260 return 0;
261
262 if (name[0] == '.') /* leading . not allowed in a SAN DNS name */
263 return 0;
264 /*
265 * A domain may not be less than two characters, so you
266 * can't wildcard a single domain of less than that
267 */
268 if (len < 4 && name[0] == '*')
269 return 0;
270 /*
271 * A wildcard may only be followed by a '.'
272 */
273 if (len >= 4 && name[0] == '*' && name[1] != '.')
274 return 0;
275
276 return x509_constraints_valid_domain_internal(name, len);
277}
278
279static inline int
280local_part_ok(char c)
281{
282 return (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') ||
283 ('A' <= c && c <= 'Z') || c == '!' || c == '#' || c == '$' ||
284 c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' ||
285 c == '-' || c == '/' || c == '=' || c == '?' || c == '^' ||
286 c == '_' || c == '`' || c == '{' || c == '|' || c == '}' ||
287 c == '~' || c == '.');
288}
289
290/*
291 * Parse "candidate" as an RFC 2821 mailbox.
292 * Returns 0 if candidate is not a valid mailbox or if an error occurs.
293 * Returns 1 if candidate is a mailbox and adds newly allocated
294 * local and domain parts of the mailbox to "name->local" and name->name"
295 */
296int
297x509_constraints_parse_mailbox(uint8_t *candidate, size_t len,
298 struct x509_constraints_name *name)
299{
300 char working[DOMAIN_PART_MAX_LEN + 1] = { 0 };
301 char *candidate_local = NULL;
302 char *candidate_domain = NULL;
303 size_t i, wi = 0;
304 int accept = 0;
305 int quoted = 0;
306
307 if (candidate == NULL)
308 return 0;
309
310 /* It can't be bigger than the local part, domain part and the '@' */
311 if (len > LOCAL_PART_MAX_LEN + DOMAIN_PART_MAX_LEN + 1)
312 return 0;
313
314 for (i = 0; i < len; i++) {
315 char c = candidate[i];
316 /* non ascii, cr, lf, or nul is never allowed */
317 if (!isascii(c) || c == '\r' || c == '\n' || c == '\0')
318 goto bad;
319 if (i == 0) {
320 /* local part is quoted part */
321 if (c == '"')
322 quoted = 1;
323 /* can not start with a . */
324 if (c == '.')
325 goto bad;
326 }
327 if (wi > DOMAIN_PART_MAX_LEN)
328 goto bad;
329 if (accept) {
330 working[wi++] = c;
331 accept = 0;
332 continue;
333 }
334 if (candidate_local != NULL) {
335 /* We are looking for the domain part */
336 if (wi > DOMAIN_PART_MAX_LEN)
337 goto bad;
338 working[wi++] = c;
339 if (i == len - 1) {
340 if (wi == 0)
341 goto bad;
342 if (candidate_domain != NULL)
343 goto bad;
344 candidate_domain = strdup(working);
345 if (candidate_domain == NULL)
346 goto bad;
347 }
348 continue;
349 }
350 /* We are looking for the local part */
351 if (wi > LOCAL_PART_MAX_LEN)
352 break;
353
354 if (quoted) {
355 if (c == '\\') {
356 accept = 1;
357 continue;
358 }
359 if (c == '"' && i != 0) {
360 /* end the quoted part. @ must be next */
361 if (i + 1 == len || candidate[i + 1] != '@')
362 goto bad;
363 quoted = 0;
364 }
365 /*
366 * XXX Go strangely permits sp but forbids ht
367 * mimic that for now
368 */
369 if (c == 9)
370 goto bad;
371 working[wi++] = c;
372 continue; /* all's good inside our quoted string */
373 }
374 if (c == '@') {
375 if (wi == 0)
376 goto bad;;
377 if (candidate_local != NULL)
378 goto bad;
379 candidate_local = strdup(working);
380 if (candidate_local == NULL)
381 goto bad;
382 memset(working, 0, sizeof(working));
383 wi = 0;
384 continue;
385 }
386 if (c == '\\') {
387 /*
388 * RFC 3936 hints these can happen outside of
389 * quotend string. don't include the \ but
390 * next character must be ok.
391 */
392 if (i + 1 == len)
393 goto bad;
394 if (!local_part_ok(candidate[i + 1]))
395 goto bad;
396 accept = 1;
397 }
398 if (!local_part_ok(c))
399 goto bad;
400 working[wi++] = c;
401 }
402 if (candidate_local == NULL || candidate_domain == NULL)
403 goto bad;
404 if (!x509_constraints_valid_host(candidate_domain,
405 strlen(candidate_domain)))
406 goto bad;
407
408 name->local = candidate_local;
409 name->name = candidate_domain;
410 name->type = GEN_EMAIL;
411 return 1;
412 bad:
413 free(candidate_local);
414 free(candidate_domain);
415 return 0;
416}
417
418int
419x509_constraints_valid_domain_constraint(uint8_t *constraint, size_t len)
420{
421 if (len == 0)
422 return 1; /* empty constraints match */
423
424 if (constraint[0] == '*') /* wildcard not allowed in a constraint */
425 return 0;
426
427 /*
428 * A domain may not be less than two characters, so you
429 * can't match a single domain of less than that
430 */
431 if (len < 3 && constraint[0] == '.')
432 return 0;
433 return x509_constraints_valid_domain_internal(constraint, len);
434}
435
436/*
437 * Extract the host part of a URI, returns the host part as a c string
438 * the caller must free, or or NULL if it could not be found or is
439 * invalid.
440 *
441 * rfc 3986:
442 * the authority part of a uri starts with // and is terminated with
443 * the next '/', '?', '#' or end of the URI.
444 *
445 * The authority itself contains [userinfo '@'] host [: port]
446 *
447 * so the host starts at the start or after the '@', and ends
448 * with end of URI, '/', '?', "#', or ':'.
449 */
450int
451x509_constraints_uri_host(uint8_t *uri, size_t len, char**hostpart)
452{
453 size_t i, hostlen = 0;
454 uint8_t *authority = NULL;
455 char *host = NULL;
456
457 /* find first // */
458 for (i = 0; i < len - 1; i++) {
459 if (!isascii(uri[i]))
460 return 0;
461 if (uri[i] == '/' && uri[i + 1] == '/') {
462 authority = uri + i + 2;
463 break;
464 }
465 }
466 if (authority == NULL)
467 return 0;
468 for (i = authority - uri; i < len; i++) {
469 if (!isascii(uri[i]))
470 return 0;
471 /* it has a userinfo part */
472 if (uri[i] == '@') {
473 hostlen = 0;
474 /* it can only have one */
475 if (host != NULL)
476 break;
477 /* start after the userinfo part */
478 host = uri + i + 1;
479 continue;
480 }
481 /* did we find the end? */
482 if (uri[i] == ':' || uri[i] == '/' || uri[i] == '?' ||
483 uri[i] == '#')
484 break;
485 hostlen++;
486 }
487 if (hostlen == 0)
488 return 0;
489 if (host == NULL)
490 host = authority;
491 if (!x509_constraints_valid_host(host, hostlen))
492 return 0;
493 *hostpart = strndup(host, hostlen);
494 return 1;
495}
496
497int
498x509_constraints_sandns(char *sandns, size_t dlen, char *constraint,
499 size_t len)
500{
501 char *suffix;
502
503 if (len == 0)
504 return 1; /* an empty constraint matches everything */
505
506 /* match the end of the domain */
507 if (dlen < len)
508 return 0;
509 suffix = sandns + (dlen - len);
510 return (strncasecmp(suffix, constraint, len) == 0);
511}
512
513/*
514 * Validate a pre-validated domain of length dlen against a pre-validated
515 * constraint of length len.
516 *
517 * returns 1 if the domain and constraint match.
518 * returns 0 otherwise.
519 *
520 * an empty constraint matches everyting.
521 * constraint will be matched against the domain as a suffix if it
522 * starts with a '.'.
523 * domain will be matched against the constraint as a suffix if it
524 * starts with a '.'.
525 */
526int
527x509_constraints_domain(char *domain, size_t dlen, char *constraint,
528 size_t len)
529{
530 if (len == 0)
531 return 1; /* an empty constraint matches everything */
532
533 if (constraint[0] == '.') {
534 /* match the end of the domain */
535 char *suffix;
536 if (dlen < len)
537 return 0;
538 suffix = domain + (dlen - len);
539 return (strncasecmp(suffix, constraint, len) == 0);
540 }
541 if (domain[0] == '.') {
542 /* match the end of the constraint */
543 char *suffix;
544 if (len < dlen)
545 return 0;
546 suffix = constraint + (len - dlen);
547 return (strncasecmp(suffix, domain, dlen) == 0);
548 }
549 /* otherwise we must exactly match the constraint */
550 if (dlen != len)
551 return 0;
552 return (strncasecmp(domain, constraint, len) == 0);
553}
554
555int
556x509_constraints_uri(uint8_t *uri, size_t ulen, uint8_t *constraint,
557 size_t len, int *error)
558{
559 int ret = 0;
560 char *hostpart;
561
562 if (!x509_constraints_uri_host(uri, ulen, &hostpart)) {
563 *error = X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
564 goto err;
565 }
566 if (hostpart == NULL) {
567 *error = X509_V_ERR_OUT_OF_MEM;
568 goto err;
569 }
570 if (!x509_constraints_valid_domain_constraint(constraint, len)) {
571 *error = X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX;
572 goto err;
573 }
574 ret = x509_constraints_domain(hostpart, strlen(hostpart),
575 constraint, len);
576 err:
577 free(hostpart);
578 return ret;
579}
580
581/*
582 * Verify a validated address of size alen with a validated contraint
583 * of size constraint_len. returns 1 if matching, 0 if not.
584 * Addresses are assumed to be pre-validated for a length of 4 and 8
585 * respectively for ipv4 addreses and constraints, and a length of
586 * 16 and 32 respectively for ipv6 address constraints by the caller.
587 */
588int
589x509_constraints_ipaddr(uint8_t *address, size_t alen,
590 uint8_t *constraint, size_t len)
591{
592 uint8_t *mask;
593 size_t i;
594
595 if (alen * 2 != len)
596 return 0;
597
598 mask = constraint + alen;
599 for (i = 0; i < alen; i++) {
600 if ((address[i] & mask[i]) != (constraint[i] & mask[i]))
601 return 0;
602 }
603 return 1;
604}
605
606/*
607 * Verify a canonicalized der encoded constraint dirname
608 * a canonicalized der encoded constraint.
609 */
610int
611x509_constraints_dirname(uint8_t *dirname, size_t dlen,
612 uint8_t *constraint, size_t len)
613{
614 if (len != dlen)
615 return 0;
616 return (memcmp(constraint, dirname, len) == 0);
617}
618
619/*
620 * De-obfuscate a GENERAL_NAME into useful bytes for a name or constraint.
621 */
622int
623x509_constraints_general_to_bytes(GENERAL_NAME *name, uint8_t **bytes,
624 size_t *len)
625{
626 *bytes = NULL;
627 *len = 0;
628
629 if (name->type == GEN_DNS) {
630 ASN1_IA5STRING *aname = name->d.dNSName;
631 *bytes = aname->data;
632 *len = strlen(aname->data);
633 return name->type;
634 }
635 if (name->type == GEN_EMAIL) {
636 ASN1_IA5STRING *aname = name->d.rfc822Name;
637 *bytes = aname->data;
638 *len = strlen(aname->data);
639 return name->type;
640 }
641 if (name->type == GEN_URI) {
642 ASN1_IA5STRING *aname = name->d.uniformResourceIdentifier;
643 *bytes = aname->data;
644 *len = strlen(aname->data);
645 return name->type;
646 }
647 if (name->type == GEN_DIRNAME) {
648 X509_NAME *dname = name->d.directoryName;
649 if (!dname->modified || i2d_X509_NAME(dname, NULL) >= 0) {
650 *bytes = dname->canon_enc;
651 *len = dname->canon_enclen;
652 return name->type;
653 }
654 }
655 if (name->type == GEN_IPADD) {
656 *bytes = name->d.ip->data;
657 *len = name->d.ip->length;
658 return name->type;
659 }
660 return 0;
661}
662
663
664/*
665 * Extract the relevant names for constraint checking from "cert",
666 * validate them, and add them to the list of cert names for "chain".
667 * returns 1 on success sets error and returns 0 on failure.
668 */
669int
670x509_constraints_extract_names(struct x509_constraints_names *names,
671 X509 *cert, int is_leaf, int *error)
672{
673 struct x509_constraints_name *vname = NULL;
674 X509_NAME *subject_name;
675 GENERAL_NAME *name;
676 ssize_t i = 0;
677 int name_type, add, include_cn = is_leaf, include_email = is_leaf;
678
679 /* first grab the altnames */
680 while ((name = sk_GENERAL_NAME_value(cert->altname, i++)) != NULL) {
681 uint8_t *bytes = NULL;
682 size_t len = 0;
683
684 if ((vname = x509_constraints_name_new()) == NULL) {
685 *error = X509_V_ERR_OUT_OF_MEM;
686 goto err;
687 }
688
689 add = 1;
690 name_type = x509_constraints_general_to_bytes(name, &bytes,
691 &len);
692 switch(name_type) {
693 case GEN_DNS:
694 if (!x509_constraints_valid_sandns(bytes, len)) {
695 *error =
696 X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
697 goto err;
698 }
699 if ((vname->name = strdup(bytes)) == NULL) {
700 *error = X509_V_ERR_OUT_OF_MEM;
701 goto err;
702 }
703 vname->type=GEN_DNS;
704 include_cn = 0; /* don't use cn from subject */
705 break;
706 case GEN_EMAIL:
707 if (!x509_constraints_parse_mailbox(bytes, len,
708 vname)) {
709 *error = X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
710 goto err;
711 }
712 vname->type = GEN_EMAIL;
713 include_email = 0; /* don't use email from subject */
714 break;
715 case GEN_URI:
716 if (!x509_constraints_uri_host(bytes, len, &vname->name)) {
717 *error = X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
718 goto err;
719 }
720 if (vname->name == NULL) {
721 *error = X509_V_ERR_OUT_OF_MEM;
722 goto err;
723 }
724 vname->type = GEN_URI;
725 break;
726 case GEN_DIRNAME:
727 if (bytes == NULL || ((vname->der = malloc(len)) ==
728 NULL)) {
729 *error = X509_V_ERR_OUT_OF_MEM;
730 goto err;
731 }
732 if (len == 0) {
733 *error = X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
734 goto err;
735 }
736 memcpy(vname->der, bytes, len);
737 vname->der_len = len;
738 vname->type = GEN_DIRNAME;
739 break;
740 case GEN_IPADD:
741 if (len == 4)
742 vname->af = AF_INET;
743 if (len == 16)
744 vname->af = AF_INET6;
745 if (vname->af != AF_INET && vname->af !=
746 AF_INET6) {
747 *error =
748 X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
749 goto err;
750 }
751 memcpy(vname->address, bytes, len);
752 vname->type = GEN_IPADD;
753 break;
754 default:
755 /* Ignore this name */
756 add = 0;
757 break;
758 }
759 if (add && !x509_constraints_names_add(names, vname)) {
760 *error = X509_V_ERR_OUT_OF_MEM;
761 goto err;
762 }
763 vname = NULL;
764 }
765 subject_name = X509_get_subject_name(cert);
766 if (X509_NAME_entry_count(subject_name) > 0) {
767 struct x509_constraints_name *vname = NULL;
768 X509_NAME_ENTRY *email;
769 X509_NAME_ENTRY *cn;
770 /*
771 * This cert has a non-empty subject, so we must add
772 * the subject as a dirname to be compared against
773 * any dirname constraints
774 */
775 if ((subject_name->modified &&
776 i2d_X509_NAME(subject_name, NULL) < 0) ||
777 (vname = x509_constraints_name_new()) == NULL ||
778 (vname->der = malloc(subject_name->canon_enclen)) == NULL) {
779 *error = X509_V_ERR_OUT_OF_MEM;
780 goto err;
781 }
782
783 memcpy(vname->der, subject_name->canon_enc,
784 subject_name->canon_enclen);
785 vname->der_len = subject_name->canon_enclen;
786 vname->type = GEN_DIRNAME;
787 if (!x509_constraints_names_add(names, vname)) {
788 *error = X509_V_ERR_OUT_OF_MEM;
789 goto err;
790 }
791 vname = NULL;
792 /*
793 * Get any email addresses from the subject line, and
794 * add them as mbox names to be compared against any
795 * email constraints
796 */
797 while (include_email &&
798 (i = X509_NAME_get_index_by_NID(subject_name,
799 NID_pkcs9_emailAddress, i)) >= 0) {
800 ASN1_STRING *aname;
801 if ((email = X509_NAME_get_entry(subject_name, i)) == NULL ||
802 (aname = X509_NAME_ENTRY_get_data(email)) == NULL) {
803 *error = X509_V_ERR_OUT_OF_MEM;
804 goto err;
805 }
806 if ((vname = x509_constraints_name_new()) == NULL) {
807 *error = X509_V_ERR_OUT_OF_MEM;
808 goto err;
809 }
810 if (!x509_constraints_parse_mailbox(aname->data,
811 strlen(aname->data), vname)) {
812 *error = X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
813 goto err;
814 }
815 vname->type = GEN_EMAIL;
816 if (!x509_constraints_names_add(names, vname)) {
817 *error = X509_V_ERR_OUT_OF_MEM;
818 goto err;
819 }
820 vname = NULL;
821 }
822 /*
823 * Include the CN as a hostname to be checked againt
824 * name constraints if it looks like a hostname.
825 */
826 while (include_cn &&
827 (i = X509_NAME_get_index_by_NID(subject_name,
828 NID_commonName, i)) >= 0) {
829 ASN1_STRING *aname;
830 if ((cn = X509_NAME_get_entry(subject_name, i)) == NULL ||
831 (aname = X509_NAME_ENTRY_get_data(cn)) == NULL) {
832 *error = X509_V_ERR_OUT_OF_MEM;
833 goto err;
834 }
835 if (!x509_constraints_valid_host(aname->data,
836 strlen(aname->data)))
837 continue; /* ignore it if not a hostname */
838 if ((vname = x509_constraints_name_new()) == NULL) {
839 *error = X509_V_ERR_OUT_OF_MEM;
840 goto err;
841 }
842 if ((vname->name = strdup(aname->data)) == NULL) {
843 *error = X509_V_ERR_OUT_OF_MEM;
844 goto err;
845 }
846 vname->type = GEN_DNS;
847 if (!x509_constraints_names_add(names, vname)) {
848 *error = X509_V_ERR_OUT_OF_MEM;
849 goto err;
850 }
851 vname = NULL;
852 }
853 }
854 return 1;
855 err:
856 x509_constraints_name_free(vname);
857 return 0;
858}
859
860/*
861 * Validate a constraint in a general name, putting the relevant data
862 * into "name" if valid. returns 0, and sets error if the constraint is
863 * not valid. returns 1 if the constraint validated. name->type will be
864 * set to a valid type if there is constraint data in name, or unmodified
865 * if the GENERAL_NAME had a valid type but was ignored.
866 */
867int
868x509_constraints_validate(GENERAL_NAME *constraint,
869 struct x509_constraints_name *name, int *error)
870{
871 uint8_t *bytes = NULL;
872 size_t len = 0;
873 int name_type;
874
875 name_type = x509_constraints_general_to_bytes(constraint, &bytes, &len);
876 switch (name_type) {
877 case GEN_DIRNAME:
878 if (bytes == NULL || (name->der = malloc(len)) == NULL) {
879 *error = X509_V_ERR_OUT_OF_MEM;
880 return 0;
881 }
882 if (len == 0)
883 goto err; /* XXX The RFC's are delightfully vague */
884 memcpy(name->der, bytes, len);
885 name->der_len = len;
886 name->type = GEN_DIRNAME;
887 break;
888 case GEN_DNS:
889 if (!x509_constraints_valid_domain_constraint(bytes,
890 len))
891 goto err;
892 if ((name->name = strdup(bytes)) == NULL) {
893 *error = X509_V_ERR_OUT_OF_MEM;
894 return 0;
895 }
896 name->type = GEN_DNS;
897 break;
898 case GEN_EMAIL:
899 if (memchr(bytes, '@', len) != NULL) {
900 if (!x509_constraints_parse_mailbox(bytes, len,
901 name))
902 goto err;
903 } else {
904 if (!x509_constraints_valid_domain_constraint(
905 bytes, len))
906 goto err;
907 if ((name->name = strdup(bytes)) == NULL) {
908 *error = X509_V_ERR_OUT_OF_MEM;
909 return 0;
910 }
911 }
912 name->type = GEN_EMAIL;
913 break;
914 case GEN_IPADD:
915 /* Constraints are ip then mask */
916 if (len == 8)
917 name->af = AF_INET;
918 else if (len == 32)
919 name->af = AF_INET6;
920 else
921 goto err;
922 memcpy(&name->address[0], bytes, len);
923 name->type = GEN_IPADD;
924 break;
925 case GEN_URI:
926 if (!x509_constraints_valid_domain_constraint(bytes,
927 len))
928 goto err;
929 name->name = strdup(bytes);
930 name->type = GEN_URI;
931 break;
932 default:
933 break;
934 }
935 return 1;
936 err:
937 *error = X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX;
938 return 0;
939}
940
941int
942x509_constraints_extract_constraints(X509 *cert,
943 struct x509_constraints_names *permitted,
944 struct x509_constraints_names *excluded,
945 int *error)
946{
947 struct x509_constraints_name *vname;
948 NAME_CONSTRAINTS *nc = cert->nc;
949 GENERAL_SUBTREE *subtree;
950 int type;
951 int i;
952
953 if (nc == NULL)
954 return 1;
955
956 for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); i++) {
957
958 subtree = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i);
959 if (subtree->minimum || subtree->maximum) {
960 *error = X509_V_ERR_SUBTREE_MINMAX;
961 return 0;
962 }
963 if ((vname = x509_constraints_name_new()) == NULL) {
964 *error = X509_V_ERR_OUT_OF_MEM;
965 return 0;
966 }
967 if ((type = x509_constraints_validate(subtree->base,
968 vname, error)) == 0) {
969 x509_constraints_name_free(vname);
970 return 0;
971 }
972 if (vname->type == 0) {
973 x509_constraints_name_free(vname);
974 continue;
975 }
976 if (!x509_constraints_names_add(permitted, vname)) {
977 x509_constraints_name_free(vname);
978 *error = X509_V_ERR_OUT_OF_MEM;
979 return 0;
980 }
981 }
982
983 for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->excludedSubtrees); i++) {
984 subtree = sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, i);
985 if (subtree->minimum || subtree->maximum) {
986 *error = X509_V_ERR_SUBTREE_MINMAX;
987 return 0;
988 }
989 if ((vname = x509_constraints_name_new()) == NULL) {
990 *error = X509_V_ERR_OUT_OF_MEM;
991 return 0;
992 }
993 if ((type = x509_constraints_validate(subtree->base,
994 vname, error)) == 0) {
995 x509_constraints_name_free(vname);
996 return 0;
997 }
998 if (vname->type == 0) {
999 x509_constraints_name_free(vname);
1000 continue;
1001 }
1002 if (!x509_constraints_names_add(excluded, vname)) {
1003 x509_constraints_name_free(vname);
1004 *error = X509_V_ERR_OUT_OF_MEM;
1005 return 0;
1006 }
1007 }
1008
1009 return 1;
1010}
1011
1012/*
1013 * Match a validated name in "name" against a validated constraint in
1014 * "constraint" return 1 if then name matches, 0 otherwise.
1015 */
1016int
1017x509_constraints_match(struct x509_constraints_name *name,
1018 struct x509_constraints_name *constraint)
1019{
1020 if (name->type != constraint->type)
1021 return 0;
1022 if (name->type == GEN_DNS)
1023 return x509_constraints_sandns(name->name,
1024 strlen(name->name), constraint->name,
1025 strlen(constraint->name));
1026 if (name->type == GEN_URI)
1027 return x509_constraints_domain(name->name,
1028 strlen(name->name), constraint->name,
1029 strlen(constraint->name));
1030 if (name->type == GEN_IPADD) {
1031 size_t nlen = name->af == AF_INET ? 4 : 16;
1032 size_t clen = name->af == AF_INET ? 8 : 32;
1033 if (name->af != AF_INET && name->af != AF_INET6)
1034 return 0;
1035 if (constraint->af != AF_INET && constraint->af != AF_INET6)
1036 return 0;
1037 if (name->af != constraint->af)
1038 return 0;
1039 return x509_constraints_ipaddr(name->address,
1040 nlen, constraint->address, clen);
1041 }
1042 if (name->type == GEN_EMAIL) {
1043 if (constraint->local) {
1044 /* mailbox local and domain parts must exactly match */
1045 return (strcmp(name->local, constraint->local) == 0 &&
1046 strcmp(name->name, constraint->name) == 0);
1047 }
1048 /* otherwise match the constraint to the domain part */
1049 return x509_constraints_domain(name->name,
1050 strlen(name->name), constraint->name,
1051 strlen(constraint->name));
1052 }
1053 if (name->type == GEN_DIRNAME)
1054 return x509_constraints_dirname(name->der, name->der_len,
1055 constraint->der, constraint->der_len);
1056 return 0;
1057}
1058
1059/*
1060 * Make sure every name in names does not match any excluded
1061 * constraints, and does match at least one permitted constraint if
1062 * any are present. Returns 1 if ok, 0, and sets error if not.
1063 */
1064int
1065x509_constraints_check(struct x509_constraints_names *names,
1066 struct x509_constraints_names *permitted,
1067 struct x509_constraints_names *excluded, int *error)
1068{
1069 size_t i, j;
1070
1071 for (i = 0; i < names->names_count; i++) {
1072 int permitted_seen = 0;
1073 int permitted_matched = 0;
1074
1075 for (j = 0; j < excluded->names_count; j++) {
1076 if (x509_constraints_match(names->names[i],
1077 excluded->names[j])) {
1078 *error = X509_V_ERR_EXCLUDED_VIOLATION;
1079 return 0;
1080 }
1081 }
1082 for (j = 0; j < permitted->names_count; j++) {
1083 if (permitted->names[j]->type == names->names[i]->type)
1084 permitted_seen++;
1085 if (x509_constraints_match(names->names[i],
1086 permitted->names[j])) {
1087 permitted_matched++;
1088 break;
1089 }
1090 }
1091 if (permitted_seen && !permitted_matched) {
1092 *error = X509_V_ERR_PERMITTED_VIOLATION;
1093 return 0;
1094 }
1095 }
1096 return 1;
1097}
1098
1099/*
1100 * Walk a validated chain of X509 certs, starting at the leaf, and
1101 * validate the name constraints in the chain. Intended for use with
1102 * the legacy X509 validtion code in x509_vfy.c
1103 *
1104 * returns 1 if the constraints are ok, 0 otherwise, setting error and
1105 * depth
1106 */
1107int
1108x509_constraints_chain(STACK_OF(X509) *chain, int *error, int *depth)
1109{
1110 int chain_length, verify_err = X509_V_ERR_UNSPECIFIED, i = 0;
1111 struct x509_constraints_names *names = NULL;
1112 struct x509_constraints_names *excluded = NULL;
1113 struct x509_constraints_names *permitted = NULL;
1114 size_t constraints_count = 0;
1115 X509 *cert;
1116
1117 if (chain == NULL || (chain_length = sk_X509_num(chain)) == 0)
1118 goto err;
1119 if (chain_length == 1)
1120 return 1;
1121 if ((names = x509_constraints_names_new()) == NULL) {
1122 verify_err = X509_V_ERR_OUT_OF_MEM;
1123 goto err;
1124 }
1125
1126 if ((cert = sk_X509_value(chain, 0)) == NULL)
1127 goto err;
1128 if (!x509_constraints_extract_names(names, cert, 1, &verify_err))
1129 goto err;
1130 for (i = 1; i < chain_length; i++) {
1131 if ((cert = sk_X509_value(chain, i)) == NULL)
1132 goto err;
1133 if (cert->nc != NULL) {
1134 if ((permitted =
1135 x509_constraints_names_new()) == NULL) {
1136 verify_err = X509_V_ERR_OUT_OF_MEM;
1137 goto err;
1138 }
1139 if ((excluded =
1140 x509_constraints_names_new()) == NULL) {
1141 verify_err = X509_V_ERR_OUT_OF_MEM;
1142 goto err;
1143 }
1144 if (!x509_constraints_extract_constraints(cert,
1145 permitted, excluded, &verify_err))
1146 goto err;
1147 constraints_count += permitted->names_count;
1148 constraints_count += excluded->names_count;
1149 if (constraints_count >
1150 X509_VERIFY_MAX_CHAIN_CONSTRAINTS) {
1151 verify_err = X509_V_ERR_OUT_OF_MEM;
1152 goto err;
1153 }
1154 if (!x509_constraints_check(names, permitted,
1155 excluded, &verify_err))
1156 goto err;
1157 x509_constraints_names_free(excluded);
1158 excluded = NULL;
1159 x509_constraints_names_free(permitted);
1160 permitted = NULL;
1161 }
1162 if (!x509_constraints_extract_names(names, cert, 0,
1163 &verify_err))
1164 goto err;
1165 if (names->names_count > X509_VERIFY_MAX_CHAIN_NAMES) {
1166 verify_err = X509_V_ERR_OUT_OF_MEM;
1167 goto err;
1168 }
1169 }
1170
1171 return 1;
1172
1173 err:
1174 *error = verify_err;
1175 *depth = i;
1176 x509_constraints_names_free(excluded);
1177 x509_constraints_names_free(permitted);
1178 x509_constraints_names_free(names);
1179 return 0;
1180}
diff --git a/src/lib/libcrypto/x509/x509_internal.h b/src/lib/libcrypto/x509/x509_internal.h
new file mode 100644
index 0000000000..fad6c93231
--- /dev/null
+++ b/src/lib/libcrypto/x509/x509_internal.h
@@ -0,0 +1,90 @@
1/* $OpenBSD: x509_internal.h,v 1.1 2020/09/11 18:34:29 beck 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#ifndef HEADER_X509_INTERNAL_H
18#define HEADER_X509_INTERNAL_H
19
20/* Internal use only, not public API */
21#include <netinet/in.h>
22
23/*
24 * Limit the number of names and constraints we will check in a chain
25 * to avoid a hostile input DOS
26 */
27#define X509_VERIFY_MAX_CHAIN_NAMES 512
28#define X509_VERIFY_MAX_CHAIN_CONSTRAINTS 512
29
30/*
31 * Hold the parsed and validated result of names from a certificate.
32 * these typically come from a GENERALNAME, but we store the parsed
33 * and validated results, not the ASN1 bytes.
34 */
35struct x509_constraints_name {
36 int type; /* GEN_* types from GENERAL_NAME */
37 char *name; /* Name to check */
38 char *local; /* holds the local part of GEN_EMAIL */
39 uint8_t *der; /* DER encoded value or NULL*/
40 size_t der_len;
41 int af; /* INET and INET6 are supported */
42 uint8_t address[32]; /* Must hold ipv6 + mask */
43};
44
45struct x509_constraints_names {
46 struct x509_constraints_name **names;
47 size_t names_len;
48 size_t names_count;
49};
50
51struct x509_verify_chain {
52 STACK_OF(X509) *certs; /* Kept in chain order, includes leaf */
53 struct x509_constraints_names *names; /* All names from all certs */
54};
55
56__BEGIN_HIDDEN_DECLS
57
58void x509_constraints_name_clear(struct x509_constraints_name *name);
59int x509_constraints_names_add(struct x509_constraints_names *names,
60 struct x509_constraints_name *name);
61struct x509_constraints_names *x509_constraints_names_dup(
62 struct x509_constraints_names *names);
63void x509_constraints_names_clear(struct x509_constraints_names *names);
64struct x509_constraints_names *x509_constraints_names_new(void);
65void x509_constraints_names_free(struct x509_constraints_names *names);
66int x509_constraints_valid_host(uint8_t *name, size_t len);
67int x509_constraints_valid_sandns(uint8_t *name, size_t len);
68int x509_constraints_domain(char *domain, size_t dlen, char *constraint,
69 size_t len);
70int x509_constraints_parse_mailbox(uint8_t *candidate, size_t len,
71 struct x509_constraints_name *name);
72int x509_constraints_valid_domain_constraint(uint8_t *constraint,
73 size_t len);
74int x509_constraints_uri_host(uint8_t *uri, size_t len, char **hostp);
75int x509_constraints_uri(uint8_t *uri, size_t ulen, uint8_t *constraint,
76 size_t len, int *error);
77int x509_constraints_extract_names(struct x509_constraints_names *names,
78 X509 *cert, int include_cn, int *error);
79int x509_constraints_extract_constraints(X509 *cert,
80 struct x509_constraints_names *permitted,
81 struct x509_constraints_names *excluded, int *error);
82int x509_constraints_check(struct x509_constraints_names *names,
83 struct x509_constraints_names *permitted,
84 struct x509_constraints_names *excluded, int *error);
85int x509_constraints_chain(STACK_OF(X509) *chain, int *error,
86 int *depth);
87
88__END_HIDDEN_DECLS
89
90#endif