Selecting a client certificate
This article covers the basics of selecting a client certificate for use within WinInet. I have put this text together, in the hope it may prevent others suffering the hair loss I have during the last week.
First of all I am assuming you know the basics of setting up a HTTPS connection, bellow is a brief outline to refresh your memory. The INTERNET_FLAG_SECURE flag can be set within the InternetConnect command to force the use of SSL.
// Initialize wininet.
hOpen = InternetOpen (...)
// connect to remote server
hConnect = InternetConnect ( hOpen, strServerName , ....)
// Open a HTTP request
hReq = HttpOpenRequest (hConnect, "GET", strObject, ....)
// Send request
HttpSendRequest (hReq, ....)
// read results
InternetReadFile (hReq, bBuf, cbBuf, &cbRead)
// use the returned HTML page
printf(bBuf);
If the server that we are making the request to requires a client certificate then the HttpSendRequest function will fail with the error ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED. At this point we must select a certificate.
There are basically 2 ways of selecting a certificate. First (the easiest, and the way IE does it), is to ask the user. See Dialog bellow.
This can be done programmatically using the following code:-
...
while ( !HttpSendRequest( hReq, NULL, 0, NULL, 0 ) )
{
dwError = GetLastError();
if ( dwError == ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED )
{
// Return ERROR_SUCCESS regardless of clicking on OK or Cancel
if( InternetErrorDlg( GetDesktopWindow(),
hReq,
ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED,
FLAGS_ERROR_UI_FILTER_FOR_ERRORS |
FLAGS_ERROR_UI_FLAGS_GENERATE_DATA |
FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS,
NULL) != ERROR_SUCCESS )
{
return ;
}
}
}
...
See Q224282 for more details.
However if you dont want the user to see the dialog, then you will need to actually select the certificate programmatically. This sounds simple but, due to the extreme lack of documentation becomes something of a nightmare.
Anyhow it can basically be accomplished using InternetSetOptions INTERNET_OPTION_SECURITY_SELECT_CLIENT_CERT flag. This, you may notice has no documentation. The sample code for using this flag is included. But the guts of it are here:-
if (!HttpSendRequest (hReq, NULL, 0, NULL, 0) )
{
dwError = GetLastError () ;
if ( dwError == ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED)
{
//This works but there is no way to iterate through the client certs
DWORD dwCert = 0;
InternetSetOption(hReq, INTERNET_OPTION_SECURITY_SELECT_CLIENT_CERT,
&dwCert, sizeof(dwCert));
goto again;
}
}
This will select the first client certificate registered with WinInet. The bad news is if there are more than one Cert on the machine then you dont know which one you got!
As the most common reason for wanting to avoid the selection dialog is because the application is running as a service, I will provide a link to an MS article that describes how to set the registry up to cope with finding the certs when running as system. Q190542.
I will also include some code I picked up from a news group posting with regards to enumerating the client certificates installed. Although this is inrelated to the article directly, it may prove useful.
I would also like to thank Scott Davis. For his Dev Studio HTML Plugin.

Comments
Can you get a realm when doing certificate authentication?
Posted by sshields on 12/04/2007 10:23pmWhen the httpsendrequest fails with ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED, is there a way to get the realm? Or does that not apply with certificates? I want to use the realm to associate it with the certificate for future connections.
ReplyVery nice
Posted by GuOddian on 12/05/2005 01:14amExactly the information I was looking for.
ReplyProxy Authentication Required
Posted by Legacy on 01/21/2004 12:00amOriginally posted by: Shan
Not able to succesfully connect to the server through SSL. Basically I am using your code as it is. The HttpSendRequest() succeeds, but it's handle response is as below.
Reply---------------------handle response-----------------------
"+ response {0x00841951 "
Proxy Authentication Required
Unable to complete request:
Access denied due to authentication failure.
---------------------------------------------------------
Could you plese tell me as to what I could be doing wrong?
Excellent Information
Posted by Legacy on 10/21/2003 12:00amOriginally posted by: Cecilia
Thank you so much.
ReplyThey are very useful information and samples. Information that you won't find in the MSDN. It certainly saves my day.
Thanks
THANK YOU
Posted by Legacy on 04/16/2003 12:00amOriginally posted by: Sunglim Lee
You have indeed saved bunch of my hair!
Thank you very much for sharing your precious information.
ReplyCode doesn't work when implemented as DLL
Posted by Legacy on 01/30/2003 12:00amOriginally posted by: Juaritos
I included this code in a VC++ desktop app and works perfect, however, when I migrated this code to an ATL component so it can be called from an ASP page, fails when executing HttpSendRequest and never returns, so I cannot even call GetLastError() to get the error code.
I tried the same using VB6 but got the same results, ...works well as a dektop app, but HttpSendRequest never returns when the code is included in an ActiveX dll.
I also tried to do the same using c# in a web service, but .net does not have access to the private key using X509Certificate.
Any clues on why this code doesn't work when implemented as a component.
ReplyBut how do you register a cert w/WinInet?
Posted by Legacy on 11/14/2001 12:00amOriginally posted by: Ken Overton
That seems, to me, to be the crux of the issue. If we want to install a trusted root and use it, how do we do that?
-- kov
ReplyHTTP With SSL
Posted by Legacy on 09/20/2001 12:00amOriginally posted by: Venkata Reddy
Here is the Solution I got working when I use SSL with HTTP.
In InternetConnect() call use the HTTPS port instead of HTTP default Port
InternetOpen(..);
InternetConnect(..,
m_szDestination,
m_bUseSSL?ITERNET_DEFAULT_HTTPS_PORT:m_uPort, ..,
..,
INTERNET_SERVICE_HTTP,
m_bUseSSL ? INTERNET_FLAG_SECURE : 0,
0);
Then in
HttpOpenRequest(.., dwFlags) use
dwFlags = INTERNET_FLAG_SECURE | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
flags..
This would work, and when HttpSendRequest() request returns error and GetLAstError() returns ERROR_INTERNET_INVALID_CA because of the invalid certificate, show up the WinInet message box for selection.
Caution: If the ITERNET_DEFAULT_HTTPS_PORT is not used then you get ERROR_INTERNET_SECURITY_CHANNEL_ERROR.
ReplyI am unable to connect to a server through HTTPS
Posted by Legacy on 09/15/2000 12:00amOriginally posted by: Jaganathan Rajagopalan
Hope you can help. I am unable to succesfully connect to the server through SSL. Basically I am using your code as is. The HttpSendRequest() method fails, with the error code ERROR_INTERNET_SECURITY_CHANNEL_ERROR . Could you plese tell me as to what I could be doing wrong?
Replypublic/private key pair
Posted by Legacy on 01/25/2000 12:00amOriginally posted by: G. Vinod Kumar
How SSL is used with public and private keys pair.
Replyi.e I want to generate public and private key pair on the server and distribute public key to the clients for encryption. Decryption is done at the server using private key. can you please give me the solution for this.