Signed-off-by: Hans Leidekker <hans(a)codeweavers.com>
---
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;
--
2.30.2