I need to send a client certificate with a web request (via SSL). This client cert is just a public key. I am trying to replicate the Request.ClientCertificates.Add(Cert); .NET method using C++/WinHTTP. I am loading the .cer file successfully and setting the CERT_CONTEXT via WinHttpSetOption/WINHTTP_OPTION_CLIENT_CERT_CONTEXT. This call succeeds, but when I call WinHttpSendRequest, it fails with ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY (12185).
So, the question is, how do I send a client cert public key to the server, as the ClientCertificates.Add method does in .NET? Code snippet sample below:
BOOL HTTPCallEx::SendHTTPRequest(int iVerb , LPCTSTR cpUID , LPCTSTR cpPWD )
{
WCHAR wcaVerb[16];
WCHAR wcaResource[1024];
m_dwLastError = 0;
switch (iVerb)
{
case HTTPCALL_POST:
lstrcpyW(wcaVerb,L"POST");
break;
case HTTPCALL_HEAD:
lstrcpyW(wcaVerb,L"HEAD");
break;
case HTTPCALL_PUT:
lstrcpyW(wcaVerb,L"PUT");
break;
case HTTPCALL_DELETE:
lstrcpyW(wcaVerb,L"DELETE");
break;
case HTTPCALL_OPTIONS:
lstrcpyW(wcaVerb,L"OPTIONS");
break;
case HTTPCALL_TRACE:
lstrcpyW(wcaVerb,L"TRACE");
break;
case HTTPCALL_CONNECT:
lstrcpyW(wcaVerb,L"CONNECT");
break;
case HTTPCALL_GET:
default:
lstrcpyW(wcaVerb,L"GET");
break;
}
#ifdef UNICODE
_tcscpy(wcaResource,m_caResource);
#else
MultiByteToWideChar(CP_UTF8,0,m_caResource,-1,wcaResource,1024);
#endif
m_hRequest = WinHttpOpenRequest(m_hConnect,wcaVerb,wcaResource,NULL,WINHTTP_NO_REFERER,WINHTTP_DEFAULT_ACCEPT_TYPES,(m_bSSL ? WINHTTP_FLAG_SECURE : 0));
if (!m_hRequest)
{
m_dwLastError = ::GetLastError();
return FALSE;
}
if (cpUID && *cpUID)
{
WCHAR wcaUID[512];
WCHAR wcaPWD[512];
#ifdef UNICODE
_tcscpy(wcaUID,cpUID);
#else
MultiByteToWideChar(CP_UTF8,0,cpUID,-1,wcaUID,512);
#endif
if (cpPWD && *cpPWD)
#ifdef UNICODE
_tcscpy(wcaPWD,cpPWD);
#else
MultiByteToWideChar(CP_UTF8,0,cpPWD,-1,wcaPWD,512);
#endif
else
wcaPWD[0] = 0;
if (!WinHttpSetCredentials(m_hRequest,
WINHTTP_AUTH_TARGET_SERVER,
WINHTTP_AUTH_SCHEME_BASIC,
wcaUID,
wcaPWD,
NULL))
{
m_dwLastError = ::GetLastError();
return FALSE;
}
}
if (m_dwRequestTimeout)
{
if (!WinHttpSetOption(m_hRequest,WINHTTP_OPTION_RECEIVE_TIMEOUT,&m_dwRequestTimeout,sizeof(m_dwRequestTimeout)))
{
m_dwLastError = ::GetLastError();
return FALSE;
}
}
if (m_pCertCtxt)
{
DWORD dwFlags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
WinHttpSetOption(m_hRequest,WINHTTP_OPTION_SECURITY_FLAGS,&dwFlags,sizeof(dwFlags));
if (!WinHttpSetOption(m_hRequest,WINHTTP_OPTION_CLIENT_CERT_CONTEXT,(void *)m_pCertCtxt,sizeof(CERT_CONTEXT)))
{
if (m_pCertCtxt)
m_dwLastError = ::GetLastError();
else
m_dwLastError = 50000;
return FALSE;
}
}
if (m_oCustomHeaders.GetSize() > 0)
{
CString cHeader;
WCHAR wcaHeaderBuf[2048];
for (int iLup = 0; iLup < m_oCustomHeaders.GetSize(); iLup++)
{
cHeader = m_oCustomHeaders.GetAt(iLup);
#ifdef UNICODE
_tcscpy(wcaHeaderBuf,(LPCTSTR)cHeader);
#else
MultiByteToWideChar(CP_UTF8,0,(LPCSTR)cHeader,-1,wcaHeaderBuf,2048);
#endif
WinHttpAddRequestHeaders(m_hRequest,wcaHeaderBuf,lstrlenW(wcaHeaderBuf),WINHTTP_ADDREQ_FLAG_ADD);
}
}
DWORD dwContentLength = 0;
if ((iVerb == HTTPCALL_POST || iVerb == HTTPCALL_PUT) && m_cpPostData)
{
if (m_iPostDataLen < 0)
dwContentLength = (DWORD)strlen(m_cpPostData);
else
dwContentLength = (DWORD)m_iPostDataLen;
}
if (!WinHttpSendRequest(m_hRequest,WINHTTP_NO_ADDITIONAL_HEADERS,NULL,(LPVOID)(m_cpPostData ? m_cpPostData : ""),dwContentLength,dwContentLength,0))
{
>>>>> THIS FAILS HERE WITH ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY (12185)
m_dwLastError = ::GetLastError();
return FALSE;
}
if (!WinHttpReceiveResponse(m_hRequest,NULL))
{
m_dwLastError = ::GetLastError();
return FALSE;
}
TCHAR caBuf[81];
caBuf[0] = 0;
int iBufSize = sizeof(caBuf)/sizeof(TCHAR);
if (!GetStandardHeader(WINHTTP_QUERY_STATUS_CODE,caBuf,&iBufSize))
{
m_dwLastError = ::GetLastError();
return FALSE;
}
m_dwHTTPStatus = _ttol(caBuf);
caBuf[0] = 0;
iBufSize = sizeof(caBuf)/sizeof(TCHAR);
if (!GetStandardHeader(WINHTTP_QUERY_CONTENT_LENGTH,caBuf,&iBufSize)) m_dwContentLength = 0;
else
m_dwContentLength = _ttol(caBuf);
return TRUE;
}
As usual, this is on a deadline, so any help is greatly appreciated! Certificate loading is shown below:
BOOL LoadCertificate(ApplicationInstance *pAppInst)
{
BOOL bRetval = FALSE;
int iThreadCount = (int)pAppInst->m_pLightningServer->m_wNumWorkerThreads;
TCHAR caCertFilePath[256];
caCertFilePath[0] = 0;
if (!pAppInst->GetUserTagValue(_T("CertFilePath"),caCertFilePath,sizeof(caCertFilePath)/sizeof(TCHAR)))
_tcscpy(caCertFilePath,_T("c:\\webapps\\test.cer"));
theApp.m_hStore = CertOpenStore(CERT_STORE_PROV_FILENAME,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
NULL,
CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG,
caCertFilePath);
if (theApp.m_hStore)
{
PCCERT_CONTEXT *pCertList = new (PCCERT_CONTEXT[iThreadCount]);
if (pCertList)
{
for (int iCert = 0; iCert < iThreadCount; iCert++)
pCertList[iCert] = NULL;
pAppInst->SetUserPtr((void *)pCertList);
PCCERT_CONTEXT pCertCtxt = CertFindCertificateInStore(theApp.m_hStore,
X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR,
(LPVOID)_T("test.myserver.com"), NULL );
if (pCertCtxt)
{
pCertList[0] = pCertCtxt;
for (int iLup = 1; iLup < iThreadCount; iLup++)
pCertList[iLup] = CertDuplicateCertificateContext(pCertCtxt);
bRetval = TRUE;
}
else
{
pAppInst->m_pLightningServer->WriteErrorLog(-3,_T("APPINIT: Error Getting CERT_CONTEXT From Store"),caCertFilePath,NULL,FALSE);
}
}
else
{
pAppInst->m_pLightningServer->WriteErrorLog(-2,_T("APPINIT: Error MemAlloc CERT_CONTEXT Array"),NULL,NULL,FALSE);
}
}
else
{
DWORD dwError = GetLastError();
TCHAR caErrBuf[1024];
_stprintf(caErrBuf,_T("APPINIT: Error Opening Cert Store [%d]..."),dwError);
::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
caErrBuf+_tcslen(caErrBuf),
sizeof(caErrBuf)/sizeof(TCHAR)-40,
NULL);
pAppInst->m_pLightningServer->WriteErrorLog(-1,caErrBuf,caCertFilePath,NULL,FALSE);
}
return bRetval;
}
onwards and upwards...
|