我似乎对此有疑问,本着提出一个可供其他人参考的通用问题的精神,我正在寻找一个使用 SSL 的好例子。
更具体地说,我从 WinHttpSendRequest 收到错误 0x00002F8F,这是 ERROR_INTERNET_DECODING_FAILED (这表明它是一个证书错误)。我已经在这台机器上导入了证书,并且能够在 IE 中拉出页面,而不会出现证书错误。
TLDR:如何将 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");
}
对于 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);
如果您碰巧使用 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
请注意,可以在 SendRequest 之前在 CHttpFile 上设置安全标志。您不必等到错误/异常发生。