From 60e10a057168847fadf0fd70e0bc49f53ebcc052 Mon Sep 17 00:00:00 2001 From: Hans Leidekker Date: Wed, 6 Apr 2022 15:15:32 +0200 Subject: [PATCH] cryptnet: Support verifying certificate revocation with OCSP. Signed-off-by: Hans Leidekker Signed-off-by: Alexandre Julliard --- dlls/cryptnet/Makefile.in | 2 +- dlls/cryptnet/cryptnet_main.c | 391 ++++++++++++++++++++++++++++++++-- include/wincrypt.h | 4 + 3 files changed, 375 insertions(+), 22 deletions(-) diff --git a/dlls/cryptnet/Makefile.in b/dlls/cryptnet/Makefile.in index 8b13861da1e..af41122193f 100644 --- a/dlls/cryptnet/Makefile.in +++ b/dlls/cryptnet/Makefile.in @@ -1,6 +1,6 @@ MODULE = cryptnet.dll IMPORTLIB = cryptnet -IMPORTS = crypt32 shell32 ole32 +IMPORTS = crypt32 shell32 ole32 advapi32 DELAYIMPORTS = wininet C_SRCS = \ diff --git a/dlls/cryptnet/cryptnet_main.c b/dlls/cryptnet/cryptnet_main.c index 8aec76e9b81..6654ef77c8c 100644 --- a/dlls/cryptnet/cryptnet_main.c +++ b/dlls/cryptnet/cryptnet_main.c @@ -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; } diff --git a/include/wincrypt.h b/include/wincrypt.h index f5f05c1f8d0..2c1e3f0d4c3 100644 --- a/include/wincrypt.h +++ b/include/wincrypt.h @@ -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;