summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjsing <>2026-07-14 15:52:52 +0000
committerjsing <>2026-07-14 15:52:52 +0000
commitd9a371b178de7043470c89bef81aa96da20dc855 (patch)
tree0c1869d6a4831f89be1d5d75189412787ded0f50 /src
parent830c372f670c7dc9435cd7c2dd13364913f22b52 (diff)
downloadopenbsd-d9a371b178de7043470c89bef81aa96da20dc855.tar.gz
openbsd-d9a371b178de7043470c89bef81aa96da20dc855.tar.bz2
openbsd-d9a371b178de7043470c89bef81aa96da20dc855.zip
Fix X.509 constraints URI host parsing.
An authority in a URI is only terminated by a slash, question mark or hash, however the current code also included colons. This allows a specically crafted userinfo to bypass name constraints host checks. Additionally, IPv6 literals may only be specified when enclosed with square brackets, which is not enforced. Rewrite parts of the host and IP parsing code to be more strict, fixing both of these issues in the process. Thanks to Jack Lloyd for reporting the userinfo bypass. ok tb@
Diffstat (limited to 'src')
-rw-r--r--src/lib/libcrypto/x509/x509_constraints.c101
1 files changed, 72 insertions, 29 deletions
diff --git a/src/lib/libcrypto/x509/x509_constraints.c b/src/lib/libcrypto/x509/x509_constraints.c
index c4f32c9cfc..61a08cacef 100644
--- a/src/lib/libcrypto/x509/x509_constraints.c
+++ b/src/lib/libcrypto/x509/x509_constraints.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: x509_constraints.c,v 1.33 2026/04/13 17:04:23 beck Exp $ */ 1/* $OpenBSD: x509_constraints.c,v 1.34 2026/07/14 15:52:52 jsing Exp $ */
2/* 2/*
3 * Copyright (c) 2020 Bob Beck <beck@openbsd.org> 3 * Copyright (c) 2020 Bob Beck <beck@openbsd.org>
4 * 4 *
@@ -38,21 +38,40 @@
38#define MAX_IP_ADDRESS_LENGTH (size_t)46 38#define MAX_IP_ADDRESS_LENGTH (size_t)46
39 39
40static int 40static int
41cbs_is_ip_address(CBS *cbs, int *is_ip) 41host_is_ip_address(CBS *cbs, int *is_ip)
42{ 42{
43 struct sockaddr_in6 sin6; 43 struct sockaddr_in6 sin6;
44 struct sockaddr_in sin4; 44 struct sockaddr_in sin4;
45 uint8_t first, last;
45 char *name = NULL; 46 char *name = NULL;
47 CBS ipv6_cbs;
46 48
47 *is_ip = 0; 49 *is_ip = 0;
50
48 if (CBS_len(cbs) > MAX_IP_ADDRESS_LENGTH) 51 if (CBS_len(cbs) > MAX_IP_ADDRESS_LENGTH)
49 return 1; 52 return 1;
53
54 /* Must be an IPv6 literal. */
55 CBS_dup(cbs, &ipv6_cbs);
56 if (!CBS_get_u8(&ipv6_cbs, &first))
57 return 1;
58 if (!CBS_get_last_u8(&ipv6_cbs, &last))
59 return 1;
60 if (first == '[' && last == ']') {
61 if (!CBS_strdup(&ipv6_cbs, &name))
62 return 0;
63 if (inet_pton(AF_INET6, name, &sin6) == 1)
64 *is_ip = 1;
65 goto done;
66 }
67
68 /* Or an IPv4 address. */
50 if (!CBS_strdup(cbs, &name)) 69 if (!CBS_strdup(cbs, &name))
51 return 0; 70 return 0;
52 if (inet_pton(AF_INET, name, &sin4) == 1 || 71 if (inet_pton(AF_INET, name, &sin4) == 1)
53 inet_pton(AF_INET6, name, &sin6) == 1)
54 *is_ip = 1; 72 *is_ip = 1;
55 73
74 done:
56 free(name); 75 free(name);
57 return 1; 76 return 1;
58} 77}
@@ -273,13 +292,13 @@ x509_constraints_valid_host(CBS *cbs, int permit_ip)
273 return 0; 292 return 0;
274 if (first == '.') 293 if (first == '.')
275 return 0; /* leading . not allowed in a host name or IP */ 294 return 0; /* leading . not allowed in a host name or IP */
276 if (!permit_ip) { 295 if (!host_is_ip_address(cbs, &is_ip))
277 if (!cbs_is_ip_address(cbs, &is_ip)) 296 return 0;
278 return 0; 297 if (is_ip) {
279 if (is_ip) 298 if (permit_ip)
280 return 0; 299 return 1;
300 return 0;
281 } 301 }
282
283 return x509_constraints_valid_domain_internal(cbs, 0); 302 return x509_constraints_valid_domain_internal(cbs, 0);
284} 303}
285 304
@@ -508,10 +527,11 @@ x509_constraints_valid_domain_constraint(CBS *cbs)
508int 527int
509x509_constraints_uri_host(uint8_t *uri, size_t len, char **hostpart) 528x509_constraints_uri_host(uint8_t *uri, size_t len, char **hostpart)
510{ 529{
511 size_t i, hostlen = 0;
512 uint8_t *authority = NULL; 530 uint8_t *authority = NULL;
513 char *host = NULL; 531 uint8_t *host = NULL;
532 size_t hostlen = 0;
514 CBS host_cbs; 533 CBS host_cbs;
534 size_t i;
515 535
516 /* 536 /*
517 * Find first '//'. there must be at least a '//' and 537 * Find first '//'. there must be at least a '//' and
@@ -542,31 +562,55 @@ x509_constraints_uri_host(uint8_t *uri, size_t len, char **hostpart)
542 for (i = authority - uri; i < len; i++) { 562 for (i = authority - uri; i < len; i++) {
543 if (!isascii(uri[i])) 563 if (!isascii(uri[i]))
544 return 0; 564 return 0;
545 /* it has a userinfo part */ 565 /*
546 if (uri[i] == '@') { 566 * Per RFC 3986 section 3.2, authority is terminated
547 hostlen = 0; 567 * by a slash (/), question mark (?), hash (#) or by
548 /* it can only have one */ 568 * the end of the URI.
549 if (host != NULL) 569 */
550 break; 570 if (uri[i] == '/' || uri[i] == '?' || uri[i] == '#')
551 /* start after the userinfo part */
552 host = uri + i + 1;
553 continue;
554 }
555 /* did we find the end? */
556 if (uri[i] == ':' || uri[i] == '/' || uri[i] == '?' ||
557 uri[i] == '#')
558 break; 571 break;
572
559 hostlen++; 573 hostlen++;
560 } 574 }
575
576 host = authority;
577
578 /* Remove any leading userinfo part. */
579 for (i = 0; i < hostlen; i++) {
580 if (authority[i] != '@')
581 continue;
582
583 /* Only one at-sign (@) is permitted. */
584 if (host != authority)
585 return 0;
586
587 /* Start after the userinfo part. */
588 host = authority + i + 1;
589 }
590 hostlen = hostlen - (host - authority);
591
561 if (hostlen == 0) 592 if (hostlen == 0)
562 return 0; 593 return 0;
563 if (host == NULL) 594
564 host = authority; 595 /* Remove any port part, respecting IPv6 literals. */
596 for (i = hostlen - 1; i > 0; i--) {
597 if (host[i] == ']')
598 break;
599 if (host[i] == ':') {
600 hostlen = i;
601 break;
602 }
603 }
604
605 if (hostlen == 0)
606 return 0;
607
565 CBS_init(&host_cbs, host, hostlen); 608 CBS_init(&host_cbs, host, hostlen);
566 if (!x509_constraints_valid_host(&host_cbs, 1)) 609 if (!x509_constraints_valid_host(&host_cbs, 1))
567 return 0; 610 return 0;
568 if (hostpart != NULL && !CBS_strdup(&host_cbs, hostpart)) 611 if (hostpart != NULL && !CBS_strdup(&host_cbs, hostpart))
569 return 0; 612 return 0;
613
570 return 1; 614 return 1;
571} 615}
572 616
@@ -647,8 +691,7 @@ x509_constraints_domain(char *domain, size_t dlen, char *constraint, size_t len)
647 691
648int 692int
649x509_constraints_uri(uint8_t *uri, size_t ulen, uint8_t *constraint, 693x509_constraints_uri(uint8_t *uri, size_t ulen, uint8_t *constraint,
650 size_t len, 694 size_t len, int *error)
651 int *error)
652{ 695{
653 int ret = 0; 696 int ret = 0;
654 char *hostpart = NULL; 697 char *hostpart = NULL;