cryptnet: Support verifying certificate revocation with OCSP.

Signed-off-by: Hans Leidekker <hans@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Hans Leidekker 2022-04-06 15:15:32 +02:00 committed by Alexandre Julliard
parent 3b85129602
commit 60e10a0571
3 changed files with 375 additions and 22 deletions

View File

@ -1,6 +1,6 @@
MODULE = cryptnet.dll
IMPORTLIB = cryptnet
IMPORTS = crypt32 shell32 ole32
IMPORTS = crypt32 shell32 ole32 advapi32
DELAYIMPORTS = wininet
C_SRCS = \

View File

@ -1720,37 +1720,386 @@ static DWORD verify_cert_revocation_from_dist_points_ext(const CRYPT_DATA_BLOB *
return error;
}
static void sha1_hash(const BYTE *data, DWORD datalen, BYTE *buf, DWORD *buflen)
{
HCRYPTPROV prov;
HCRYPTHASH hash;
CryptAcquireContextW(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
CryptCreateHash(prov, CALG_SHA1, 0, 0, &hash);
CryptHashData(hash, data, datalen, 0);
CryptGetHashParam(hash, HP_HASHVAL, buf, buflen, 0);
CryptDestroyHash(hash);
CryptReleaseContext(prov, 0);
}
static BYTE *build_ocsp_request(const CERT_CONTEXT *cert, const CERT_CONTEXT *issuer_cert, DWORD *ret_size)
{
OCSP_REQUEST_ENTRY entry;
OCSP_REQUEST_INFO request;
OCSP_SIGNED_REQUEST_INFO request_signed;
CERT_INFO *issuer = issuer_cert->pCertInfo;
BYTE issuer_name_hash[20], issuer_key_hash[20], *buf, *ret;
DWORD size = 0, hash_len = sizeof(issuer_name_hash);
memset(&entry, 0, sizeof(entry));
entry.CertId.HashAlgorithm.pszObjId = (char *)szOID_OIWSEC_sha1;
sha1_hash(issuer->Subject.pbData, issuer->Subject.cbData, issuer_name_hash, &hash_len);
entry.CertId.IssuerNameHash.cbData = sizeof(issuer_name_hash);
entry.CertId.IssuerNameHash.pbData = issuer_name_hash;
sha1_hash(issuer->SubjectPublicKeyInfo.PublicKey.pbData, issuer->SubjectPublicKeyInfo.PublicKey.cbData,
issuer_key_hash, &hash_len);
entry.CertId.IssuerKeyHash.cbData = sizeof(issuer_key_hash);
entry.CertId.IssuerKeyHash.pbData = issuer_key_hash;
entry.CertId.SerialNumber.cbData = cert->pCertInfo->SerialNumber.cbData;
entry.CertId.SerialNumber.pbData = cert->pCertInfo->SerialNumber.pbData;
request.dwVersion = OCSP_REQUEST_V1;
request.pRequestorName = NULL;
request.cRequestEntry = 1;
request.rgRequestEntry = &entry;
request.cExtension = 0;
request.rgExtension = NULL;
if (!CryptEncodeObjectEx(X509_ASN_ENCODING, OCSP_REQUEST, &request, CRYPT_ENCODE_ALLOC_FLAG, NULL, &buf, &size))
{
ERR("failed to encode request %#lx\n", GetLastError());
return NULL;
}
request_signed.ToBeSigned.pbData = buf;
request_signed.ToBeSigned.cbData = size;
request_signed.pOptionalSignatureInfo = NULL;
if (!CryptEncodeObjectEx(X509_ASN_ENCODING, OCSP_SIGNED_REQUEST, &request_signed, CRYPT_ENCODE_ALLOC_FLAG, NULL,
&ret, &size))
{
ERR("failed to encode signed request %#lx\n", GetLastError());
LocalFree(buf);
return NULL;
}
LocalFree(buf);
*ret_size = size;
return ret;
}
static void escape_path(const WCHAR *src, DWORD src_len, WCHAR *dst, DWORD *dst_len)
{
static const WCHAR hex[] = L"0123456789ABCDEF";
WCHAR *ptr = dst;
DWORD i;
*dst_len = src_len;
for (i = 0; i < src_len; i++)
{
if (src[i] == '+' || src[i] == '/' || src[i] == '=')
{
if (dst)
{
ptr[0] = '%';
ptr[1] = hex[(src[i] >> 4) & 0xf];
ptr[2] = hex[src[i] & 0xf];
ptr += 3;
}
*dst_len += 2;
}
else if (dst) *ptr++ = src[i];
}
}
static WCHAR *build_request_path(const BYTE *data, DWORD data_size)
{
WCHAR *path, *ret;
DWORD path_len, ret_len;
if (!CryptBinaryToStringW(data, data_size, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &path_len)) return NULL;
if (!(path = malloc(path_len * sizeof(WCHAR)))) return NULL;
CryptBinaryToStringW(data, data_size, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, path, &path_len);
escape_path(path, path_len, NULL, &ret_len);
if (!(ret = malloc((ret_len + 2) * sizeof(WCHAR))))
{
free(path);
return NULL;
}
escape_path(path, path_len, ret + 1, &ret_len);
ret[ret_len + 1] = 0;
ret[0] = '/';
free(path);
return ret;
}
static WCHAR *build_request_url(const WCHAR *base_url, const BYTE *data, DWORD data_size)
{
WCHAR *path, *ret;
DWORD len = 0;
if (!(path = build_request_path(data, data_size))) return NULL;
InternetCombineUrlW(base_url, path, NULL, &len, 0);
if (!(ret = malloc(len * sizeof(WCHAR))))
{
free(path);
return NULL;
}
InternetCombineUrlW(base_url, path, ret, &len, 0);
free(path);
return ret;
}
static DWORD map_ocsp_status(DWORD status)
{
switch (status)
{
case OCSP_BASIC_GOOD_CERT_STATUS: return ERROR_SUCCESS;
case OCSP_BASIC_REVOKED_CERT_STATUS: return CRYPT_E_REVOKED;
case OCSP_BASIC_UNKNOWN_CERT_STATUS: return CRYPT_E_REVOCATION_OFFLINE;
default:
FIXME("unhandled status %lu\n", status);
return CRYPT_E_REVOCATION_OFFLINE;
}
}
static BOOL match_cert_id(const OCSP_CERT_ID *id, const CERT_INFO *cert, const CERT_INFO *issuer)
{
BYTE hash[20];
DWORD hash_len = sizeof(hash);
if (!id->HashAlgorithm.pszObjId || strcmp(id->HashAlgorithm.pszObjId, szOID_OIWSEC_sha1))
{
FIXME("hash algorithm %s not supported\n", debugstr_a(id->HashAlgorithm.pszObjId));
return FALSE;
}
sha1_hash(issuer->Subject.pbData, issuer->Subject.cbData, hash, &hash_len);
if (id->IssuerNameHash.cbData != hash_len) return FALSE;
if (memcmp(id->IssuerNameHash.pbData, hash, hash_len)) return FALSE;
sha1_hash(issuer->SubjectPublicKeyInfo.PublicKey.pbData,
issuer->SubjectPublicKeyInfo.PublicKey.cbData, hash, &hash_len);
if (id->IssuerKeyHash.cbData != hash_len) return FALSE;
if (memcmp(id->IssuerKeyHash.pbData, hash, hash_len)) return FALSE;
if (cert->SerialNumber.cbData != id->SerialNumber.cbData) return FALSE;
return !memcmp(cert->SerialNumber.pbData, id->SerialNumber.pbData, id->SerialNumber.cbData);
}
static DWORD check_ocsp_response_info(const CERT_INFO *cert, const CERT_INFO *issuer,
const CRYPT_OBJID_BLOB *blob, DWORD *status)
{
OCSP_BASIC_RESPONSE_INFO *info;
DWORD size, i;
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, OCSP_BASIC_RESPONSE, blob->pbData, blob->cbData,
CRYPT_DECODE_ALLOC_FLAG, NULL, &info, &size)) return GetLastError();
FIXME("check responder id\n");
for (i = 0; i < info->cResponseEntry; i++)
{
OCSP_BASIC_RESPONSE_ENTRY *entry = &info->rgResponseEntry[i];
if (match_cert_id(&entry->CertId, cert, issuer)) *status = map_ocsp_status(entry->dwCertStatus);
}
LocalFree(info);
return ERROR_SUCCESS;
}
static DWORD verify_signed_ocsp_response_info(const CERT_INFO *cert, const CERT_INFO *issuer,
const CRYPT_OBJID_BLOB *blob)
{
OCSP_BASIC_SIGNED_RESPONSE_INFO *info;
DWORD size, error, status = CRYPT_E_REVOCATION_OFFLINE;
CRYPT_ALGORITHM_IDENTIFIER *alg;
CRYPT_BIT_BLOB *sig;
HCRYPTPROV prov = 0;
HCRYPTHASH hash = 0;
HCRYPTKEY key = 0;
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))) goto done;
alg = &info->SignatureInfo.SignatureAlgorithm;
if (!alg->pszObjId || strcmp(alg->pszObjId, szOID_RSA_SHA256RSA))
{
FIXME("unhandled signature algorithm %s\n", debugstr_a(alg->pszObjId));
error = CRYPT_E_NO_REVOCATION_CHECK;
goto done;
}
if (!CryptAcquireContextW(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) goto done;
if (!CryptCreateHash(prov, CALG_SHA_256, 0, 0, &hash)) goto done;
if (!CryptHashData(hash, info->ToBeSigned.pbData, info->ToBeSigned.cbData, 0)) goto done;
sig = &info->SignatureInfo.Signature;
if (!CryptImportPublicKeyInfoEx(prov, X509_ASN_ENCODING, (CERT_PUBLIC_KEY_INFO *)&issuer->SubjectPublicKeyInfo,
0, 0, NULL, &key))
{
error = GetLastError();
TRACE("failed to import public key %#lx\n", error);
}
else if (!CryptVerifySignatureW(hash, sig->pbData, sig->cbData, key, NULL, 0))
{
error = GetLastError();
TRACE("failed to verify signature %#lx\n", error);
}
else error = ERROR_SUCCESS;
done:
CryptDestroyKey(key);
CryptDestroyHash(hash);
CryptReleaseContext(prov, 0);
LocalFree(info);
if (error) return error;
return status;
}
static DWORD handle_ocsp_response(const CERT_INFO *cert, const CERT_INFO *issuer, const BYTE *encoded,
DWORD encoded_size)
{
OCSP_RESPONSE_INFO *info;
DWORD size, error = CRYPT_E_NO_REVOCATION_CHECK;
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, OCSP_RESPONSE, encoded, encoded_size, CRYPT_DECODE_ALLOC_FLAG, NULL,
&info, &size)) return GetLastError();
switch (info->dwStatus)
{
case OCSP_SUCCESSFUL_RESPONSE:
if (!info->pszObjId || strcmp(info->pszObjId, szOID_PKIX_OCSP_BASIC_SIGNED_RESPONSE))
{
FIXME("unhandled response type %s\n", debugstr_a(info->pszObjId));
break;
}
error = verify_signed_ocsp_response_info(cert, issuer, &info->Value);
break;
default:
FIXME("unhandled status %lu\n", info->dwStatus);
break;
}
LocalFree(info);
return error;
}
static DWORD verify_cert_revocation_with_ocsp(const CERT_CONTEXT *cert, const WCHAR *base_url,
const CERT_REVOCATION_PARA *revpara)
{
HINTERNET ses, con, req = NULL;
BYTE *request_data = NULL, *response_data = NULL;
DWORD size, flags, status, request_len, response_len, count, ret = CRYPT_E_REVOCATION_OFFLINE;
URL_COMPONENTSW comp;
WCHAR *url;
if (!revpara || !revpara->pIssuerCert)
{
TRACE("no issuer certificate\n");
return CRYPT_E_REVOCATION_OFFLINE;
}
if (!(request_data = build_ocsp_request(cert, revpara->pIssuerCert, &request_len)))
return CRYPT_E_REVOCATION_OFFLINE;
url = build_request_url(base_url, request_data, request_len);
LocalFree(request_data);
if (!url) return CRYPT_E_REVOCATION_OFFLINE;
memset(&comp, 0, sizeof(comp));
comp.dwStructSize = sizeof(comp);
comp.dwHostNameLength = ~0u;
comp.dwUrlPathLength = ~0u;
if (!InternetCrackUrlW(url, 0, 0, &comp))
{
free(url);
return CRYPT_E_REVOCATION_OFFLINE;
}
switch (comp.nScheme)
{
case INTERNET_SCHEME_HTTP:
flags = 0;
break;
case INTERNET_SCHEME_HTTPS:
flags = INTERNET_FLAG_SECURE;
break;
default:
FIXME("scheme %u not supported\n", comp.nScheme);
free(url);
return ERROR_NOT_SUPPORTED;
}
if (!(ses = InternetOpenW(L"CryptoAPI", 0, NULL, NULL, 0))) return GetLastError();
comp.lpszHostName[comp.dwHostNameLength] = 0;
if (!(con = InternetConnectW(ses, comp.lpszHostName, comp.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0)))
{
free(url);
InternetCloseHandle(ses);
return GetLastError();
}
comp.lpszHostName[comp.dwHostNameLength] = '/';
if (!(req = HttpOpenRequestW(con, NULL, comp.lpszUrlPath, NULL, NULL, NULL, flags, 0)) ||
!HttpSendRequestW(req, NULL, 0, NULL, 0)) goto done;
size = sizeof(status);
if (!HttpQueryInfoW(req, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &size, NULL)) goto done;
if (status != HTTP_STATUS_OK)
{
WARN("request status %lu\n", status);
goto done;
}
size = sizeof(response_len);
if (!HttpQueryInfoW(req, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, &response_len, &size, 0) ||
!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);
done:
free(url);
free(response_data);
InternetCloseHandle(req);
InternetCloseHandle(con);
InternetCloseHandle(ses);
return ret;
}
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)
{
BOOL ret;
DWORD error, size;
DWORD size, i, error = CRYPT_E_NO_REVOCATION_CHECK;
CERT_AUTHORITY_INFO_ACCESS *aia;
ret = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_AUTHORITY_INFO_ACCESS,
value->pbData, value->cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &aia, &size);
if (ret)
{
DWORD i;
ret = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_AUTHORITY_INFO_ACCESS, value->pbData, value->cbData,
CRYPT_DECODE_ALLOC_FLAG, NULL, &aia, &size);
if (!ret) return GetLastError();
for (i = 0; i < aia->cAccDescr; i++)
if (!strcmp(aia->rgAccDescr[i].pszAccessMethod,
szOID_PKIX_OCSP))
for (i = 0; i < aia->cAccDescr; i++)
{
if (!strcmp(aia->rgAccDescr[i].pszAccessMethod, szOID_PKIX_OCSP))
{
if (aia->rgAccDescr[i].AccessLocation.dwAltNameChoice == CERT_ALT_NAME_URL)
{
if (aia->rgAccDescr[i].AccessLocation.dwAltNameChoice ==
CERT_ALT_NAME_URL)
FIXME("OCSP URL = %s\n",
debugstr_w(aia->rgAccDescr[i].AccessLocation.u.pwszURL));
else
FIXME("unsupported AccessLocation type %ld\n",
aia->rgAccDescr[i].AccessLocation.dwAltNameChoice);
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);
}
LocalFree(aia);
/* FIXME: lie and pretend OCSP validated the cert */
error = ERROR_SUCCESS;
else
{
FIXME("unsupported AccessLocation type %lu\n", aia->rgAccDescr[i].AccessLocation.dwAltNameChoice);
error = ERROR_NOT_SUPPORTED;
}
break;
}
}
else
error = GetLastError();
LocalFree(aia);
return error;
}

View File

@ -669,6 +669,10 @@ typedef struct _OCSP_BASIC_REVOKED_INFO {
DWORD dwCrlReasonCode;
} OCSP_BASIC_REVOKED_INFO, *POCSP_BASIC_REVOKED_INFO;
#define OCSP_BASIC_GOOD_CERT_STATUS 0
#define OCSP_BASIC_REVOKED_CERT_STATUS 1
#define OCSP_BASIC_UNKNOWN_CERT_STATUS 2
typedef struct _OCSP_BASIC_RESPONSE_ENTRY {
OCSP_CERT_ID CertId;
DWORD dwCertStatus;