Juan Lang : crypt32: Implement matching a certificate with a wildcard in its name.

Alexandre Julliard julliard at winehq.org
Fri Nov 13 09:37:04 CST 2009


Module: wine
Branch: master
Commit: b91d0c8bde9e27ea9c647c6ba9913487d60b3d2e
URL:    http://source.winehq.org/git/wine.git/?a=commit;h=b91d0c8bde9e27ea9c647c6ba9913487d60b3d2e

Author: Juan Lang <juan.lang at gmail.com>
Date:   Wed Nov 11 17:45:49 2009 -0800

crypt32: Implement matching a certificate with a wildcard in its name.

---

 dlls/crypt32/chain.c       |  138 +++++++++++++++++++++++++++++++++++++++++---
 dlls/crypt32/tests/chain.c |    2 +-
 2 files changed, 131 insertions(+), 9 deletions(-)

diff --git a/dlls/crypt32/chain.c b/dlls/crypt32/chain.c
index 54bff75..805447d 100644
--- a/dlls/crypt32/chain.c
+++ b/dlls/crypt32/chain.c
@@ -2418,6 +2418,134 @@ static BOOL find_matching_domain_component(CERT_NAME_INFO *name,
     return matches;
 }
 
+static BOOL match_domain_component(LPCWSTR allowed_component, DWORD allowed_len,
+ LPCWSTR server_component, DWORD server_len, BOOL allow_wildcards,
+ BOOL *see_wildcard)
+{
+    LPCWSTR allowed_ptr, server_ptr;
+    BOOL matches = TRUE;
+
+    *see_wildcard = FALSE;
+    if (server_len < allowed_len)
+    {
+        WARN_(chain)("domain component %s too short for %s\n",
+         debugstr_wn(server_component, server_len),
+         debugstr_wn(allowed_component, allowed_len));
+        /* A domain component can't contain a wildcard character, so a domain
+         * component shorter than the allowed string can't produce a match.
+         */
+        return FALSE;
+    }
+    for (allowed_ptr = allowed_component, server_ptr = server_component;
+         matches && allowed_ptr - allowed_component < allowed_len;
+         allowed_ptr++, server_ptr++)
+    {
+        if (*allowed_ptr == '*')
+        {
+            if (allowed_ptr - allowed_component < allowed_len - 1)
+            {
+                WARN_(chain)("non-wildcard characters after wildcard not supported\n");
+                matches = FALSE;
+            }
+            else if (!allow_wildcards)
+            {
+                WARN_(chain)("wildcard after non-wildcard component\n");
+                matches = FALSE;
+            }
+            else
+            {
+                /* the preceding characters must have matched, so the rest of
+                 * the component also matches.
+                 */
+                *see_wildcard = TRUE;
+                break;
+            }
+        }
+        matches = tolowerW(*allowed_ptr) == tolowerW(*server_ptr);
+    }
+    if (matches && server_ptr - server_component < server_len)
+    {
+        /* If there are unmatched characters in the server domain component,
+         * the server domain only matches if the allowed string ended in a '*'.
+         */
+        matches = *allowed_ptr == '*';
+    }
+    return matches;
+}
+
+static BOOL match_common_name(LPCWSTR server_name, PCERT_RDN_ATTR nameAttr)
+{
+    LPCWSTR allowed = (LPCWSTR)nameAttr->Value.pbData;
+    LPCWSTR allowed_component = allowed;
+    DWORD allowed_len = nameAttr->Value.cbData / sizeof(WCHAR);
+    LPCWSTR server_component = server_name;
+    DWORD server_len = strlenW(server_name);
+    BOOL matches = TRUE, allow_wildcards = TRUE;
+
+    TRACE_(chain)("CN = %s\n", debugstr_wn(allowed_component, allowed_len));
+
+    /* From RFC 2818 (HTTP over TLS), section 3.1:
+     * "Names may contain the wildcard character * which is considered to match
+     *  any single domain name component or component fragment. E.g.,
+     *  *.a.com matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com
+     *  but not bar.com."
+     *
+     * And from RFC 2595 (Using TLS with IMAP, POP3 and ACAP), section 2.4:
+     * "A "*" wildcard character MAY be used as the left-most name component in
+     *  the certificate.  For example, *.example.com would match a.example.com,
+     *  foo.example.com, etc. but would not match example.com."
+     *
+     * There are other protocols which use TLS, and none of them is
+     * authoritative.  This accepts certificates in common usage, e.g.
+     * *.domain.com matches www.domain.com but not domain.com, and
+     * www*.domain.com matches www1.domain.com but not mail.domain.com.
+     */
+    do {
+        LPCWSTR allowed_dot, server_dot;
+
+        allowed_dot = memchrW(allowed_component, '.',
+         allowed_len - (allowed_component - allowed));
+        server_dot = memchrW(server_component, '.',
+         server_len - (server_component - server_name));
+        /* The number of components must match */
+        if ((!allowed_dot && server_dot) || (allowed_dot && !server_dot))
+        {
+            if (!allowed_dot)
+                WARN_(chain)("%s: too many components for CN=%s\n",
+                 debugstr_w(server_name), debugstr_wn(allowed, allowed_len));
+            else
+                WARN_(chain)("%s: not enough components for CN=%s\n",
+                 debugstr_w(server_name), debugstr_wn(allowed, allowed_len));
+            matches = FALSE;
+        }
+        else
+        {
+            LPCWSTR allowed_end, server_end;
+            BOOL has_wildcard;
+
+            allowed_end = allowed_dot ? allowed_dot : allowed + allowed_len;
+            server_end = server_dot ? server_dot : server_name + server_len;
+            matches = match_domain_component(allowed_component,
+             allowed_end - allowed_component, server_component,
+             server_end - server_component, allow_wildcards, &has_wildcard);
+            /* Once a non-wildcard component is seen, no wildcard components
+             * may follow
+             */
+            if (!has_wildcard)
+                allow_wildcards = FALSE;
+            if (matches)
+            {
+                allowed_component = allowed_dot ? allowed_dot + 1 : allowed_end;
+                server_component = server_dot ? server_dot + 1 : server_end;
+            }
+        }
+    } while (matches && allowed_component &&
+     allowed_component - allowed < allowed_len &&
+     server_component && server_component - server_name < server_len);
+    TRACE_(chain)("returning %d\n", matches);
+    return matches;
+}
+
 static BOOL match_dns_to_subject_dn(PCCERT_CONTEXT cert, LPCWSTR server_name)
 {
     BOOL matches = FALSE;
@@ -2466,16 +2594,10 @@ static BOOL match_dns_to_subject_dn(PCCERT_CONTEXT cert, LPCWSTR server_name)
             PCERT_RDN_ATTR attr;
 
             /* If the certificate isn't using a DN attribute in the name, make
-             * make sure the common name matches.  Again, use memicmpW rather
-             * than strcmpiW in order to avoid being fooled by an embedded NULL.
+             * make sure the common name matches.
              */
             if ((attr = CertFindRDNAttr(szOID_COMMON_NAME, name)))
-            {
-                TRACE_(chain)("CN = %s\n", debugstr_w(
-                 (LPWSTR)attr->Value.pbData));
-                matches = !memicmpW(server_name, (LPWSTR)attr->Value.pbData,
-                 attr->Value.cbData / sizeof(WCHAR));
-            }
+                matches = match_common_name(server_name, attr);
         }
         LocalFree(name);
     }
diff --git a/dlls/crypt32/tests/chain.c b/dlls/crypt32/tests/chain.c
index 8092c8c..57fe2b0 100644
--- a/dlls/crypt32/tests/chain.c
+++ b/dlls/crypt32/tests/chain.c
@@ -3392,7 +3392,7 @@ static const ChainPolicyCheck iTunesPolicyCheckWithoutMatchingName = {
 
 static const ChainPolicyCheck opensslPolicyCheckWithMatchingName = {
  { sizeof(opensslChain) / sizeof(opensslChain[0]), opensslChain },
- { 0, 0, -1, -1, NULL}, NULL, TODO_ERROR
+ { 0, 0, -1, -1, NULL}, NULL, 0
 };
 
 static const ChainPolicyCheck opensslPolicyCheckWithoutMatchingName = {




More information about the wine-cvs mailing list