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:
parent
3b85129602
commit
60e10a0571
|
@ -1,6 +1,6 @@
|
|||
MODULE = cryptnet.dll
|
||||
IMPORTLIB = cryptnet
|
||||
IMPORTS = crypt32 shell32 ole32
|
||||
IMPORTS = crypt32 shell32 ole32 advapi32
|
||||
DELAYIMPORTS = wininet
|
||||
|
||||
C_SRCS = \
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue