From d9a371b178de7043470c89bef81aa96da20dc855 Mon Sep 17 00:00:00 2001 From: jsing <> Date: Tue, 14 Jul 2026 15:52:52 +0000 Subject: 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@ --- src/lib/libcrypto/x509/x509_constraints.c | 101 +++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 29 deletions(-) (limited to 'src') 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 @@ -/* $OpenBSD: x509_constraints.c,v 1.33 2026/04/13 17:04:23 beck Exp $ */ +/* $OpenBSD: x509_constraints.c,v 1.34 2026/07/14 15:52:52 jsing Exp $ */ /* * Copyright (c) 2020 Bob Beck * @@ -38,21 +38,40 @@ #define MAX_IP_ADDRESS_LENGTH (size_t)46 static int -cbs_is_ip_address(CBS *cbs, int *is_ip) +host_is_ip_address(CBS *cbs, int *is_ip) { struct sockaddr_in6 sin6; struct sockaddr_in sin4; + uint8_t first, last; char *name = NULL; + CBS ipv6_cbs; *is_ip = 0; + if (CBS_len(cbs) > MAX_IP_ADDRESS_LENGTH) return 1; + + /* Must be an IPv6 literal. */ + CBS_dup(cbs, &ipv6_cbs); + if (!CBS_get_u8(&ipv6_cbs, &first)) + return 1; + if (!CBS_get_last_u8(&ipv6_cbs, &last)) + return 1; + if (first == '[' && last == ']') { + if (!CBS_strdup(&ipv6_cbs, &name)) + return 0; + if (inet_pton(AF_INET6, name, &sin6) == 1) + *is_ip = 1; + goto done; + } + + /* Or an IPv4 address. */ if (!CBS_strdup(cbs, &name)) return 0; - if (inet_pton(AF_INET, name, &sin4) == 1 || - inet_pton(AF_INET6, name, &sin6) == 1) + if (inet_pton(AF_INET, name, &sin4) == 1) *is_ip = 1; + done: free(name); return 1; } @@ -273,13 +292,13 @@ x509_constraints_valid_host(CBS *cbs, int permit_ip) return 0; if (first == '.') return 0; /* leading . not allowed in a host name or IP */ - if (!permit_ip) { - if (!cbs_is_ip_address(cbs, &is_ip)) - return 0; - if (is_ip) - return 0; + if (!host_is_ip_address(cbs, &is_ip)) + return 0; + if (is_ip) { + if (permit_ip) + return 1; + return 0; } - return x509_constraints_valid_domain_internal(cbs, 0); } @@ -508,10 +527,11 @@ x509_constraints_valid_domain_constraint(CBS *cbs) int x509_constraints_uri_host(uint8_t *uri, size_t len, char **hostpart) { - size_t i, hostlen = 0; uint8_t *authority = NULL; - char *host = NULL; + uint8_t *host = NULL; + size_t hostlen = 0; CBS host_cbs; + size_t i; /* * 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) for (i = authority - uri; i < len; i++) { if (!isascii(uri[i])) return 0; - /* it has a userinfo part */ - if (uri[i] == '@') { - hostlen = 0; - /* it can only have one */ - if (host != NULL) - break; - /* start after the userinfo part */ - host = uri + i + 1; - continue; - } - /* did we find the end? */ - if (uri[i] == ':' || uri[i] == '/' || uri[i] == '?' || - uri[i] == '#') + /* + * Per RFC 3986 section 3.2, authority is terminated + * by a slash (/), question mark (?), hash (#) or by + * the end of the URI. + */ + if (uri[i] == '/' || uri[i] == '?' || uri[i] == '#') break; + hostlen++; } + + host = authority; + + /* Remove any leading userinfo part. */ + for (i = 0; i < hostlen; i++) { + if (authority[i] != '@') + continue; + + /* Only one at-sign (@) is permitted. */ + if (host != authority) + return 0; + + /* Start after the userinfo part. */ + host = authority + i + 1; + } + hostlen = hostlen - (host - authority); + if (hostlen == 0) return 0; - if (host == NULL) - host = authority; + + /* Remove any port part, respecting IPv6 literals. */ + for (i = hostlen - 1; i > 0; i--) { + if (host[i] == ']') + break; + if (host[i] == ':') { + hostlen = i; + break; + } + } + + if (hostlen == 0) + return 0; + CBS_init(&host_cbs, host, hostlen); if (!x509_constraints_valid_host(&host_cbs, 1)) return 0; if (hostpart != NULL && !CBS_strdup(&host_cbs, hostpart)) return 0; + return 1; } @@ -647,8 +691,7 @@ x509_constraints_domain(char *domain, size_t dlen, char *constraint, size_t len) int x509_constraints_uri(uint8_t *uri, size_t ulen, uint8_t *constraint, - size_t len, - int *error) + size_t len, int *error) { int ret = 0; char *hostpart = NULL; -- cgit v1.2.3-55-g6feb