Introduction
This is a WTL program that is able to test connections with web servers using just about every possible combination of requirements that are commonly encountered. The program is able to connect:
- POST or GET
- SSL
- supply personal certificate, if required by the web server
- vary receive time out
- supply username/password for web server
- ignore bad CA certificates
The program uses asynchronous WinINet calls to handle all communication and reporting. The program also demonstrates how to allow the user to select a personal certificate from the My store.
Background
I put this program together to help test some issues I encountered with an unusual firewall configuration and web server security requirements. The output from the WinINet call back routine is very helpful when debugging connection issues. I have placed a number of messages in the ATLTRACE macro output as well as the main dialog of the program. While example code for most of the operations can be found in a variety of locations, I needed to tie everything together into a single application, implemented with consistent error handling.
The program started as an amalgamation of several samples from Microsoft. After a few hours of coding and a quick port to WTL, the application was pretty well gutted and rewritten. I need to give credit where credit is due for this application: the Microsoft examples saved me a lot of time (except when they were wrong or incomplete and cost me a lot of time).
Using the Code
The code is very simple to use. Simply download the demonstration and compile it. I am using WTL 8.0 with VS 2005. You may need to download WTL.
This is a WTL modal dialog application. I used some wait functions developed by Andy Smith to get around blocking issues when a modal dialog is utilized. You'll really want to examine the main WinINet loop, located in SendRequest
. Here, you will learn how to handle ERROR_INTERNET_INVALID_CA
, ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED
, and HTTP_STATUS_DENIED
. The WinInetCallback
method is utilized to supply information like personal certificates or user name and password for resubmission to the web server and pass communication errors to the WinINet loop in SendRequest
via the context variable.
Obtaining a Pointer to a Personal Certificate
There are times when you may need to allow a user to select a personal certificate. For instance, you are writing an application that uses WinINet. Here is some simple code that allows a user to select from a list of personal certificates loaded in their certificate store:
#include <prsht.h>
#include <cryptuiapi.h>
...
HCERTSTORE m_hMyStore;
PCCERT_CONTEXT m_pHSCertContext;
...
if(!(m_hMyStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
NULL,
CERT_SYSTEM_STORE_CURRENT_USER,
L"MY")))
{
CString tMsg, tTitle;
GetErrorMessage(GetLastError(), &tMsg);
MessageBox((LPCTSTR) tMsg,
(LPCTSTR) tTitle.LoadString(IDS_TITLEFAILED),
MB_ICONEXCLAMATION);
return FALSE;
}
else
{
m_pHSCertContext = CryptUIDlgSelectCertificateFromStore(
m_hMyStore,
NULL,
NULL,
NULL,
CRYPTUI_SELECT_EXPIRATION_COLUMN |
CRYPTUI_SELECT_LOCATION_COLUMN |
CRYPTUI_SELECT_FRIENDLYNAME_COLUMN |
CRYPTUI_SELECT_INTENDEDUSE_COLUMN ,
0,
NULL);
}
if (!m_pHSCertContext)
{
CString tMsg, tTitle;
MessageBox((LPCTSTR) tMsg.LoadString(IDS_ERR1),
(LPCTSTR) tTitle.LoadString(IDS_ERRT1),
MB_ICONEXCLAMATION);
return FALSE;
}
Supplying a Personal Certificate on a WinINet Handle
TCP/IP communications are a little tricky, in that you cannot supply the certificate when you make the request. You need to call OpenRequest
, and then wait for the INTERNET_STATUS_REQUEST_COMPLETE
signal in your Callback routine. You can then check the returned error, looking for ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED
in the INTERNET_ASYNC_RESULT
struct. When you get the error, you need to use InternetSetOption
on your OpenRequest
handle to supply the certificate and then repeat the OpenRequest
. It's really pretty simple. It's just that the docs and samples leave out a lot of the error checking and recovery work that you need. It helps to have a server setup with a valid SSL certificate and require personal certificates in order to connect. Here is a code fragment for handling ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED
:
case INTERNET_STATUS_REQUEST_COMPLETE:
{
if(ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED == pRes->dwError)
{
cpMainDlg->m_dwError = pRes->dwError;
sMsg.Format("%s: CLIENT_AUTH_CERT_NEEDED (%d)",
cpMainDlg->m_szMemo, pRes->dwError);
if(cpMainDlg->m_pHSCertContext == NULL)
{
sMsg += _T(" No cert provided by user");
}
else
{
if (!::InternetSetOption( cpMainDlg->hRequest,
INTERNET_OPTION_CLIENT_CERT_CONTEXT ,
(LPVOID) cpMainDlg->m_pHSCertContext,
sizeof(CERT_CONTEXT) ))
{
CString sErrMsg;
cpMainDlg->GetErrorMessage(GetLastError(), &sErrMsg);
sMsg += _T(" ");
sMsg += sErrMsg;
}
}
}
...
}
Future Plans
I will add authentication handling for proxy servers in a future revision. Currently, only one set of credentials may be supplied, but the program needs to allow for two sets of credentials, one for the proxy server and one for the web server. I also want to add a file attachment dialog for testing the POST method with binary data or pseudo form data attached.
History
- Version 1.0 - First release.