Client-side namecheck wildcards.
authorViktor Dukhovni <openssl-users@dukhovni.org>
Thu, 12 Jun 2014 05:56:31 +0000 (01:56 -0400)
committerDr. Stephen Henson <steve@openssl.org>
Thu, 12 Jun 2014 22:19:25 +0000 (23:19 +0100)
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
crypto/x509v3/v3nametest.c
crypto/x509v3/x509v3.h
doc/crypto/X509_check_host.pod

index 7a4bd459604cbdcfa688da4f45a79439bb3680b8..004a1339ea5d392507bf7a0e4674f8459dbf3af8 100644 (file)
@@ -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);
 
                        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,
 /* 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)
        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,
 /* 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);
        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 *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);
        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;
        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;
                alt_type = V_ASN1_IA5STRING;
                if (flags & X509_CHECK_FLAG_NO_WILDCARDS)
                        equal = equal_nocase;
index 4cd6f3688809997b0fd4d33dc8ecc27f5b415196..ad820fdfd9b379bf0f132bcec410e9f863ed92d5 100644 (file)
@@ -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",
        "*@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",
        "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: [*.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]",
        "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: [*.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]",
        "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]",
index 406300f29656c7322b5381442700b3ad29d39c31..f22c0ef5f9a4f860a64910038b0f5f349cd150e2 100644 (file)
@@ -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
 #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);
 
 int X509_check_host(X509 *x, const unsigned char *chk, size_t chklen,
                                        unsigned int flags);
index 64a84d2ab5e490bbe1a402b2460d09349c1fe335..7f6adf642429d44b66fcf0820dd1c25dda2dcccf 100644 (file)
@@ -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<namelen> argument must be
 the number of characters in the name string or zero in which case the
 host name, which must be encoded in the preferred name syntax
 described in section 3.5 of RFC 1034. The B<namelen> 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<name> starts with
+a dot (e.g ".example.com"), it will be matched by a certificate
+valid for any sub-domain of B<name>, (see also
+B<X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS> below).
 
 X509_check_email() checks if the certificate matches the specified
 email address.  Only the mailbox syntax of RFC 822 is supported,
 
 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<X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS>.
 
 
 =item B<X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS>.
 
+=item B<X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS>.
+
 =back
 
 The B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> flag causes the function
 =back
 
 The B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> flag causes the function
@@ -74,10 +79,18 @@ If set, B<X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS> suppresses support
 for "*" as wildcard pattern in labels that have a prefix or suffix,
 such as: "www*" or "*www"; this only aplies to B<X509_check_host>.
 
 for "*" as wildcard pattern in labels that have a prefix or suffix,
 such as: "www*" or "*www"; this only aplies to B<X509_check_host>.
 
-If set, B<X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS>, allows a "*"
-that constitutes the complete label of a DNS name (e.g.
-"*.example.com") to match more than one label in B<name>;
-this only applies to B<X509_check_host>.
+If set, B<X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS> allows a "*" that
+constitutes the complete label of a DNS name (e.g. "*.example.com")
+to match more than one label in B<name>; this flag only applies
+to B<X509_check_host>.
+
+If set, B<X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS> restricts B<name>
+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<name> 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<X509_check_host>.
 
 =head1 RETURN VALUES
 
 
 =head1 RETURN VALUES