Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=54353
There are two issues which the patchset is fixing: 1. Using cert's serial is not quite right, we need to hash full certificate contents as the check result depends on the other certificate data. 2. The check result also depends on revocation check parameters. I think currently it is only issuer certificate from the parameters which can affect the result which is cached.
From: Paul Gofman pgofman@codeweavers.com
--- dlls/cryptnet/cryptnet_main.c | 56 +++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 25 deletions(-)
diff --git a/dlls/cryptnet/cryptnet_main.c b/dlls/cryptnet/cryptnet_main.c index 516bbad9ca4..211b5c3cf8c 100644 --- a/dlls/cryptnet/cryptnet_main.c +++ b/dlls/cryptnet/cryptnet_main.c @@ -1683,7 +1683,8 @@ static const CRL_CONTEXT *retrieve_crl_from_dist_points(const CRYPT_URL_ARRAY *a }
static DWORD verify_cert_revocation_from_dist_points_ext(const CRYPT_DATA_BLOB *value, const CERT_CONTEXT *cert, - FILETIME *time, DWORD flags, const CERT_REVOCATION_PARA *params, CERT_REVOCATION_STATUS *status) + FILETIME *time, DWORD flags, const CERT_REVOCATION_PARA *params, CERT_REVOCATION_STATUS *status, + FILETIME *next_update) { DWORD url_array_size, error; CRYPT_URL_ARRAY *url_array; @@ -1719,7 +1720,7 @@ static DWORD verify_cert_revocation_from_dist_points_ext(const CRYPT_DATA_BLOB *
error = verify_cert_revocation_with_crl_online(cert, crl, time, status);
- cache_revocation_status(&cert->pCertInfo->SerialNumber, &crl->pCrlInfo->NextUpdate, status); + *next_update = crl->pCrlInfo->NextUpdate;
CertFreeCRLContext(crl); CryptMemFree(url_array); @@ -1920,12 +1921,11 @@ static DWORD check_ocsp_response_info(const CERT_INFO *cert, const CERT_INFO *is }
static DWORD verify_signed_ocsp_response_info(const CERT_INFO *cert, const CERT_INFO *issuer, - const CRYPT_OBJID_BLOB *blob) + const CRYPT_OBJID_BLOB *blob, FILETIME *next_update) { OCSP_BASIC_SIGNED_RESPONSE_INFO *info; DWORD size, error, status = CRYPT_E_REVOCATION_OFFLINE; CRYPT_ALGORITHM_IDENTIFIER *alg; - FILETIME next_update; CRYPT_BIT_BLOB *sig; HCRYPTPROV prov = 0; HCRYPTHASH hash = 0; @@ -1935,7 +1935,7 @@ static DWORD verify_signed_ocsp_response_info(const CERT_INFO *cert, const CERT_ if (!CryptDecodeObjectEx(X509_ASN_ENCODING, OCSP_BASIC_SIGNED_RESPONSE, blob->pbData, blob->cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &info, &size)) return GetLastError();
- if ((error = check_ocsp_response_info(cert, issuer, &info->ToBeSigned, &status, &next_update))) goto done; + if ((error = check_ocsp_response_info(cert, issuer, &info->ToBeSigned, &status, next_update))) goto done;
alg = &info->SignatureInfo.SignatureAlgorithm; if (!alg->pszObjId || !(algid = CertOIDToAlgId(alg->pszObjId))) @@ -1964,16 +1964,6 @@ static DWORD verify_signed_ocsp_response_info(const CERT_INFO *cert, const CERT_ else error = ERROR_SUCCESS;
done: - if (next_update.dwLowDateTime || next_update.dwHighDateTime) - { - CERT_REVOCATION_STATUS rev_status; - - memset(&rev_status, 0, sizeof(rev_status)); - rev_status.cbSize = sizeof(rev_status); - rev_status.dwError = status; - cache_revocation_status(&cert->SerialNumber, &next_update, &rev_status); - } - CryptDestroyKey(key); CryptDestroyHash(hash); CryptReleaseContext(prov, 0); @@ -1983,7 +1973,7 @@ done: }
static DWORD handle_ocsp_response(const CERT_INFO *cert, const CERT_INFO *issuer, const BYTE *encoded, - DWORD encoded_size) + DWORD encoded_size, FILETIME *next_update) { OCSP_RESPONSE_INFO *info; DWORD size, error = CRYPT_E_NO_REVOCATION_CHECK; @@ -1999,7 +1989,7 @@ static DWORD handle_ocsp_response(const CERT_INFO *cert, const CERT_INFO *issuer FIXME("unhandled response type %s\n", debugstr_a(info->pszObjId)); break; } - error = verify_signed_ocsp_response_info(cert, issuer, &info->Value); + error = verify_signed_ocsp_response_info(cert, issuer, &info->Value, next_update); break;
default: @@ -2012,7 +2002,7 @@ static DWORD handle_ocsp_response(const CERT_INFO *cert, const CERT_INFO *issuer }
static DWORD verify_cert_revocation_with_ocsp(const CERT_CONTEXT *cert, const WCHAR *base_url, - const CERT_REVOCATION_PARA *revpara) + const CERT_REVOCATION_PARA *revpara, FILETIME *next_update) { HINTERNET ses, con, req = NULL; BYTE *request_data = NULL, *response_data = NULL; @@ -2081,7 +2071,8 @@ static DWORD verify_cert_revocation_with_ocsp(const CERT_CONTEXT *cert, const WC !response_len || !(response_data = malloc(response_len)) || !InternetReadFile(req, response_data, response_len, &count) || count != response_len) goto done;
- ret = handle_ocsp_response(cert->pCertInfo, revpara->pIssuerCert->pCertInfo, response_data, response_len); + ret = handle_ocsp_response(cert->pCertInfo, revpara->pIssuerCert->pCertInfo, response_data, response_len, + next_update);
done: free(url); @@ -2093,7 +2084,8 @@ done: }
static DWORD verify_cert_revocation_from_aia_ext(const CRYPT_DATA_BLOB *value, const CERT_CONTEXT *cert, - FILETIME *pTime, DWORD dwFlags, CERT_REVOCATION_PARA *pRevPara, CERT_REVOCATION_STATUS *pRevStatus) + FILETIME *pTime, DWORD dwFlags, CERT_REVOCATION_PARA *pRevPara, CERT_REVOCATION_STATUS *pRevStatus, + FILETIME *next_update) { BOOL ret; DWORD size, i, error = CRYPT_E_NO_REVOCATION_CHECK; @@ -2111,7 +2103,7 @@ static DWORD verify_cert_revocation_from_aia_ext(const CRYPT_DATA_BLOB *value, c { const WCHAR *url = aia->rgAccDescr[i].AccessLocation.u.pwszURL; TRACE("OCSP URL = %s\n", debugstr_w(url)); - error = verify_cert_revocation_with_ocsp(cert, url, pRevPara); + error = verify_cert_revocation_with_ocsp(cert, url, pRevPara, next_update); } else { @@ -2157,6 +2149,7 @@ static DWORD verify_cert_revocation(const CERT_CONTEXT *cert, FILETIME *pTime, DWORD dwFlags, CERT_REVOCATION_PARA *pRevPara, CERT_REVOCATION_STATUS *pRevStatus) { DWORD error = ERROR_SUCCESS; + FILETIME next_update = {0}; PCERT_EXTENSION ext;
if (find_cached_revocation_status(&cert->pCertInfo->SerialNumber, pTime, pRevStatus)) @@ -2167,15 +2160,17 @@ static DWORD verify_cert_revocation(const CERT_CONTEXT *cert, FILETIME *pTime,
if ((ext = CertFindExtension(szOID_AUTHORITY_INFO_ACCESS, cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension))) { - error = verify_cert_revocation_from_aia_ext(&ext->Value, cert, pTime, dwFlags, pRevPara, pRevStatus); + error = verify_cert_revocation_from_aia_ext(&ext->Value, cert, pTime, dwFlags, pRevPara, pRevStatus, + &next_update); TRACE("verify_cert_revocation_from_aia_ext() returned %08lx\n", error); - if (error == ERROR_SUCCESS || error == CRYPT_E_REVOKED) return error; + if (error == ERROR_SUCCESS || error == CRYPT_E_REVOKED) goto done; } if ((ext = CertFindExtension(szOID_CRL_DIST_POINTS, cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension))) { - error = verify_cert_revocation_from_dist_points_ext(&ext->Value, cert, pTime, dwFlags, pRevPara, pRevStatus); + error = verify_cert_revocation_from_dist_points_ext(&ext->Value, cert, pTime, dwFlags, pRevPara, pRevStatus, + &next_update); TRACE("verify_cert_revocation_from_dist_points_ext() returned %08lx\n", error); - if (error == ERROR_SUCCESS || error == CRYPT_E_REVOKED) return error; + if (error == ERROR_SUCCESS || error == CRYPT_E_REVOKED) goto done; } if (!ext) { @@ -2247,6 +2242,17 @@ static DWORD verify_cert_revocation(const CERT_CONTEXT *cert, FILETIME *pTime, error = CRYPT_E_NO_REVOCATION_CHECK; } } +done: + if ((next_update.dwLowDateTime || next_update.dwHighDateTime) + && (error == ERROR_SUCCESS || error == CRYPT_E_REVOKED)) + { + CERT_REVOCATION_STATUS rev_status; + + memset(&rev_status, 0, sizeof(rev_status)); + rev_status.cbSize = sizeof(rev_status); + rev_status.dwError = error; + cache_revocation_status(&cert->pCertInfo->SerialNumber, &next_update, &rev_status); + } return error; }
From: Paul Gofman pgofman@codeweavers.com
--- dlls/cryptnet/cryptnet_main.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/dlls/cryptnet/cryptnet_main.c b/dlls/cryptnet/cryptnet_main.c index 211b5c3cf8c..a6a9791586f 100644 --- a/dlls/cryptnet/cryptnet_main.c +++ b/dlls/cryptnet/cryptnet_main.c @@ -1537,11 +1537,16 @@ BOOL WINAPI CryptRetrieveObjectByUrlW(LPCWSTR pszURL, LPCSTR pszObjectOid,
static const char revocation_cache_signature[] = "Wine cached revocation";
-static FILE *open_cached_revocation_file(const CRYPT_INTEGER_BLOB *serial, const WCHAR *mode, int sharing) +#define CACHED_CERT_HASH_SIZE 20 + +static FILE *open_cached_revocation_file(const CERT_CONTEXT *cert, const WCHAR *mode, int sharing) { + BYTE hash_data[CACHED_CERT_HASH_SIZE]; WCHAR path[MAX_PATH]; WCHAR *appdata_path; - DWORD len, i; + DWORD len, i, size; + HCRYPTPROV prov; + HCRYPTHASH hash; HRESULT hr;
if (FAILED(hr = SHGetKnownFolderPath(&FOLDERID_LocalAppDataLow, 0, NULL, &appdata_path))) @@ -1553,24 +1558,32 @@ static FILE *open_cached_revocation_file(const CRYPT_INTEGER_BLOB *serial, const len = swprintf(path, ARRAY_SIZE(path), L"%s\Microsoft\CryptnetUrlCache\Content\", appdata_path); CoTaskMemFree(appdata_path);
- if (len + serial->cbData * 2 * sizeof(WCHAR) > ARRAY_SIZE(path) - 1) + if (len + CACHED_CERT_HASH_SIZE * 2 * sizeof(WCHAR) > ARRAY_SIZE(path) - 1) { WARN("Serial length exceeds static buffer; not caching.\n"); return INVALID_HANDLE_VALUE; }
+ CryptAcquireContextW(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptCreateHash(prov, CALG_SHA1, 0, 0, &hash); + CryptHashData(hash, cert->pbCertEncoded, cert->cbCertEncoded, 0); + size = sizeof(hash_data); + CryptGetHashParam(hash, HP_HASHVAL, hash_data, &size, 0); + CryptDestroyHash(hash); + CryptReleaseContext(prov, 0); + SHCreateDirectoryExW(NULL, path, NULL);
- for (i = 0; i < serial->cbData; ++i) + for (i = 0; i < CACHED_CERT_HASH_SIZE; ++i) { - swprintf(path + len, 3, L"%02x", serial->pbData[i]); + swprintf(path + len, 3, L"%02x", hash_data[i]); len += 2; }
return _wfsopen(path, mode, sharing); }
-static BOOL find_cached_revocation_status(const CRYPT_INTEGER_BLOB *serial, +static BOOL find_cached_revocation_status(const CERT_CONTEXT *cert, const FILETIME *time, CERT_REVOCATION_STATUS *status) { char buffer[sizeof(revocation_cache_signature)]; @@ -1578,7 +1591,7 @@ static BOOL find_cached_revocation_status(const CRYPT_INTEGER_BLOB *serial, FILE *file; int len;
- if (!(file = open_cached_revocation_file(serial, L"rb", _SH_DENYWR))) + if (!(file = open_cached_revocation_file(cert, L"rb", _SH_DENYWR))) return FALSE;
if ((len = fread(buffer, 1, sizeof(buffer), file)) != sizeof(buffer) @@ -1621,12 +1634,12 @@ static BOOL find_cached_revocation_status(const CRYPT_INTEGER_BLOB *serial, return TRUE; }
-static void cache_revocation_status(const CRYPT_INTEGER_BLOB *serial, +static void cache_revocation_status(const CERT_CONTEXT *cert, const FILETIME *time, const CERT_REVOCATION_STATUS *status) { FILE *file;
- if (!(file = open_cached_revocation_file(serial, L"wb", _SH_DENYRW))) + if (!(file = open_cached_revocation_file(cert, L"wb", _SH_DENYRW))) return; fwrite(revocation_cache_signature, 1, sizeof(revocation_cache_signature), file); fwrite(time, sizeof(*time), 1, file); @@ -2152,7 +2165,7 @@ static DWORD verify_cert_revocation(const CERT_CONTEXT *cert, FILETIME *pTime, FILETIME next_update = {0}; PCERT_EXTENSION ext;
- if (find_cached_revocation_status(&cert->pCertInfo->SerialNumber, pTime, pRevStatus)) + if (find_cached_revocation_status(cert, pTime, pRevStatus)) { if (pRevStatus->dwError == ERROR_SUCCESS || pRevStatus->dwError == CRYPT_E_REVOKED) return pRevStatus->dwError; @@ -2251,7 +2264,7 @@ done: memset(&rev_status, 0, sizeof(rev_status)); rev_status.cbSize = sizeof(rev_status); rev_status.dwError = error; - cache_revocation_status(&cert->pCertInfo->SerialNumber, &next_update, &rev_status); + cache_revocation_status(cert, &next_update, &rev_status); } return error; }
From: Paul Gofman pgofman@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=54353 --- dlls/cryptnet/cryptnet_main.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-)
diff --git a/dlls/cryptnet/cryptnet_main.c b/dlls/cryptnet/cryptnet_main.c index a6a9791586f..8907502e053 100644 --- a/dlls/cryptnet/cryptnet_main.c +++ b/dlls/cryptnet/cryptnet_main.c @@ -1539,7 +1539,8 @@ static const char revocation_cache_signature[] = "Wine cached revocation";
#define CACHED_CERT_HASH_SIZE 20
-static FILE *open_cached_revocation_file(const CERT_CONTEXT *cert, const WCHAR *mode, int sharing) +static FILE *open_cached_revocation_file(const CERT_CONTEXT *cert, const CERT_REVOCATION_PARA *params, + const WCHAR *mode, int sharing) { BYTE hash_data[CACHED_CERT_HASH_SIZE]; WCHAR path[MAX_PATH]; @@ -1567,6 +1568,16 @@ static FILE *open_cached_revocation_file(const CERT_CONTEXT *cert, const WCHAR * CryptAcquireContextW(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); CryptCreateHash(prov, CALG_SHA1, 0, 0, &hash); CryptHashData(hash, cert->pbCertEncoded, cert->cbCertEncoded, 0); + if (params && params->pIssuerCert) + { + CryptHashData(hash, (BYTE *)¶ms->pIssuerCert->cbCertEncoded, sizeof(params->pIssuerCert->cbCertEncoded), 0); + CryptHashData(hash, params->pIssuerCert->pbCertEncoded, params->pIssuerCert->cbCertEncoded, 0); + } + else + { + size = 0; + CryptHashData(hash, (BYTE *)&size, sizeof(size), 0); + } size = sizeof(hash_data); CryptGetHashParam(hash, HP_HASHVAL, hash_data, &size, 0); CryptDestroyHash(hash); @@ -1583,7 +1594,7 @@ static FILE *open_cached_revocation_file(const CERT_CONTEXT *cert, const WCHAR * return _wfsopen(path, mode, sharing); }
-static BOOL find_cached_revocation_status(const CERT_CONTEXT *cert, +static BOOL find_cached_revocation_status(const CERT_CONTEXT *cert, const CERT_REVOCATION_PARA *params, const FILETIME *time, CERT_REVOCATION_STATUS *status) { char buffer[sizeof(revocation_cache_signature)]; @@ -1591,7 +1602,7 @@ static BOOL find_cached_revocation_status(const CERT_CONTEXT *cert, FILE *file; int len;
- if (!(file = open_cached_revocation_file(cert, L"rb", _SH_DENYWR))) + if (!(file = open_cached_revocation_file(cert, params, L"rb", _SH_DENYWR))) return FALSE;
if ((len = fread(buffer, 1, sizeof(buffer), file)) != sizeof(buffer) @@ -1634,12 +1645,12 @@ static BOOL find_cached_revocation_status(const CERT_CONTEXT *cert, return TRUE; }
-static void cache_revocation_status(const CERT_CONTEXT *cert, +static void cache_revocation_status(const CERT_CONTEXT *cert, const CERT_REVOCATION_PARA *params, const FILETIME *time, const CERT_REVOCATION_STATUS *status) { FILE *file;
- if (!(file = open_cached_revocation_file(cert, L"wb", _SH_DENYRW))) + if (!(file = open_cached_revocation_file(cert, params, L"wb", _SH_DENYRW))) return; fwrite(revocation_cache_signature, 1, sizeof(revocation_cache_signature), file); fwrite(time, sizeof(*time), 1, file); @@ -2165,10 +2176,13 @@ static DWORD verify_cert_revocation(const CERT_CONTEXT *cert, FILETIME *pTime, FILETIME next_update = {0}; PCERT_EXTENSION ext;
- if (find_cached_revocation_status(cert, pTime, pRevStatus)) + if (find_cached_revocation_status(cert, pRevPara, pTime, pRevStatus)) { if (pRevStatus->dwError == ERROR_SUCCESS || pRevStatus->dwError == CRYPT_E_REVOKED) + { + TRACE("Returning cached status.\n"); return pRevStatus->dwError; + } }
if ((ext = CertFindExtension(szOID_AUTHORITY_INFO_ACCESS, cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension))) @@ -2264,7 +2278,7 @@ done: memset(&rev_status, 0, sizeof(rev_status)); rev_status.cbSize = sizeof(rev_status); rev_status.dwError = error; - cache_revocation_status(cert, &next_update, &rev_status); + cache_revocation_status(cert, pRevPara, &next_update, &rev_status); } return error; }
Hans Leidekker (@hans) commented about dlls/cryptnet/cryptnet_main.c:
len = swprintf(path, ARRAY_SIZE(path), L"%s\\Microsoft\\CryptnetUrlCache\\Content\\", appdata_path); CoTaskMemFree(appdata_path);
- if (len + serial->cbData * 2 * sizeof(WCHAR) > ARRAY_SIZE(path) - 1)
- if (len + CACHED_CERT_HASH_SIZE * 2 * sizeof(WCHAR) > ARRAY_SIZE(path) - 1) { WARN("Serial length exceeds static buffer; not caching.\n"); return INVALID_HANDLE_VALUE; }
The warning needs an update, otherwise this MR looks good.