WinInet:HTTPS 请求出现无效的证书颁发机构的处理
? ? ?首先,微軟提供的WinInet庫封裝了對網頁訪問的方法。
? ? ?最近工作需要從https服務器獲取數據,都知道https和http網頁的訪問方式不同,多了一道證書認證程序,這樣就使得https在請求起來比http要復雜的多;好在,WinInet庫中提供了對https網頁請求的處理,這樣就不需要在使用openssl中的一些方法來復雜化程序了。
? ? ?下面貼上我的解決前的代碼,再對比我遇到問題之后的代碼,在通過實際遇到的問題和環境來闡述:
解決前代碼:
#include <cstdio> #include <cstdlib> #include <iostream> #include <fstream> #include "Windows.h" #include "wininet.h" using namespace std; //鏈接需要 wininet.lib #pragma comment(lib,"wininet.lib") int main(int argc, char* argv[]) {LPCTSTR lpszAgent = "WinInetGet/0.1";//初始化HINTERNET hInternet = InternetOpen(lpszAgent,INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);LPCTSTR lpszServerName = "data.btcchina.com";//"ssl.google-analytics.com"; //設置serverINTERNET_PORT nServerPort = INTERNET_DEFAULT_HTTPS_PORT; // HTTPS端口443LPCTSTR lpszUserName = NULL; //無登錄用戶名LPCTSTR lpszPassword = NULL; //無登錄密碼DWORD dwConnectFlags = 0;DWORD dwConnectContext = 0;//連接HINTERNET hConnect = InternetConnect(hInternet,lpszServerName, nServerPort,lpszUserName, lpszPassword,INTERNET_SERVICE_HTTP,dwConnectFlags, dwConnectContext);//使用GetLPCTSTR lpszVerb = "GET";LPCTSTR lpszObjectName = "/data/ticker";LPCTSTR lpszVersion = NULL; // 默認.LPCTSTR lpszReferrer = NULL; // 沒有引用頁LPCTSTR *lplpszAcceptTypes = NULL; // Accpet所有類型.DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |INTERNET_FLAG_KEEP_CONNECTION |INTERNET_FLAG_NO_AUTH |INTERNET_FLAG_NO_COOKIES |INTERNET_FLAG_NO_UI |//設置啟用HTTPSINTERNET_FLAG_SECURE |INTERNET_FLAG_RELOAD;DWORD dwOpenRequestContext = 0;//初始化RequestHINTERNET hRequest = HttpOpenRequest(hConnect, lpszVerb, lpszObjectName, lpszVersion,lpszReferrer, lplpszAcceptTypes,dwOpenRequestFlags, dwOpenRequestContext);//發送Request HttpSendRequest(hRequest, NULL, 0, NULL, 0);//獲得HTTP Response Header信息DWORD dwInfoLevel = HTTP_QUERY_RAW_HEADERS_CRLF;DWORD dwInfoBufferLength = 2048;BYTE *pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2);while(!HttpQueryInfo(hRequest, dwInfoLevel, pInfoBuffer, &dwInfoBufferLength, NULL)) {DWORD dwError = GetLastError();if(dwError == ERROR_INSUFFICIENT_BUFFER) {free(pInfoBuffer);pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2);} else {fprintf(stderr, "HttpQueryInfo failed, error = %d (0x%x)/n",GetLastError(), GetLastError());break;}}pInfoBuffer[dwInfoBufferLength] = '/0';pInfoBuffer[dwInfoBufferLength + 1] = '/0';printf("%S", pInfoBuffer); //很奇怪HttpQueryInfo保存的格式是wchar_t 和下面的InternetReadFile不一樣 free(pInfoBuffer);//HTTP Response 的 Body, 需要的內容就在里面 DWORD dwBytesAvailable;while(InternetQueryDataAvailable(hRequest, &dwBytesAvailable, 0, 0)) {BYTE *pMessageBody = (BYTE *)malloc(dwBytesAvailable + 1);DWORD dwBytesRead;BOOL bResult = InternetReadFile(hRequest, pMessageBody,dwBytesAvailable, &dwBytesRead);if(!bResult) {fprintf(stderr, "InternetReadFile failed, error = %d (0x%x)/n",GetLastError(), GetLastError());break;}if(dwBytesRead == 0)break; // End of File.pMessageBody[dwBytesRead] = '/0';printf("%s", pMessageBody); //InternetReadFile讀出來的是普通的char. InternetReadFileEx 似乎是有寬字節版本的 ofstream out("ofs.txt");std::string s = (char *)pMessageBody;out << s.c_str()<< endl;free(pMessageBody);}getchar(); }
解決后代碼:
#include <cstdio> #include <cstdlib> #include <iostream> #include <fstream> #include "Windows.h" #include "wininet.h"using namespace std; //鏈接需要 wininet.lib #pragma comment(lib,"wininet.lib")int main(int argc, char* argv[]) {LPCTSTR lpszAgent = "WinInetGet/0.1";//初始化HINTERNET hInternet = InternetOpen(lpszAgent,INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);LPCTSTR lpszServerName = "data.btcchina.com";//"ssl.google-analytics.com"; //設置serverINTERNET_PORT nServerPort = INTERNET_DEFAULT_HTTPS_PORT; // HTTPS端口443LPCTSTR lpszUserName = NULL; //無登錄用戶名LPCTSTR lpszPassword = NULL; //無登錄密碼DWORD dwConnectFlags = 0;DWORD dwConnectContext = 0;//連接HINTERNET hConnect = InternetConnect(hInternet,lpszServerName, nServerPort,lpszUserName, lpszPassword,INTERNET_SERVICE_HTTP,dwConnectFlags, dwConnectContext);//使用GetLPCTSTR lpszVerb = "GET";LPCTSTR lpszObjectName = "/data/ticker";LPCTSTR lpszVersion = NULL; // 默認.LPCTSTR lpszReferrer = NULL; // 沒有引用頁LPCTSTR *lplpszAcceptTypes = NULL; // Accpet所有類型.DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |INTERNET_FLAG_KEEP_CONNECTION |INTERNET_FLAG_NO_AUTH |INTERNET_FLAG_NO_COOKIES |INTERNET_FLAG_NO_UI |//設置啟用HTTPSINTERNET_FLAG_SECURE |INTERNET_FLAG_RELOAD;DWORD dwOpenRequestContext = 0;//初始化RequestHINTERNET hRequest = HttpOpenRequest(hConnect, lpszVerb, lpszObjectName, lpszVersion,lpszReferrer, lplpszAcceptTypes,dwOpenRequestFlags, dwOpenRequestContext);//發送Request again:DWORD dwError = 0;if (!HttpSendRequest(hRequest, NULL, 0, NULL, 0)){dwError = GetLastError();}if (dwError == ERROR_INTERNET_INVALID_CA){fprintf(stderr, "HttpSendRequest failed, error = %d (0x%x)/n",dwError, dwError );DWORD dwFlags;DWORD dwBuffLen = sizeof(dwFlags);InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS,(LPVOID)&dwFlags, &dwBuffLen);dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;InternetSetOption (hRequest, INTERNET_OPTION_SECURITY_FLAGS,&dwFlags, sizeof(dwFlags));goto again;}//獲得HTTP Response Header信息DWORD dwInfoLevel = HTTP_QUERY_RAW_HEADERS_CRLF;DWORD dwInfoBufferLength = 2048;BYTE *pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2);while(!HttpQueryInfo(hRequest, dwInfoLevel, pInfoBuffer, &dwInfoBufferLength, NULL)) {DWORD dwError = GetLastError();if(dwError == ERROR_INSUFFICIENT_BUFFER) {free(pInfoBuffer);pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2);} else {fprintf(stderr, "HttpQueryInfo failed, error = %d (0x%x)/n",GetLastError(), GetLastError());break;}}pInfoBuffer[dwInfoBufferLength] = '/0';pInfoBuffer[dwInfoBufferLength + 1] = '/0';printf("%S", pInfoBuffer); //很奇怪HttpQueryInfo保存的格式是wchar_t 和下面的InternetReadFile不一樣 free(pInfoBuffer);//HTTP Response 的 Body, 需要的內容就在里面 DWORD dwBytesAvailable;while(InternetQueryDataAvailable(hRequest, &dwBytesAvailable, 0, 0)) {BYTE *pMessageBody = (BYTE *)malloc(dwBytesAvailable + 1);DWORD dwBytesRead;BOOL bResult = InternetReadFile(hRequest, pMessageBody,dwBytesAvailable, &dwBytesRead);if(!bResult) {fprintf(stderr, "InternetReadFile failed, error = %d (0x%x)/n",GetLastError(), GetLastError());break;}if(dwBytesRead == 0)break; // End of File.pMessageBody[dwBytesRead] = '/0';printf("%s", pMessageBody); //InternetReadFile讀出來的是普通的char. InternetReadFileEx 似乎是有寬字節版本的 ofstream out("ofs.txt");std::string s = (char *)pMessageBody;out << s.c_str()<< endl;free(pMessageBody);}getchar(); }?
? ? 大家看到HttpOpenRequest這個函數中,dwOpenRequestFlag參數:
DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |INTERNET_FLAG_KEEP_CONNECTION |INTERNET_FLAG_NO_AUTH |INTERNET_FLAG_NO_COOKIES |INTERNET_FLAG_NO_UI |//設置啟用HTTPSINTERNET_FLAG_SECURE |INTERNET_FLAG_RELOAD;要request到https網頁的數據,INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP和INTERNET_FLAG_SECURE兩個選項要設置。
從上面的前后兩段代碼,大家應該能看到邏輯的變化在哪里,就在于HttpSendRequest這個函數的返回值的處理上。
? 看HttpSendRequest這層邏輯的處理,你會好奇為什么要用到這樣的邏輯呢?答案其實并不是那么好告訴你的,因為,這樣設置邏輯是因為微軟當時在設計這個庫的時候留下的一個漏洞。
因為,https協議涉及到證書認證問題,而IE低版本內核的瀏覽器打開你要請求的https Url的時候,會出現證書認證失敗,(比如我這里的:btc.china.com/data/ticker),而高級版本的瀏覽器可能就不會有任何問題。
在解決問題前,我的環境是Win7系統,IE10瀏覽器,在我運行程序的時候一切正常,能正常獲取到程序,瀏覽器也能打開網頁看到網頁上的數據,但是當我把程序發布release然后交給運維測試的時候,他那邊環境是(win server 2003, IE7環境),這就出現了問題,他那邊獲取不到那個請求https網站的數據,于是我建議他們按照步驟通過瀏覽器端安裝該網站的認證證書,安裝之后瀏覽器可以看到數據,但是運行程序并不能正常獲得數據,這就是我的問題所在。
于是,就問Google大嬸們,無果,所以只有解鈴還須系鈴人了,遂到微軟的問題解決網站尋求幫助,結果,查出來這是微軟設計的一個缺陷,但是他們給出了很好的解決辦法,那就是忽略證書認證。
微軟解決辦法:http://support.microsoft.com/kb/182888/zh-cn考慮到有的時候,有些人會打不開微軟的這個網站,我在這里把他復制粘貼出來,如下:
客戶端不知道有關頒發服務器證書的證書頒發機構時,就會發生此錯誤。通過安裝證書頒發機構的根證書,問題可能得到解決。可以從 Internet Explorer 查看所有已安裝的證書列表。從視圖菜單上,單擊 Internet 選項,單擊內容選項卡,單擊機構。很可能繞過此 WinInet 應用程序中的錯誤,而不安裝證書。有兩種方法來處理該錯誤。您可以使用類似于以下示例的代碼。方法 1。與用戶界面 (生成類似于 Internet Explorer 的消息框):...Again:if (!HttpSendRequest (hReq,...))dwError = GetLastError ();if (dwError == ERROR_INTERNET_INVALID_CA){// Make sure to check return code from InternetErrorDlg// user may click either OK or Cancel. In case of Cancel// request should not be resumbitted. InternetErrorDlg (GetDesktopWindow(),hReq,ERROR_INTERNET_INVALID_CA,FLAGS_ERROR_UI_FILTER_FOR_ERRORS |FLAGS_ERROR_UI_FLAGS_GENERATE_DATA |FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS,NULL);goto again;}...方法 2。而無需用戶界面:...Again:if (!HttpSendRequest (hReq,...))dwError = GetLastError ();if (dwError == ERROR_INTERNET_INVALID_CA){DWORD dwFlags;DWORD dwBuffLen = sizeof(dwFlags);InternetQueryOption (hReq, INTERNET_OPTION_SECURITY_FLAGS,(LPVOID)&dwFlags, &dwBuffLen);dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS,&dwFlags, sizeof (dwFlags) );goto again;}...與 MFC WinInet 類可以使用相似的邏輯。在這種情況下,下列 MFC 方法對應于 WinInet 上面使用的 Api:CInternetFile::SendRequest CInternetFile::QueryOption CInternetFile::SetOption CInternetFile::ErrorDlg 請注意缺少 Visual C++ 5.0 CInternetFile::ErrorDlg,CInternetFile::QueryOption 和 CInternetFile::SetOption 上的文檔。請參閱 Inet.cpp MFC 源代碼文件的信息如何使用此方法。注 1: InternetErrorDlg 可能會返回下列值:ERROR_SUCCESSERROR_CANCELLEDERROR_INTERNET_FORCE_RETRY.僅當返回 ERROR_INTERNET_FORCE_RETRY 時,才應重新提交請求。在 Internet Explorer 4.0 和 4.01 中,但是,該請求必須重新提交即使 ERROR_SUCCESS 將返回。Microsoft 已經確認這是 InternetErrorDlg API 中的問題。注 2: SECURITY_FLAG_IGNORE_UNKNOWN_CA 在 Internet Explorer 3.0 和 3.02 未實現。InternetErrorDlg 仍然起作用,但有以下例外。此 api 生成對話框中不允許忽略無效的證書頒發機構的錯誤 ;它是只是通知頁該用戶不能查看。注 3: 在錯誤發生之前,不能設置選項,將忽略此錯誤。您首先必須嘗試發送請求、 收到錯誤消息,然后設置選項 (或調用 InternetErrorDlg),然后重新提交。 View Code?
我用的是提供的第二個方法無用戶界面的解決方法。然后這樣大家應該就會明白我那里的處理邏輯為啥會那個樣子了。
好了,問題就是這個樣子了,我的問題解決了,你的呢?
?
轉載于:https://www.cnblogs.com/foundwant/p/3453963.html
總結
以上是生活随笔為你收集整理的WinInet:HTTPS 请求出现无效的证书颁发机构的处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [VC]strcpy memcpy me
- 下一篇: BZOJ-2463 谁能赢呢?