From a09e4d24ada871ed0e6f5e37fadd52a76b29542a Mon Sep 17 00:00:00 2001 From: Viktor Dukhovni Date: Thu, 12 Jun 2014 01:56:31 -0400 Subject: [PATCH] Client-side namecheck wildcards. A client reference identity of ".example.com" matches a server certificate presented identity that is any sub-domain of "example.com" (e.g. "www.sub.example.com). With the X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS flag, it matches only direct child sub-domains (e.g. "www.sub.example.com"). --- crypto/x509v3/v3_utl.c | 59 ++++++++++++++++++++++++++++++---- crypto/x509v3/v3nametest.c | 11 +++++++ crypto/x509v3/x509v3.h | 8 +++++ doc/crypto/X509_check_host.pod | 23 ++++++++++--- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c index 7a4bd45960..004a1339ea 100644 --- a/crypto/x509v3/v3_utl.c +++ b/crypto/x509v3/v3_utl.c @@ -572,11 +572,50 @@ typedef int (*equal_fn)(const unsigned char *pattern, size_t pattern_len, const unsigned char *subject, size_t subject_len, unsigned int flags); +/* Skip pattern prefix to match "wildcard" subject */ +static void skip_prefix(const unsigned char **p, size_t *plen, + const unsigned char *subject, size_t subject_len, + unsigned int flags) + { + const unsigned char *pattern = *p; + size_t pattern_len = *plen; + + /* + * If subject starts with a leading '.' followed by more octets, and + * pattern is longer, compare just an equal-length suffix with the + * full subject (starting at the '.'), provided the prefix contains + * no NULs. (We check again that subject starts with '.' and + * contains at least one subsequent character, just in case the + * internal _X509_CHECK_FLAG_DOT_SUBDOMAINS flag was erroneously + * set by the user). + */ + if ((flags & _X509_CHECK_FLAG_DOT_SUBDOMAINS) == 0 || + subject_len <= 1 || subject[0] != '.') + return; + + while (pattern_len > subject_len && *pattern) + { + if ((flags & X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS) && + *pattern == '.') + break; + ++pattern; + --pattern_len; + } + + /* Skip if entire prefix acceptable */ + if (pattern_len == subject_len) + { + *p = pattern; + *plen = pattern_len; + } + } + /* Compare while ASCII ignoring case. */ static int equal_nocase(const unsigned char *pattern, size_t pattern_len, const unsigned char *subject, size_t subject_len, - unsigned int unused_flags) + unsigned int flags) { + skip_prefix(&pattern, &pattern_len, subject, subject_len, flags); if (pattern_len != subject_len) return 0; while (pattern_len) @@ -605,11 +644,9 @@ static int equal_nocase(const unsigned char *pattern, size_t pattern_len, /* Compare using memcmp. */ static int equal_case(const unsigned char *pattern, size_t pattern_len, const unsigned char *subject, size_t subject_len, - unsigned int unused_flags) + unsigned int flags) { - /* The pattern must not contain NUL characters. */ - if (memchr(pattern, '\0', pattern_len) != NULL) - return 0; + skip_prefix(&pattern, &pattern_len, subject, subject_len, flags); if (pattern_len != subject_len) return 0; return !memcmp(pattern, subject, pattern_len); @@ -797,7 +834,14 @@ static int equal_wildcard(const unsigned char *pattern, size_t pattern_len, const unsigned char *subject, size_t subject_len, unsigned int flags) { - const unsigned char *star = valid_star(pattern, pattern_len, flags); + const unsigned char *star = NULL; + + /* + * Subject names starting with '.' can only match a wildcard pattern + * via a subject sub-domain pattern suffix match. + */ + if (!(subject_len > 1 && subject[0] == '.')) + star = valid_star(pattern, pattern_len, flags); if (star == NULL) return equal_nocase(pattern, pattern_len, subject, subject_len, flags); @@ -860,6 +904,9 @@ static int do_x509_check(X509 *x, const unsigned char *chk, size_t chklen, else if (check_type == GEN_DNS) { cnid = NID_commonName; + /* Implicit client-side DNS sub-domain pattern */ + if (chklen > 1 && chk[0] == '.') + flags |= _X509_CHECK_FLAG_DOT_SUBDOMAINS; alt_type = V_ASN1_IA5STRING; if (flags & X509_CHECK_FLAG_NO_WILDCARDS) equal = equal_nocase; diff --git a/crypto/x509v3/v3nametest.c b/crypto/x509v3/v3nametest.c index 4cd6f36888..ad820fdfd9 100644 --- a/crypto/x509v3/v3nametest.c +++ b/crypto/x509v3/v3nametest.c @@ -11,6 +11,7 @@ static const char *const names[] = "*@example.com", "test@*.example.com", "example.com", "www.example.com", "test.www.example.com", "*.example.com", "*.www.example.com", "test.*.example.com", "www.*.com", + ".www.example.com", "*www.example.com", "example.net", "xn--rger-koa.example.com", "a.example.com", "b.example.com", "postmaster@example.com", "Postmaster@example.com", @@ -25,6 +26,11 @@ static const char *const exceptions[] = "set CN: host: [*.example.com] matches [www.example.com]", "set CN: host: [*.example.com] matches [xn--rger-koa.example.com]", "set CN: host: [*.www.example.com] matches [test.www.example.com]", + "set CN: host: [*.www.example.com] matches [.www.example.com]", + "set CN: host: [*www.example.com] matches [www.example.com]", + "set CN: host: [test.www.example.com] matches [.www.example.com]", + "set CN: host-no-wildcards: [*.www.example.com] matches [.www.example.com]", + "set CN: host-no-wildcards: [test.www.example.com] matches [.www.example.com]", "set emailAddress: email: [postmaster@example.com] does not match [Postmaster@example.com]", "set emailAddress: email: [postmaster@EXAMPLE.COM] does not match [Postmaster@example.com]", "set emailAddress: email: [Postmaster@example.com] does not match [postmaster@example.com]", @@ -34,6 +40,11 @@ static const char *const exceptions[] = "set dnsName: host: [*.example.com] matches [b.example.com]", "set dnsName: host: [*.example.com] matches [xn--rger-koa.example.com]", "set dnsName: host: [*.www.example.com] matches [test.www.example.com]", + "set dnsName: host-no-wildcards: [*.www.example.com] matches [.www.example.com]", + "set dnsName: host-no-wildcards: [test.www.example.com] matches [.www.example.com]", + "set dnsName: host: [*.www.example.com] matches [.www.example.com]", + "set dnsName: host: [*www.example.com] matches [www.example.com]", + "set dnsName: host: [test.www.example.com] matches [.www.example.com]", "set rfc822Name: email: [postmaster@example.com] does not match [Postmaster@example.com]", "set rfc822Name: email: [Postmaster@example.com] does not match [postmaster@example.com]", "set rfc822Name: email: [Postmaster@example.com] does not match [postmaster@EXAMPLE.COM]", diff --git a/crypto/x509v3/x509v3.h b/crypto/x509v3/x509v3.h index 406300f296..f22c0ef5f9 100644 --- a/crypto/x509v3/x509v3.h +++ b/crypto/x509v3/x509v3.h @@ -710,6 +710,14 @@ STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x); #define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0x4 /* Allow (non-partial) wildcards to match multiple labels. */ #define X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS 0x8 +/* Constraint verifier subdomain patterns to match a single labels. */ +#define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0x10 +/* + * Match reference identifiers starting with "." to any sub-domain. + * This is a non-public flag, turned on implicitly when the subject + * reference identity is a DNS name. + */ +#define _X509_CHECK_FLAG_DOT_SUBDOMAINS 0x8000 int X509_check_host(X509 *x, const unsigned char *chk, size_t chklen, unsigned int flags); diff --git a/doc/crypto/X509_check_host.pod b/doc/crypto/X509_check_host.pod index 64a84d2ab5..7f6adf6424 100644 --- a/doc/crypto/X509_check_host.pod +++ b/doc/crypto/X509_check_host.pod @@ -27,7 +27,10 @@ X509_check_host() checks if the certificate matches the specified host name, which must be encoded in the preferred name syntax described in section 3.5 of RFC 1034. The B argument must be the number of characters in the name string or zero in which case the -length is calculated with strlen(name). +length is calculated with strlen(name). When B starts with +a dot (e.g ".example.com"), it will be matched by a certificate +valid for any sub-domain of B, (see also +B below). X509_check_email() checks if the certificate matches the specified email address. Only the mailbox syntax of RFC 822 is supported, @@ -59,6 +62,8 @@ flags: =item B. +=item B. + =back The B flag causes the function @@ -74,10 +79,18 @@ If set, B suppresses support for "*" as wildcard pattern in labels that have a prefix or suffix, such as: "www*" or "*www"; this only aplies to B. -If set, B, allows a "*" -that constitutes the complete label of a DNS name (e.g. -"*.example.com") to match more than one label in B; -this only applies to B. +If set, B allows a "*" that +constitutes the complete label of a DNS name (e.g. "*.example.com") +to match more than one label in B; this flag only applies +to B. + +If set, B restricts B +values which start with ".", that would otherwise match any sub-domain +in the peer certificate, to only match direct child sub-domains. +Thus, for instance, with this flag set a B of ".example.com" +would match a peer certificate with a DNS name of "www.example.com", +but would not match a peer certificate with a DNS name of +"www.sub.example.com"; this flag only applies to B. =head1 RETURN VALUES -- 2.25.1