如何使用WinHTTP与自签名证书进行SSL连接

13
我似乎在这方面遇到了问题,为了让其他人也能参考,我正在寻找一个使用SSL的好例子。
更具体地说,我从WinHttpSendRequest收到错误代码0x00002F8F,这是ERROR_INTERNET_DECODING_FAILED(对我来说表示证书错误)。我已经在这台机器上导入了证书,并且能够在IE中打开页面而没有证书错误。
简而言之:如何使用自签名证书与WinHTTP一起使用?
// pochttpclient.cpp : Defines the entry point for the console application.
//
 
#include "stdafx.h"
#include "windows.h"
#include "winhttp.h"
#include "wchar.h"
 
 
 
// SSL (Secure Sockets Layer) example
// compile for console
 
void main()
{
    HINTERNET hOpen = 0;
    HINTERNET hConnect = 0;
    HINTERNET hRequest = 0;
    IStream *stream = NULL;
    HRESULT hr;
 
    while (1)
    {
        hOpen = WinHttpOpen(L"Aurora Console App", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
 
        if (!hOpen) {
            wprintf(L"WinHttpOpen failed (0x%.8X)\n", GetLastError());
            break;
        }
 
        hConnect = WinHttpConnect(hOpen, L"www.codeproject.com", INTERNET_DEFAULT_HTTPS_PORT, 0);
 
        if (!hConnect) {
            wprintf(L"WinHttpConnect failed (0x%.8X)\n", GetLastError());
            break;
        }
 
        LPCWSTR types[2];
        types[0] = L"text/html";
        types[1] = 0;
 
        // use flag WINHTTP_FLAG_SECURE to initiate SSL
        hRequest = WinHttpOpenRequest(hConnect, L"GET", L"KB/IP/NagTPortScanner.aspx",
                NULL, WINHTTP_NO_REFERER, types, WINHTTP_FLAG_SECURE);
 
        if (!hRequest)
        {
            wprintf(L"WinHttpOpenRequest failed (0x%.8X)\n", GetLastError());
            break;
        }
 
        if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
        {
            wprintf(L"WinHttpSendRequest failed (0x%.8X)\n", GetLastError());
            break;
        }
        if (!WinHttpReceiveResponse(hRequest, 0))
        {
            wprintf(L"WinHttpReceiveResponse failed (0x%.8X)\n", GetLastError());
            break;
        }
        // query remote file size, set haveContentLength on success and dwContentLength to the length
        wchar_t szContentLength[32];
        DWORD cch = 64;
        DWORD dwHeaderIndex = WINHTTP_NO_HEADER_INDEX;
 
        BOOL haveContentLength = WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH, NULL,
                &szContentLength, &cch, &dwHeaderIndex);
 
        DWORD dwContentLength;
        if (haveContentLength) dwContentLength = _wtoi(szContentLength);
 
        // read the response into memory stream
        hr = CreateStreamOnHGlobal(0, true, &stream);
        if (hr) {
            wprintf(L"CreateStreamOnHGlobal failed (0x%.8X)\n", hr);
            break;
        }
        // allocate buffer for streaming received data
        unsigned char* p = new unsigned char[4096];
        if (!p)
        {
            wprintf(L"failed to allocate buffer\n");
            break;
        }
        // to receive all data, we need to enter a loop
        DWORD dwReceivedTotal = 0;
        while (WinHttpQueryDataAvailable(hRequest, &cch) && cch)
        {
            if (cch > 4096) cch = 4096;
            dwReceivedTotal += cch;
 
            // display number of received bytes
            if (haveContentLength)
            {
                wprintf(L"received %d of %d (%d%%)%c", dwReceivedTotal, dwContentLength, 
                    dwReceivedTotal*100/dwContentLength, 13);
            }
            else {
                wprintf(L"received %d (unknown length)%c", dwReceivedTotal, 10);
            }
 
            WinHttpReadData(hRequest, p, cch, &cch);
            // write into stream
            hr = stream->Write(p, cch, NULL);
            if (hr)
            {
                wprintf(L"failed to write data to stream (0x%.8X)\n", hr);
            }
        }
 
        delete p;
        wprintf(L"\n\nreceived all data.\n");
        // terminate the sream with a NULL
        p = NULL;
        stream->Write(&p, 1, NULL);
        // get pointer to stream bytes
        wprintf(L"getting HGLOBAL from stream...\n");
        HGLOBAL hgl;
        hr = GetHGlobalFromStream(stream, &hgl);
        if (hr) {
            wprintf(L"GetHGlobalFromStream failed (0x%.8X)\n", hr);
            break;
        }
        wprintf(L"locking memory...\n");
        p = (unsigned char*)GlobalLock(hgl);
        if (!p)
        {
            wprintf(L"GlobalLock failed (0x%.8X)\n", GetLastError());
            break;
        }
        wprintf(L"displaying received data...\n");
        // terminate the string at 1024 bytes (MessageBox lag)
        //if (dwReceivedTotal > 1024) dwReceivedTotal = 1024;
        //*p[dwReceivedTotal] = 0;
 
        MessageBox(0, (LPCWSTR)p, L"", 0);
        GlobalUnlock(hgl);
 
        break;
    }
    // delete stream and close handles
    if (stream) stream->Release();
    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hOpen) WinHttpCloseHandle(hOpen);
    system("pause");
}
2个回答

14

对于WinHTTP,如果想接受/允许SSL验证失败,首先必须发起请求并允许其失败,然后禁用安全检查并在请求句柄上重新尝试操作。 大致的代码如下:

// Certain circumstances dictate that we may need to loop on WinHttpSendRequest
// hence the do/while
do
{
    retry = false;
    result = NO_ERROR;

    // no retry on success, possible retry on failure
    if(WinHttpSendRequest(
        mHRequest,
        WINHTTP_NO_ADDITIONAL_HEADERS,
        0,
        optionalData,
        optionalLength,
        totalLength,
        NULL
        ) == FALSE)
    {
        result = GetLastError();

        // (1) If you want to allow SSL certificate errors and continue
        // with the connection, you must allow and initial failure and then
        // reset the security flags. From: "HOWTO: Handle Invalid Certificate
        // Authority Error with WinInet"
        // http://support.microsoft.com/default.aspx?scid=kb;EN-US;182888
        if(result == ERROR_WINHTTP_SECURE_FAILURE)
        {
            DWORD dwFlags =
                SECURITY_FLAG_IGNORE_UNKNOWN_CA |
                SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
                SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
                SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;

            if(WinHttpSetOption(
                mHRequest,
                WINHTTP_OPTION_SECURITY_FLAGS,
                &dwFlags,
                sizeof(dwFlags)))
            {
                retry = true;
            }
        }
        // (2) Negotiate authorization handshakes may return this error
        // and require multiple attempts
        // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383144%28v=vs.85%29.aspx
        else if(result == ERROR_WINHTTP_RESEND_REQUEST)
        {
            retry = true;
        }
    }
} while(retry);

4
我刚刚遇到了这个问题,不愿意相信这种愚蠢的重复是必要的。而事实上,它是可能的:1.使用WinHttpOpen获取会话句柄。2.使用WinHttpConnect获取连接句柄。3.使用WinHttpOpenRequest获取请求句柄(使用WINHTTP_FLAG_SECURE打开TLS,这不是WINHTTP_OPTION_SECURITY_FLAGS的相同标志集)。4.在获取的请求句柄上使用如上所述的WinHttpSetOption。5.使用WinHttpSendRequest发送请求。 - gilgwath
哇,这太愚蠢了。谁认为这是可以的?我使用Boost/Beast来对REST端点进行MTLS身份验证。证书在请求之前附加到会话中,它就可以正常工作,无需重试。 - Menace
你真是救星啊!我绝对搞不定这个问题! - Sander Bouwhuis

0
如果你碰巧正在使用MFC包装类:
CHttpFile* pFile = m_pConnection->OpenRequest(nRequestVerb, strRequest, NULL, 1, NULL, NULL, INTERNET_FLAG_EXISTING_CONNECT | dwFlags);
...
DWORD dwSecurity = 0;
if (pFile->QueryOption(INTERNET_OPTION_SECURITY_FLAGS, dwSecurity))
{
    dwSecurity |= SECURITY_IGNORE_ERROR_MASK;
    pFile->SetOption(INTERNET_OPTION_SECURITY_FLAGS, dwSecurity);
}
...
pFile->SendRequest(strRequestHeaders); // throws exception without IGNORE_ERROR_MASK

请注意,在发送请求之前可以在CHttpFile上设置安全标志。您不必等到出现错误/异常才进行设置。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接