[PATCH] cryptnet: Support verifying certificate revocation with OCSP.

Hans Leidekker hans at codeweavers.com
Wed Apr 6 08:15:32 CDT 2022


Signed-off-by: Hans Leidekker <hans at 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




More information about the wine-devel mailing list