Search code examples
c++cwinapisslwinhttp

How do you use WinHTTP to do SSL with a self signed cert


I seem to be having issues with this, and in the spirit of having a generic question that can be referenced by others, I am looking for a good example of using SSL.

More specifically, I am getting the error 0x00002F8F from WinHttpSendRequest, which is ERROR_INTERNET_DECODING_FAILED (which indicates to me that its a cert error). I have imported the cert on this machine, and am able to pull up the page in IE without a cert error.

TLDR: How do you use WinHTTP with a self signed cert?

// 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");
}

Solution

  • For WinHTTP, in order to accept/allow SSL validation failures, you must first make the request and allow it to fail, then disable the security checks and retry the operation on the request handle. Something along the lines of:

    // 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);