schannel: check schannel_sha256sum() success, and more

Also:
- support 4GiB+ SHA-256 digest inputs.
- check `CryptGetHashParam()` output size.
- avoid overwriting existing digest when new digest calculation fails.
- avoid adding digest hash element on failure.

Closes #21739
This commit is contained in:
Viktor Szakats 2026-05-24 14:55:05 +02:00
parent c7f0267eb7
commit a1baacc670
No known key found for this signature in database

View file

@ -2640,16 +2640,24 @@ static CURLcode schannel_random(struct Curl_easy *data,
return Curl_win32_random(entropy, length);
}
static void schannel_checksum(const unsigned char *input,
size_t inputlen,
unsigned char *checksum,
size_t checksumlen,
DWORD provType,
const unsigned int algId)
static CURLcode schannel_checksum(const unsigned char *input,
size_t inputlen,
unsigned char *checksum,
size_t checksumlen,
DWORD provType,
const unsigned int algId)
{
CURLcode result = CURLE_FAILED_INIT;
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
size_t off;
DWORD cbHashSize;
DWORD dwHashSizeLen;
DWORD dwChecksumLen;
/* since this can fail in multiple ways, zero memory first so we never
* return old data
*/
@ -2657,37 +2665,45 @@ static void schannel_checksum(const unsigned char *input,
if(!CryptAcquireContext(&hProv, NULL, NULL, provType,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
return; /* failed */
goto out;
do {
DWORD cbHashSize = 0;
DWORD dwHashSizeLen = (DWORD)sizeof(cbHashSize);
DWORD dwChecksumLen = (DWORD)checksumlen;
if(!CryptCreateHash(hProv, algId, 0, 0, &hHash))
goto out;
if(!CryptCreateHash(hProv, algId, 0, 0, &hHash))
break; /* failed */
result = CURLE_BAD_FUNCTION_ARGUMENT;
if(!CryptHashData(hHash, input, (DWORD)inputlen, 0))
break; /* failed */
off = 0;
while(off < inputlen) {
DWORD chunk = (DWORD)CURLMIN(inputlen - off, 0xffffffffUL);
if(!CryptHashData(hHash, input + off, chunk, 0))
goto out;
off += chunk;
}
/* get hash size */
if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize,
&dwHashSizeLen, 0))
break; /* failed */
/* get hash size */
cbHashSize = 0;
dwHashSizeLen = (DWORD)sizeof(cbHashSize);
if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize,
&dwHashSizeLen, 0))
goto out;
/* check hash size */
if(checksumlen < cbHashSize)
break; /* failed */
/* check if hash fits into the return buffer */
if(checksumlen < cbHashSize)
goto out;
if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &dwChecksumLen, 0))
break; /* failed */
} while(0);
dwChecksumLen = (DWORD)checksumlen;
if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &dwChecksumLen, 0) &&
dwChecksumLen == cbHashSize)
result = CURLE_OK;
out:
if(hHash)
CryptDestroyHash(hHash);
if(hProv)
CryptReleaseContext(hProv, 0);
return result;
}
static CURLcode schannel_sha256sum(const unsigned char *input,
@ -2695,9 +2711,8 @@ static CURLcode schannel_sha256sum(const unsigned char *input,
unsigned char *sha256sum,
size_t sha256len)
{
schannel_checksum(input, inputlen, sha256sum, sha256len,
PROV_RSA_AES, CALG_SHA_256);
return CURLE_OK;
return schannel_checksum(input, inputlen, sha256sum, sha256len,
PROV_RSA_AES, CALG_SHA_256);
}
static void *schannel_get_internals(struct ssl_connect_data *connssl,
@ -2755,10 +2770,11 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf,
if(share->CAinfo_blob_size != ca_info_blob->len) {
return NULL;
}
schannel_sha256sum((const unsigned char *)ca_info_blob->data,
ca_info_blob->len,
info_blob_digest,
CURL_SHA256_DIGEST_LENGTH);
if(schannel_sha256sum((const unsigned char *)ca_info_blob->data,
ca_info_blob->len,
info_blob_digest,
CURL_SHA256_DIGEST_LENGTH))
return NULL;
if(memcmp(share->CAinfo_blob_digest, info_blob_digest,
CURL_SHA256_DIGEST_LENGTH)) {
return NULL;
@ -2796,7 +2812,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf,
struct Curl_multi *multi = data->multi;
const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
struct schannel_cert_share *share;
size_t CAinfo_blob_size = 0;
unsigned char digest[CURL_SHA256_DIGEST_LENGTH];
char *CAfile = NULL;
DEBUGASSERT(multi);
@ -2805,12 +2821,26 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf,
return FALSE;
}
if(ca_info_blob) {
if(schannel_sha256sum((const unsigned char *)ca_info_blob->data,
ca_info_blob->len, digest, sizeof(digest))) {
return FALSE;
}
}
else if(conn_config->CAfile) {
CAfile = curlx_strdup(conn_config->CAfile);
if(!CAfile) {
return FALSE;
}
}
share = Curl_hash_pick(&multi->proto_hash,
CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY),
sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1);
if(!share) {
share = curlx_calloc(1, sizeof(*share));
if(!share) {
curlx_free(CAfile);
return FALSE;
}
if(!Curl_hash_add2(&multi->proto_hash,
@ -2818,35 +2848,26 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf,
sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1,
share, schannel_cert_share_free)) {
curlx_free(share);
curlx_free(CAfile);
return FALSE;
}
}
if(ca_info_blob) {
schannel_sha256sum((const unsigned char *)ca_info_blob->data,
ca_info_blob->len,
share->CAinfo_blob_digest,
CURL_SHA256_DIGEST_LENGTH);
CAinfo_blob_size = ca_info_blob->len;
}
else {
if(conn_config->CAfile) {
CAfile = curlx_strdup(conn_config->CAfile);
if(!CAfile) {
return FALSE;
}
}
}
/* free old cache data */
if(share->cert_store) {
CertCloseStore(share->cert_store, 0);
}
curlx_free(share->CAfile);
if(ca_info_blob) {
memcpy(share->CAinfo_blob_digest, digest, sizeof(digest));
share->CAinfo_blob_size = ca_info_blob->len;
}
else
share->CAinfo_blob_size = 0;
share->time = curlx_now();
share->cert_store = cert_store;
share->CAinfo_blob_size = CAinfo_blob_size;
share->CAfile = CAfile;
return TRUE;
}