mirror of
git://source.winehq.org/git/wine.git
synced 2024-11-05 18:01:34 +00:00
crypt32: Implement CertVerifyCertificateChainPolicy for CERT_CHAIN_POLICY_SSL.
This commit is contained in:
parent
2fbb736e01
commit
9059892ec1
1 changed files with 231 additions and 0 deletions
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue