Juan Lang : crypt32: Implement CertVerifyCertificateChainPolicy for CERT_CHAIN_POLICY_SSL.

Alexandre Julliard julliard at winehq.org
Thu Oct 29 11:20:36 CDT 2009


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

Author: Juan Lang <juan.lang at gmail.com>
Date:   Wed Oct 28 09:17:30 2009 -0700

crypt32: Implement CertVerifyCertificateChainPolicy for CERT_CHAIN_POLICY_SSL.

---

 dlls/crypt32/chain.c |  231 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 231 insertions(+), 0 deletions(-)

diff --git a/dlls/crypt32/chain.c b/dlls/crypt32/chain.c
index e8f9de3..8972463 100644
--- a/dlls/crypt32/chain.c
+++ b/dlls/crypt32/chain.c
@@ -2117,6 +2117,234 @@ static BOOL WINAPI verify_basic_constraints_policy(LPCSTR szPolicyOID,
     return TRUE;
 }
 
+static inline PCERT_EXTENSION get_subject_alt_name_ext(PCCERT_CONTEXT cert)
+{
+    PCERT_EXTENSION ext;
+
+    ext = CertFindExtension(szOID_SUBJECT_ALT_NAME2,
+     cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension);
+    if (!ext)
+        ext = CertFindExtension(szOID_SUBJECT_ALT_NAME,
+         cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension);
+    return ext;
+}
+
+static BOOL match_dns_to_subject_alt_name(PCERT_EXTENSION ext,
+ LPCWSTR server_name)
+{
+    BOOL matches = FALSE;
+    CERT_ALT_NAME_INFO *subjectName;
+    DWORD size;
+
+    TRACE_(chain)("%s\n", debugstr_w(server_name));
+    /* FIXME: This can be spoofed by the embedded NULL vulnerability.  The
+     * returned CERT_ALT_NAME_INFO doesn't have a way to indicate the
+     * encoded length of a name, so a certificate issued to
+     * winehq.org\0badsite.com will get treated as having been issued to
+     * winehq.org.
+     */
+    if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_ALTERNATE_NAME,
+     ext->Value.pbData, ext->Value.cbData,
+     CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL,
+     &subjectName, &size))
+    {
+        DWORD i;
+        BOOL found = FALSE;
+
+        for (i = 0; !found && i < subjectName->cAltEntry; i++)
+        {
+            if (subjectName->rgAltEntry[i].dwAltNameChoice ==
+             CERT_ALT_NAME_DNS_NAME)
+            {
+                TRACE_(chain)("dNSName: %s\n", debugstr_w(
+                 subjectName->rgAltEntry[i].u.pwszDNSName));
+                found = TRUE;
+                if (!strcmpiW(server_name,
+                 subjectName->rgAltEntry[i].u.pwszDNSName))
+                    matches = TRUE;
+            }
+        }
+        LocalFree(subjectName);
+    }
+    return matches;
+}
+
+static BOOL find_matching_domain_component(CERT_NAME_INFO *name,
+ LPCWSTR component)
+{
+    BOOL matches = FALSE;
+    DWORD i, j;
+
+    for (i = 0; !matches && i < name->cRDN; i++)
+        for (j = 0; j < name->rgRDN[i].cRDNAttr; j++)
+            if (!strcmp(szOID_DOMAIN_COMPONENT,
+             name->rgRDN[i].rgRDNAttr[j].pszObjId))
+            {
+                PCERT_RDN_ATTR attr;
+
+                attr = &name->rgRDN[i].rgRDNAttr[j];
+                /* Compare with memicmpW rather than strcmpiW in order to avoid
+                 * a match with a string with an embedded NULL.  The component
+                 * must match one domain component attribute's entire string
+                 * value with a case-insensitive match.
+                 */
+                matches = !memicmpW(component, (LPWSTR)attr->Value.pbData,
+                 attr->Value.cbData / sizeof(WCHAR));
+            }
+    return matches;
+}
+
+static BOOL match_dns_to_subject_dn(PCCERT_CONTEXT cert, LPCWSTR server_name)
+{
+    BOOL matches = FALSE;
+    CERT_NAME_INFO *name;
+    DWORD size;
+
+    TRACE_(chain)("%s\n", debugstr_w(server_name));
+    if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_UNICODE_NAME,
+     cert->pCertInfo->Subject.pbData, cert->pCertInfo->Subject.cbData,
+     CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL,
+     &name, &size))
+    {
+        /* If the subject distinguished name contains any name components,
+         * make sure all of them are present.
+         */
+        if (CertFindRDNAttr(szOID_DOMAIN_COMPONENT, name))
+        {
+            LPCWSTR ptr = server_name;
+
+            matches = TRUE;
+            do {
+                LPCWSTR dot = strchrW(ptr, '.'), end;
+                /* 254 is the maximum DNS label length, see RFC 1035 */
+                WCHAR component[255];
+                DWORD len;
+
+                end = dot ? dot : ptr + strlenW(ptr);
+                len = end - ptr;
+                if (len >= sizeof(component) / sizeof(component[0]))
+                {
+                    WARN_(chain)("domain component %s too long\n",
+                     debugstr_wn(ptr, len));
+                    matches = FALSE;
+                }
+                else
+                {
+                    memcpy(component, ptr, len * sizeof(WCHAR));
+                    component[len] = 0;
+                    matches = find_matching_domain_component(name, component);
+                }
+                ptr = dot ? dot + 1 : end;
+            } while (matches && ptr && *ptr);
+        }
+        else
+        {
+            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.
+             */
+            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));
+            }
+        }
+        LocalFree(name);
+    }
+    return matches;
+}
+
+static BOOL WINAPI verify_ssl_policy(LPCSTR szPolicyOID,
+ PCCERT_CHAIN_CONTEXT pChainContext, PCERT_CHAIN_POLICY_PARA pPolicyPara,
+ PCERT_CHAIN_POLICY_STATUS pPolicyStatus)
+{
+    pPolicyStatus->lChainIndex = pPolicyStatus->lElementIndex = -1;
+    if (pChainContext->TrustStatus.dwErrorStatus &
+     CERT_TRUST_IS_NOT_SIGNATURE_VALID)
+    {
+        pPolicyStatus->dwError = TRUST_E_CERT_SIGNATURE;
+        find_element_with_error(pChainContext,
+         CERT_TRUST_IS_NOT_SIGNATURE_VALID, &pPolicyStatus->lChainIndex,
+         &pPolicyStatus->lElementIndex);
+    }
+    else if (pChainContext->TrustStatus.dwErrorStatus &
+     CERT_TRUST_IS_UNTRUSTED_ROOT)
+    {
+        pPolicyStatus->dwError = CERT_E_UNTRUSTEDROOT;
+        find_element_with_error(pChainContext,
+         CERT_TRUST_IS_UNTRUSTED_ROOT, &pPolicyStatus->lChainIndex,
+         &pPolicyStatus->lElementIndex);
+    }
+    else if (pChainContext->TrustStatus.dwErrorStatus & CERT_TRUST_IS_CYCLIC)
+    {
+        pPolicyStatus->dwError = CERT_E_UNTRUSTEDROOT;
+        find_element_with_error(pChainContext,
+         CERT_TRUST_IS_CYCLIC, &pPolicyStatus->lChainIndex,
+         &pPolicyStatus->lElementIndex);
+        /* For a cyclic chain, which element is a cycle isn't meaningful */
+        pPolicyStatus->lElementIndex = -1;
+    }
+    else if (pChainContext->TrustStatus.dwErrorStatus &
+     CERT_TRUST_IS_NOT_TIME_VALID)
+    {
+        pPolicyStatus->dwError = CERT_E_EXPIRED;
+        find_element_with_error(pChainContext,
+         CERT_TRUST_IS_NOT_TIME_VALID, &pPolicyStatus->lChainIndex,
+         &pPolicyStatus->lElementIndex);
+    }
+    else
+        pPolicyStatus->dwError = NO_ERROR;
+    /* We only need bother checking whether the name in the end certificate
+     * matches if the chain is otherwise okay.
+     */
+    if (!pPolicyStatus->dwError && pPolicyPara &&
+     pPolicyPara->cbSize >= sizeof(CERT_CHAIN_POLICY_PARA))
+    {
+        HTTPSPolicyCallbackData *sslPara = pPolicyPara->pvExtraPolicyPara;
+
+        if (sslPara && sslPara->u.cbSize >= sizeof(HTTPSPolicyCallbackData))
+        {
+            if (sslPara->dwAuthType == AUTHTYPE_SERVER &&
+             sslPara->pwszServerName)
+            {
+                PCCERT_CONTEXT cert;
+                PCERT_EXTENSION altNameExt;
+                BOOL matches;
+
+                cert = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;
+                altNameExt = get_subject_alt_name_ext(cert);
+                /* If the alternate name extension exists, the name it contains
+                 * is bound to the certificate, so make sure the name matches
+                 * it.  Otherwise, look for the server name in the subject
+                 * distinguished name.  RFC5280, section 4.2.1.6:
+                 * "Whenever such identities are to be bound into a
+                 *  certificate, the subject alternative name (or issuer
+                 *  alternative name) extension MUST be used; however, a DNS
+                 *  name MAY also be represented in the subject field using the
+                 *  domainComponent attribute."
+                 */
+                if (altNameExt)
+                    matches = match_dns_to_subject_alt_name(altNameExt,
+                     sslPara->pwszServerName);
+                else
+                    matches = match_dns_to_subject_dn(cert,
+                     sslPara->pwszServerName);
+                if (!matches)
+                {
+                    pPolicyStatus->dwError = CERT_E_CN_NO_MATCH;
+                    pPolicyStatus->lChainIndex = 0;
+                    pPolicyStatus->lElementIndex = 0;
+                }
+            }
+        }
+    }
+    return TRUE;
+}
+
 static BYTE msPubKey1[] = {
 0x30,0x82,0x01,0x0a,0x02,0x82,0x01,0x01,0x00,0xdf,0x08,0xba,0xe3,0x3f,0x6e,
 0x64,0x9b,0xf5,0x89,0xaf,0x28,0x96,0x4a,0x07,0x8f,0x1b,0x2e,0x8b,0x3e,0x1d,
@@ -2257,6 +2485,9 @@ BOOL WINAPI CertVerifyCertificateChainPolicy(LPCSTR szPolicyOID,
         case LOWORD(CERT_CHAIN_POLICY_AUTHENTICODE):
             verifyPolicy = verify_authenticode_policy;
             break;
+        case LOWORD(CERT_CHAIN_POLICY_SSL):
+            verifyPolicy = verify_ssl_policy;
+            break;
         case LOWORD(CERT_CHAIN_POLICY_BASIC_CONSTRAINTS):
             verifyPolicy = verify_basic_constraints_policy;
             break;




More information about the wine-cvs mailing list