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