Created
June 7, 2022 00:06
-
-
Save odzhan/1b5836c4c8b02d4d9cb9ec574432432c to your computer and use it in GitHub Desktop.
C++ SSPI Schannel TLS example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compiles with Visual Studio 2008 for Windows | |
// This C example is designed as more of a guide than a library to be plugged into an application | |
// That module required a couple of major re-writes and is available upon request | |
// The Basic example has tips to the direction you should take | |
// This will work with connections on port 587 that upgrade a plain text session to an encrypted session with STARTTLS as covered here. | |
// TLSclient.c - SSPI Schannel gmail TLS connection example | |
#define SECURITY_WIN32 | |
#define IO_BUFFER_SIZE 0x10000 | |
#define DLL_NAME TEXT("Secur32.dll") | |
#define NT4_DLL_NAME TEXT("Security.dll") | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <windows.h> | |
#include <winsock.h> | |
#include <wincrypt.h> | |
#include <wintrust.h> | |
#include <schannel.h> | |
#include <security.h> | |
#include <sspi.h> | |
#pragma comment(lib, "WSock32.Lib") | |
#pragma comment(lib, "Crypt32.Lib") | |
#pragma comment(lib, "user32.lib") | |
#pragma comment(lib, "MSVCRTD.lib") | |
// Globals. | |
BOOL fVerbose = FALSE; // FALSE; // TRUE; | |
INT iPortNumber = 465; // gmail TLS | |
LPSTR pszServerName = "smtp.gmail.com"; // DNS name of server | |
LPSTR pszUser = 0; // if specified, a certificate in "MY" store is searched for | |
DWORD dwProtocol = SP_PROT_TLS1; // SP_PROT_TLS1; // SP_PROT_PCT1; SP_PROT_SSL2; SP_PROT_SSL3; 0=default | |
ALG_ID aiKeyExch = 0; // = default; CALG_DH_EPHEM; CALG_RSA_KEYX; | |
BOOL fUseProxy = FALSE; | |
LPSTR pszProxyServer = "proxy"; | |
INT iProxyPort = 80; | |
HCERTSTORE hMyCertStore = NULL; | |
HMODULE g_hSecurity = NULL; | |
SCHANNEL_CRED SchannelCred; | |
PSecurityFunctionTable g_pSSPI; | |
/*****************************************************************************/ | |
static void DisplayWinVerifyTrustError(DWORD Status) | |
{ | |
LPSTR pszName = NULL; | |
switch(Status) | |
{ | |
case CERT_E_EXPIRED: pszName = "CERT_E_EXPIRED"; break; | |
case CERT_E_VALIDITYPERIODNESTING: pszName = "CERT_E_VALIDITYPERIODNESTING"; break; | |
case CERT_E_ROLE: pszName = "CERT_E_ROLE"; break; | |
case CERT_E_PATHLENCONST: pszName = "CERT_E_PATHLENCONST"; break; | |
case CERT_E_CRITICAL: pszName = "CERT_E_CRITICAL"; break; | |
case CERT_E_PURPOSE: pszName = "CERT_E_PURPOSE"; break; | |
case CERT_E_ISSUERCHAINING: pszName = "CERT_E_ISSUERCHAINING"; break; | |
case CERT_E_MALFORMED: pszName = "CERT_E_MALFORMED"; break; | |
case CERT_E_UNTRUSTEDROOT: pszName = "CERT_E_UNTRUSTEDROOT"; break; | |
case CERT_E_CHAINING: pszName = "CERT_E_CHAINING"; break; | |
case TRUST_E_FAIL: pszName = "TRUST_E_FAIL"; break; | |
case CERT_E_REVOKED: pszName = "CERT_E_REVOKED"; break; | |
case CERT_E_UNTRUSTEDTESTROOT: pszName = "CERT_E_UNTRUSTEDTESTROOT"; break; | |
case CERT_E_REVOCATION_FAILURE: pszName = "CERT_E_REVOCATION_FAILURE"; break; | |
case CERT_E_CN_NO_MATCH: pszName = "CERT_E_CN_NO_MATCH"; break; | |
case CERT_E_WRONG_USAGE: pszName = "CERT_E_WRONG_USAGE"; break; | |
default: pszName = "(unknown)"; break; | |
} | |
printf("Error 0x%x (%s) returned by CertVerifyCertificateChainPolicy!\n", Status, pszName); | |
} | |
/*****************************************************************************/ | |
static void DisplayWinSockError(DWORD ErrCode) | |
{ | |
LPSTR pszName = NULL; // http://www.sockets.com/err_lst1.htm#WSANO_DATA | |
switch(ErrCode) // http://msdn.microsoft.com/en-us/library/ms740668(VS.85).aspx | |
{ | |
case 10035: pszName = "WSAEWOULDBLOCK "; break; | |
case 10036: pszName = "WSAEINPROGRESS "; break; | |
case 10037: pszName = "WSAEALREADY "; break; | |
case 10038: pszName = "WSAENOTSOCK "; break; | |
case 10039: pszName = "WSAEDESTADDRREQ "; break; | |
case 10040: pszName = "WSAEMSGSIZE "; break; | |
case 10041: pszName = "WSAEPROTOTYPE "; break; | |
case 10042: pszName = "WSAENOPROTOOPT "; break; | |
case 10043: pszName = "WSAEPROTONOSUPPORT"; break; | |
case 10044: pszName = "WSAESOCKTNOSUPPORT"; break; | |
case 10045: pszName = "WSAEOPNOTSUPP "; break; | |
case 10046: pszName = "WSAEPFNOSUPPORT "; break; | |
case 10047: pszName = "WSAEAFNOSUPPORT "; break; | |
case 10048: pszName = "WSAEADDRINUSE "; break; | |
case 10049: pszName = "WSAEADDRNOTAVAIL "; break; | |
case 10050: pszName = "WSAENETDOWN "; break; | |
case 10051: pszName = "WSAENETUNREACH "; break; | |
case 10052: pszName = "WSAENETRESET "; break; | |
case 10053: pszName = "WSAECONNABORTED "; break; | |
case 10054: pszName = "WSAECONNRESET "; break; | |
case 10055: pszName = "WSAENOBUFS "; break; | |
case 10056: pszName = "WSAEISCONN "; break; | |
case 10057: pszName = "WSAENOTCONN "; break; | |
case 10058: pszName = "WSAESHUTDOWN "; break; | |
case 10059: pszName = "WSAETOOMANYREFS "; break; | |
case 10060: pszName = "WSAETIMEDOUT "; break; | |
case 10061: pszName = "WSAECONNREFUSED "; break; | |
case 10062: pszName = "WSAELOOP "; break; | |
case 10063: pszName = "WSAENAMETOOLONG "; break; | |
case 10064: pszName = "WSAEHOSTDOWN "; break; | |
case 10065: pszName = "WSAEHOSTUNREACH "; break; | |
case 10066: pszName = "WSAENOTEMPTY "; break; | |
case 10067: pszName = "WSAEPROCLIM "; break; | |
case 10068: pszName = "WSAEUSERS "; break; | |
case 10069: pszName = "WSAEDQUOT "; break; | |
case 10070: pszName = "WSAESTALE "; break; | |
case 10071: pszName = "WSAEREMOTE "; break; | |
case 10091: pszName = "WSASYSNOTREADY "; break; | |
case 10092: pszName = "WSAVERNOTSUPPORTED"; break; | |
case 10093: pszName = "WSANOTINITIALISED "; break; | |
case 11001: pszName = "WSAHOST_NOT_FOUND "; break; | |
case 11002: pszName = "WSATRY_AGAIN "; break; | |
case 11003: pszName = "WSANO_RECOVERY "; break; | |
case 11004: pszName = "WSANO_DATA "; break; | |
} | |
printf("Error 0x%x (%s)\n", ErrCode, pszName); | |
} | |
/*****************************************************************************/ | |
static void DisplaySECError(DWORD ErrCode) | |
{ | |
LPSTR pszName = NULL; // WinError.h | |
switch(ErrCode) | |
{ | |
case SEC_E_BUFFER_TOO_SMALL: | |
pszName = "SEC_E_BUFFER_TOO_SMALL - The message buffer is too small. Used with the Digest SSP."; | |
break; | |
case SEC_E_CRYPTO_SYSTEM_INVALID: | |
pszName = "SEC_E_CRYPTO_SYSTEM_INVALID - The cipher chosen for the security context is not supported. Used with the Digest SSP."; | |
break; | |
case SEC_E_INCOMPLETE_MESSAGE: | |
pszName = "SEC_E_INCOMPLETE_MESSAGE - The data in the input buffer is incomplete. The application needs to read more data from the server and call DecryptMessage (General) again."; | |
break; | |
case SEC_E_INVALID_HANDLE: | |
pszName = "SEC_E_INVALID_HANDLE - A context handle that is not valid was specified in the phContext parameter. Used with the Digest and Schannel SSPs."; | |
break; | |
case SEC_E_INVALID_TOKEN: | |
pszName = "SEC_E_INVALID_TOKEN - The buffers are of the wrong type or no buffer of type SECBUFFER_DATA was found. Used with the Schannel SSP."; | |
break; | |
case SEC_E_MESSAGE_ALTERED: | |
pszName = "SEC_E_MESSAGE_ALTERED - The message has been altered. Used with the Digest and Schannel SSPs."; | |
break; | |
case SEC_E_OUT_OF_SEQUENCE: | |
pszName = "SEC_E_OUT_OF_SEQUENCE - The message was not received in the correct sequence."; | |
break; | |
case SEC_E_QOP_NOT_SUPPORTED: | |
pszName = "SEC_E_QOP_NOT_SUPPORTED - Neither confidentiality nor integrity are supported by the security context. Used with the Digest SSP."; | |
break; | |
case SEC_I_CONTEXT_EXPIRED: | |
pszName = "SEC_I_CONTEXT_EXPIRED - The message sender has finished using the connection and has initiated a shutdown."; | |
break; | |
case SEC_I_RENEGOTIATE: | |
pszName = "SEC_I_RENEGOTIATE - The remote party requires a new handshake sequence or the application has just initiated a shutdown."; | |
break; | |
case SEC_E_ENCRYPT_FAILURE: | |
pszName = "SEC_E_ENCRYPT_FAILURE - The specified data could not be encrypted."; | |
break; | |
case SEC_E_DECRYPT_FAILURE: | |
pszName = "SEC_E_DECRYPT_FAILURE - The specified data could not be decrypted."; | |
break; | |
} | |
printf("Error 0x%x %s \n", ErrCode, pszName); | |
} | |
/*****************************************************************************/ | |
static void DisplayCertChain( PCCERT_CONTEXT pServerCert, BOOL fLocal ) | |
{ | |
CHAR szName[1000]; | |
PCCERT_CONTEXT pCurrentCert, pIssuerCert; | |
DWORD dwVerificationFlags; | |
printf("\n"); | |
// display leaf name | |
if( !CertNameToStr( pServerCert->dwCertEncodingType, | |
&pServerCert->pCertInfo->Subject, | |
CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, | |
szName, sizeof(szName) ) ) | |
{ printf("**** Error 0x%x building subject name\n", GetLastError()); } | |
if(fLocal) printf("Client subject: %s\n", szName); | |
else printf("Server subject: %s\n", szName); | |
if( !CertNameToStr( pServerCert->dwCertEncodingType, | |
&pServerCert->pCertInfo->Issuer, | |
CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, | |
szName, sizeof(szName) ) ) | |
{ printf("**** Error 0x%x building issuer name\n", GetLastError()); } | |
if(fLocal) printf("Client issuer: %s\n", szName); | |
else printf("Server issuer: %s\n\n", szName); | |
// display certificate chain | |
pCurrentCert = pServerCert; | |
while(pCurrentCert != NULL) | |
{ | |
dwVerificationFlags = 0; | |
pIssuerCert = CertGetIssuerCertificateFromStore( pServerCert->hCertStore, pCurrentCert, NULL, &dwVerificationFlags ); | |
if(pIssuerCert == NULL) | |
{ | |
if(pCurrentCert != pServerCert) CertFreeCertificateContext(pCurrentCert); | |
break; | |
} | |
if( !CertNameToStr( pIssuerCert->dwCertEncodingType, | |
&pIssuerCert->pCertInfo->Subject, | |
CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, | |
szName, sizeof(szName) ) ) | |
{ printf("**** Error 0x%x building subject name\n", GetLastError()); } | |
printf("CA subject: %s\n", szName); | |
if( !CertNameToStr( pIssuerCert->dwCertEncodingType, | |
&pIssuerCert->pCertInfo->Issuer, | |
CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, | |
szName, sizeof(szName) ) ) | |
{ printf("**** Error 0x%x building issuer name\n", GetLastError()); } | |
printf("CA issuer: %s\n\n", szName); | |
if(pCurrentCert != pServerCert) CertFreeCertificateContext(pCurrentCert); | |
pCurrentCert = pIssuerCert; | |
pIssuerCert = NULL; | |
} | |
} | |
/*****************************************************************************/ | |
static void DisplayConnectionInfo( CtxtHandle *phContext ) | |
{ | |
SECURITY_STATUS Status; | |
SecPkgContext_ConnectionInfo ConnectionInfo; | |
Status = g_pSSPI->QueryContextAttributes( phContext, SECPKG_ATTR_CONNECTION_INFO, (PVOID)&ConnectionInfo ); | |
if(Status != SEC_E_OK) { printf("Error 0x%x querying connection info\n", Status); return; } | |
printf("\n"); | |
switch(ConnectionInfo.dwProtocol) | |
{ | |
case SP_PROT_TLS1_CLIENT: | |
printf("Protocol: TLS1\n"); | |
break; | |
case SP_PROT_SSL3_CLIENT: | |
printf("Protocol: SSL3\n"); | |
break; | |
case SP_PROT_PCT1_CLIENT: | |
printf("Protocol: PCT\n"); | |
break; | |
case SP_PROT_SSL2_CLIENT: | |
printf("Protocol: SSL2\n"); | |
break; | |
default: | |
printf("Protocol: 0x%x\n", ConnectionInfo.dwProtocol); | |
} | |
switch(ConnectionInfo.aiCipher) | |
{ | |
case CALG_RC4: | |
printf("Cipher: RC4\n"); | |
break; | |
case CALG_3DES: | |
printf("Cipher: Triple DES\n"); | |
break; | |
case CALG_RC2: | |
printf("Cipher: RC2\n"); | |
break; | |
case CALG_DES: | |
case CALG_CYLINK_MEK: | |
printf("Cipher: DES\n"); | |
break; | |
case CALG_SKIPJACK: | |
printf("Cipher: Skipjack\n"); | |
break; | |
default: | |
printf("Cipher: 0x%x\n", ConnectionInfo.aiCipher); | |
} | |
printf("Cipher strength: %d\n", ConnectionInfo.dwCipherStrength); | |
switch(ConnectionInfo.aiHash) | |
{ | |
case CALG_MD5: | |
printf("Hash: MD5\n"); | |
break; | |
case CALG_SHA: | |
printf("Hash: SHA\n"); | |
break; | |
default: | |
printf("Hash: 0x%x\n", ConnectionInfo.aiHash); | |
} | |
printf("Hash strength: %d\n", ConnectionInfo.dwHashStrength); | |
switch(ConnectionInfo.aiExch) | |
{ | |
case CALG_RSA_KEYX: | |
case CALG_RSA_SIGN: | |
printf("Key exchange: RSA\n"); | |
break; | |
case CALG_KEA_KEYX: | |
printf("Key exchange: KEA\n"); | |
break; | |
case CALG_DH_EPHEM: | |
printf("Key exchange: DH Ephemeral\n"); | |
break; | |
default: | |
printf("Key exchange: 0x%x\n", ConnectionInfo.aiExch); | |
} | |
printf("Key exchange strength: %d\n", ConnectionInfo.dwExchStrength); | |
} | |
/*****************************************************************************/ | |
static void PrintHexDump( DWORD length, PBYTE buffer ) | |
{ | |
DWORD i,count,index; | |
CHAR rgbDigits[]="0123456789abcdef"; | |
CHAR rgbLine[100]; | |
char cbLine; | |
for(index = 0; length; length -= count, buffer += count, index += count) | |
{ | |
count = (length > 16) ? 16:length; | |
sprintf(rgbLine, "%4.4x ",index); | |
cbLine = 6; | |
for(i=0;i<count;i++) | |
{ | |
rgbLine[cbLine++] = rgbDigits[buffer[i] >> 4]; | |
rgbLine[cbLine++] = rgbDigits[buffer[i] & 0x0f]; | |
if(i == 7) rgbLine[cbLine++] = ':'; | |
else rgbLine[cbLine++] = ' '; | |
} | |
for(; i < 16; i++) | |
{ | |
rgbLine[cbLine++] = ' '; | |
rgbLine[cbLine++] = ' '; | |
rgbLine[cbLine++] = ' '; | |
} | |
rgbLine[cbLine++] = ' '; | |
for(i = 0; i < count; i++) | |
{ | |
if(buffer[i] < 32 || buffer[i] > 126 || buffer[i] == '%') rgbLine[cbLine++] = '.'; | |
else rgbLine[cbLine++] = buffer[i]; | |
} | |
rgbLine[cbLine++] = 0; | |
printf("%s\n", rgbLine); | |
} | |
} | |
/*****************************************************************************/ | |
static void PrintText( DWORD length, PBYTE buffer ) // handle unprintable charaters | |
{ | |
int i; // | |
printf("\n"); // "length = %d bytes \n", length); | |
for( i = 0; i < (int)length; i++ ) | |
{ | |
if( buffer[i] == 10 || buffer[i] == 13 ) | |
printf("%c", (char)buffer[i]); | |
else if( buffer[i] < 32 || buffer[i] > 126 || buffer[i] == '%' ) | |
printf("%c", '.'); | |
else | |
printf("%c", (char)buffer[i]); | |
} | |
printf("\n"); | |
} | |
/*****************************************************************************/ | |
static void WriteDataToFile( PSTR pszData, PBYTE pbData, DWORD cbData ) | |
{ | |
FILE *file; | |
file = fopen(pszData, "wb"); | |
if(file == NULL) | |
{ printf("**** Error opening file '%s'\n", pszData); return; } | |
if(fwrite(pbData, 1, cbData, file) != cbData) | |
{ printf("**** Error writing to file\n"); return; } | |
fclose(file); | |
} | |
/*****************************************************************************/ | |
BOOL LoadSecurityLibrary( void ) // load SSPI.DLL, set up a special table - PSecurityFunctionTable | |
{ | |
INIT_SECURITY_INTERFACE pInitSecurityInterface; | |
// QUERY_CREDENTIALS_ATTRIBUTES_FN pQueryCredentialsAttributes; | |
OSVERSIONINFO VerInfo; | |
UCHAR lpszDLL[MAX_PATH]; | |
// Find out which security DLL to use, depending on | |
// whether we are on Win2K, NT or Win9x | |
VerInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); | |
if ( !GetVersionEx (&VerInfo) ) return FALSE; | |
if ( VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && VerInfo.dwMajorVersion == 4 ) | |
{ | |
strcpy (lpszDLL, NT4_DLL_NAME ); // NT4_DLL_NAME TEXT("Security.dll") | |
} | |
else if ( VerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS || | |
VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) | |
{ | |
strcpy(lpszDLL, DLL_NAME); // DLL_NAME TEXT("Secur32.dll") | |
} | |
else | |
{ printf( "System not recognized\n" ); return FALSE; } | |
// Load Security DLL | |
g_hSecurity = LoadLibrary(lpszDLL); | |
if(g_hSecurity == NULL) { printf( "Error 0x%x loading %s.\n", GetLastError(), lpszDLL ); return FALSE; } | |
pInitSecurityInterface = (INIT_SECURITY_INTERFACE)GetProcAddress( g_hSecurity, "InitSecurityInterfaceA" ); | |
if(pInitSecurityInterface == NULL) { printf( "Error 0x%x reading InitSecurityInterface entry point.\n", GetLastError() ); return FALSE; } | |
g_pSSPI = pInitSecurityInterface(); // call InitSecurityInterfaceA(void); | |
if(g_pSSPI == NULL) { printf("Error 0x%x reading security interface.\n", GetLastError()); return FALSE; } | |
return TRUE; // and PSecurityFunctionTable | |
} | |
/*****************************************************************************/ | |
void UnloadSecurityLibrary(void) | |
{ | |
FreeLibrary(g_hSecurity); | |
g_hSecurity = NULL; | |
} | |
/*****************************************************************************/ | |
static DWORD VerifyServerCertificate( PCCERT_CONTEXT pServerCert, PSTR pszServerName, DWORD dwCertFlags ) | |
{ | |
HTTPSPolicyCallbackData polHttps; | |
CERT_CHAIN_POLICY_PARA PolicyPara; | |
CERT_CHAIN_POLICY_STATUS PolicyStatus; | |
CERT_CHAIN_PARA ChainPara; | |
PCCERT_CHAIN_CONTEXT pChainContext = NULL; | |
DWORD cchServerName, Status; | |
LPSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH, | |
szOID_SERVER_GATED_CRYPTO, | |
szOID_SGC_NETSCAPE }; | |
DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR); | |
PWSTR pwszServerName = NULL; | |
if(pServerCert == NULL) | |
{ Status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } | |
// Convert server name to unicode. | |
if(pszServerName == NULL || strlen(pszServerName) == 0) | |
{ Status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } | |
cchServerName = MultiByteToWideChar(CP_ACP, 0, pszServerName, -1, NULL, 0); | |
pwszServerName = LocalAlloc(LMEM_FIXED, cchServerName * sizeof(WCHAR)); | |
if(pwszServerName == NULL) | |
{ Status = SEC_E_INSUFFICIENT_MEMORY; goto cleanup; } | |
cchServerName = MultiByteToWideChar(CP_ACP, 0, pszServerName, -1, pwszServerName, cchServerName); | |
if(cchServerName == 0) | |
{ Status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } | |
// Build certificate chain. | |
ZeroMemory(&ChainPara, sizeof(ChainPara)); | |
ChainPara.cbSize = sizeof(ChainPara); | |
ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; | |
ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages; | |
ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; | |
if( !CertGetCertificateChain( NULL, | |
pServerCert, | |
NULL, | |
pServerCert->hCertStore, | |
&ChainPara, | |
0, | |
NULL, | |
&pChainContext ) ) | |
{ | |
Status = GetLastError(); | |
printf("Error 0x%x returned by CertGetCertificateChain!\n", Status); | |
goto cleanup; | |
} | |
// Validate certificate chain. | |
ZeroMemory(&polHttps, sizeof(HTTPSPolicyCallbackData)); | |
polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); | |
polHttps.dwAuthType = AUTHTYPE_SERVER; | |
polHttps.fdwChecks = dwCertFlags; | |
polHttps.pwszServerName = pwszServerName; | |
memset(&PolicyPara, 0, sizeof(PolicyPara)); | |
PolicyPara.cbSize = sizeof(PolicyPara); | |
PolicyPara.pvExtraPolicyPara = &polHttps; | |
memset(&PolicyStatus, 0, sizeof(PolicyStatus)); | |
PolicyStatus.cbSize = sizeof(PolicyStatus); | |
if( !CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL, | |
pChainContext, | |
&PolicyPara, | |
&PolicyStatus ) ) | |
{ | |
Status = GetLastError(); | |
printf("Error 0x%x returned by CertVerifyCertificateChainPolicy!\n", Status); | |
goto cleanup; | |
} | |
if(PolicyStatus.dwError) | |
{ | |
Status = PolicyStatus.dwError; | |
DisplayWinVerifyTrustError(Status); | |
goto cleanup; | |
} | |
Status = SEC_E_OK; | |
cleanup: | |
if(pChainContext) CertFreeCertificateChain(pChainContext); | |
if(pwszServerName) LocalFree(pwszServerName); | |
return Status; | |
} | |
/*****************************************************************************/ | |
static SECURITY_STATUS CreateCredentials( LPSTR pszUser, PCredHandle phCreds ) | |
{ // in out | |
TimeStamp tsExpiry; | |
SECURITY_STATUS Status; | |
DWORD cSupportedAlgs = 0; | |
ALG_ID rgbSupportedAlgs[16]; | |
PCCERT_CONTEXT pCertContext = NULL; | |
// Open the "MY" certificate store, where IE stores client certificates. | |
// Windows maintains 4 stores -- MY, CA, ROOT, SPC. | |
if(hMyCertStore == NULL) | |
{ | |
hMyCertStore = CertOpenSystemStore(0, "MY"); | |
if(!hMyCertStore) | |
{ | |
printf( "**** Error 0x%x returned by CertOpenSystemStore\n", GetLastError() ); | |
return SEC_E_NO_CREDENTIALS; | |
} | |
} | |
// If a user name is specified, then attempt to find a client | |
// certificate. Otherwise, just create a NULL credential. | |
if(pszUser) | |
{ | |
// Find client certificate. Note that this sample just searches for a | |
// certificate that contains the user name somewhere in the subject name. | |
// A real application should be a bit less casual. | |
pCertContext = CertFindCertificateInStore( hMyCertStore, // hCertStore | |
X509_ASN_ENCODING, // dwCertEncodingType | |
0, // dwFindFlags | |
CERT_FIND_SUBJECT_STR_A,// dwFindType | |
pszUser, // *pvFindPara | |
NULL ); // pPrevCertContext | |
if(pCertContext == NULL) | |
{ | |
printf("**** Error 0x%x returned by CertFindCertificateInStore\n", GetLastError()); | |
if( GetLastError() == CRYPT_E_NOT_FOUND ) printf("CRYPT_E_NOT_FOUND - property doesn't exist\n"); | |
return SEC_E_NO_CREDENTIALS; | |
} | |
} | |
// Build Schannel credential structure. Currently, this sample only | |
// specifies the protocol to be used (and optionally the certificate, | |
// of course). Real applications may wish to specify other parameters as well. | |
ZeroMemory( &SchannelCred, sizeof(SchannelCred) ); | |
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; | |
if(pCertContext) | |
{ | |
SchannelCred.cCreds = 1; | |
SchannelCred.paCred = &pCertContext; | |
} | |
SchannelCred.grbitEnabledProtocols = dwProtocol; | |
if(aiKeyExch) rgbSupportedAlgs[cSupportedAlgs++] = aiKeyExch; | |
if(cSupportedAlgs) | |
{ | |
SchannelCred.cSupportedAlgs = cSupportedAlgs; | |
SchannelCred.palgSupportedAlgs = rgbSupportedAlgs; | |
} | |
SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; | |
// The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because | |
// this sample verifies the server certificate manually. | |
// Applications that expect to run on WinNT, Win9x, or WinME | |
// should specify this flag and also manually verify the server | |
// certificate. Applications running on newer versions of Windows can | |
// leave off this flag, in which case the InitializeSecurityContext | |
// function will validate the server certificate automatically. | |
SchannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; | |
// Create an SSPI credential. | |
Status = g_pSSPI->AcquireCredentialsHandleA( NULL, // Name of principal | |
UNISP_NAME_A, // Name of package | |
SECPKG_CRED_OUTBOUND, // Flags indicating use | |
NULL, // Pointer to logon ID | |
&SchannelCred, // Package specific data | |
NULL, // Pointer to GetKey() func | |
NULL, // Value to pass to GetKey() | |
phCreds, // (out) Cred Handle | |
&tsExpiry ); // (out) Lifetime (optional) | |
if(Status != SEC_E_OK) printf("**** Error 0x%x returned by AcquireCredentialsHandle\n", Status); | |
// cleanup: Free the certificate context. Schannel has already made its own copy. | |
if(pCertContext) CertFreeCertificateContext(pCertContext); | |
return Status; | |
} | |
/*****************************************************************************/ | |
static INT ConnectToServer( LPSTR pszServerName, INT iPortNumber, SOCKET * pSocket ) | |
{ // in in out | |
SOCKET Socket; | |
struct sockaddr_in sin; | |
struct hostent *hp; | |
Socket = socket(PF_INET, SOCK_STREAM, 0); | |
if(Socket == INVALID_SOCKET) | |
{ | |
printf("**** Error %d creating socket\n", WSAGetLastError()); | |
DisplayWinSockError( WSAGetLastError() ); | |
return WSAGetLastError(); | |
} | |
if(fUseProxy) | |
{ | |
sin.sin_family = AF_INET; | |
sin.sin_port = ntohs((u_short)iProxyPort); | |
if((hp = gethostbyname(pszProxyServer)) == NULL) | |
{ | |
printf("**** Error %d returned by gethostbyname using Proxy\n", WSAGetLastError()); | |
DisplayWinSockError( WSAGetLastError() ); | |
return WSAGetLastError(); | |
} | |
else | |
memcpy(&sin.sin_addr, hp->h_addr, 4); | |
} | |
else // No proxy used | |
{ | |
sin.sin_family = AF_INET; | |
sin.sin_port = htons((u_short)iPortNumber); | |
if((hp = gethostbyname(pszServerName)) == NULL) | |
{ | |
printf("**** Error returned by gethostbyname\n"); | |
DisplayWinSockError( WSAGetLastError() ); | |
return WSAGetLastError(); | |
} | |
else | |
memcpy(&sin.sin_addr, hp->h_addr, 4); | |
} | |
if(connect(Socket, (struct sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR) | |
{ | |
printf( "**** Error %d connecting to \"%s\" (%s)\n", WSAGetLastError(), pszServerName, inet_ntoa(sin.sin_addr) ); | |
closesocket(Socket); | |
DisplayWinSockError( WSAGetLastError() ); | |
return WSAGetLastError(); | |
} | |
if(fUseProxy) | |
{ | |
BYTE pbMessage[200]; | |
DWORD cbMessage; | |
// Build message for proxy server | |
strcpy(pbMessage, "CONNECT "); | |
strcat(pbMessage, pszServerName); | |
strcat(pbMessage, ":"); | |
_itoa(iPortNumber, pbMessage + strlen(pbMessage), 10); | |
strcat(pbMessage, " HTTP/1.0\r\nUser-Agent: webclient\r\n\r\n"); | |
cbMessage = (DWORD)strlen(pbMessage); | |
// Send message to proxy server | |
if(send(Socket, pbMessage, cbMessage, 0) == SOCKET_ERROR) | |
{ | |
printf("**** Error %d sending message to proxy!\n", WSAGetLastError()); | |
DisplayWinSockError( WSAGetLastError() ); | |
return WSAGetLastError(); | |
} | |
// Receive message from proxy server | |
cbMessage = recv(Socket, pbMessage, 200, 0); | |
if(cbMessage == SOCKET_ERROR) | |
{ | |
printf("**** Error %d receiving message from proxy\n", WSAGetLastError()); | |
DisplayWinSockError( WSAGetLastError() ); | |
return WSAGetLastError(); | |
} | |
// this sample is limited but in normal use it | |
// should continue to receive until CR LF CR LF is received | |
} | |
*pSocket = Socket; | |
return SEC_E_OK; | |
} | |
/*****************************************************************************/ | |
static LONG DisconnectFromServer( SOCKET Socket, PCredHandle phCreds, CtxtHandle * phContext ) | |
{ | |
PBYTE pbMessage; | |
DWORD dwType, dwSSPIFlags, dwSSPIOutFlags, cbMessage, cbData, Status; | |
SecBufferDesc OutBuffer; | |
SecBuffer OutBuffers[1]; | |
TimeStamp tsExpiry; | |
dwType = SCHANNEL_SHUTDOWN; // Notify schannel that we are about to close the connection. | |
OutBuffers[0].pvBuffer = &dwType; | |
OutBuffers[0].BufferType = SECBUFFER_TOKEN; | |
OutBuffers[0].cbBuffer = sizeof(dwType); | |
OutBuffer.cBuffers = 1; | |
OutBuffer.pBuffers = OutBuffers; | |
OutBuffer.ulVersion = SECBUFFER_VERSION; | |
Status = g_pSSPI->ApplyControlToken(phContext, &OutBuffer); | |
if(FAILED(Status)) { printf("**** Error 0x%x returned by ApplyControlToken\n", Status); goto cleanup; } | |
// Build an SSL close notify message. | |
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | | |
ISC_REQ_REPLAY_DETECT | | |
ISC_REQ_CONFIDENTIALITY | | |
ISC_RET_EXTENDED_ERROR | | |
ISC_REQ_ALLOCATE_MEMORY | | |
ISC_REQ_STREAM; | |
OutBuffers[0].pvBuffer = NULL; | |
OutBuffers[0].BufferType = SECBUFFER_TOKEN; | |
OutBuffers[0].cbBuffer = 0; | |
OutBuffer.cBuffers = 1; | |
OutBuffer.pBuffers = OutBuffers; | |
OutBuffer.ulVersion = SECBUFFER_VERSION; | |
Status = g_pSSPI->InitializeSecurityContextA( phCreds, | |
phContext, | |
NULL, | |
dwSSPIFlags, | |
0, | |
SECURITY_NATIVE_DREP, | |
NULL, | |
0, | |
phContext, | |
&OutBuffer, | |
&dwSSPIOutFlags, | |
&tsExpiry ); | |
if(FAILED(Status)) { printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status); goto cleanup; } | |
pbMessage = OutBuffers[0].pvBuffer; | |
cbMessage = OutBuffers[0].cbBuffer; | |
// Send the close notify message to the server. | |
if(pbMessage != NULL && cbMessage != 0) | |
{ | |
cbData = send(Socket, pbMessage, cbMessage, 0); | |
if(cbData == SOCKET_ERROR || cbData == 0) | |
{ | |
Status = WSAGetLastError(); | |
printf("**** Error %d sending close notify\n", Status); | |
DisplayWinSockError( WSAGetLastError() ); | |
goto cleanup; | |
} | |
printf("Sending Close Notify\n"); | |
printf("%d bytes of handshake data sent\n", cbData); | |
if(fVerbose) { PrintHexDump(cbData, pbMessage); printf("\n"); } | |
g_pSSPI->FreeContextBuffer(pbMessage); // Free output buffer. | |
} | |
cleanup: | |
g_pSSPI->DeleteSecurityContext(phContext); // Free the security context. | |
closesocket(Socket); // Close the socket. | |
return Status; | |
} | |
/*****************************************************************************/ | |
static void GetNewClientCredentials( CredHandle *phCreds, CtxtHandle *phContext ) | |
{ | |
CredHandle hCreds; | |
SecPkgContext_IssuerListInfoEx IssuerListInfo; | |
PCCERT_CHAIN_CONTEXT pChainContext; | |
CERT_CHAIN_FIND_BY_ISSUER_PARA FindByIssuerPara; | |
PCCERT_CONTEXT pCertContext; | |
TimeStamp tsExpiry; | |
SECURITY_STATUS Status; | |
// Read list of trusted issuers from schannel. | |
Status = g_pSSPI->QueryContextAttributes( phContext, SECPKG_ATTR_ISSUER_LIST_EX, (PVOID)&IssuerListInfo ); | |
if(Status != SEC_E_OK) { printf("Error 0x%x querying issuer list info\n", Status); return; } | |
// Enumerate the client certificates. | |
ZeroMemory(&FindByIssuerPara, sizeof(FindByIssuerPara)); | |
FindByIssuerPara.cbSize = sizeof(FindByIssuerPara); | |
FindByIssuerPara.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH; | |
FindByIssuerPara.dwKeySpec = 0; | |
FindByIssuerPara.cIssuer = IssuerListInfo.cIssuers; | |
FindByIssuerPara.rgIssuer = IssuerListInfo.aIssuers; | |
pChainContext = NULL; | |
while(TRUE) | |
{ // Find a certificate chain. | |
pChainContext = CertFindChainInStore( hMyCertStore, | |
X509_ASN_ENCODING, | |
0, | |
CERT_CHAIN_FIND_BY_ISSUER, | |
&FindByIssuerPara, | |
pChainContext ); | |
if(pChainContext == NULL) { printf("Error 0x%x finding cert chain\n", GetLastError()); break; } | |
printf("\ncertificate chain found\n"); | |
// Get pointer to leaf certificate context. | |
pCertContext = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext; | |
// Create schannel credential. | |
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; | |
SchannelCred.cCreds = 1; | |
SchannelCred.paCred = &pCertContext; | |
Status = g_pSSPI->AcquireCredentialsHandleA( NULL, // Name of principal | |
UNISP_NAME_A, // Name of package | |
SECPKG_CRED_OUTBOUND, // Flags indicating use | |
NULL, // Pointer to logon ID | |
&SchannelCred, // Package specific data | |
NULL, // Pointer to GetKey() func | |
NULL, // Value to pass to GetKey() | |
&hCreds, // (out) Cred Handle | |
&tsExpiry ); // (out) Lifetime (optional) | |
if(Status != SEC_E_OK) {printf("**** Error 0x%x returned by AcquireCredentialsHandle\n", Status); continue;} | |
printf("\nnew schannel credential created\n"); | |
g_pSSPI->FreeCredentialsHandle(phCreds); // Destroy the old credentials. | |
*phCreds = hCreds; | |
} | |
} | |
/*****************************************************************************/ | |
static SECURITY_STATUS ClientHandshakeLoop( SOCKET Socket, // in | |
PCredHandle phCreds, // in | |
CtxtHandle * phContext, // in, out | |
BOOL fDoInitialRead, // in | |
SecBuffer * pExtraData ) // out | |
{ | |
SecBufferDesc OutBuffer, InBuffer; | |
SecBuffer InBuffers[2], OutBuffers[1]; | |
DWORD dwSSPIFlags, dwSSPIOutFlags, cbData, cbIoBuffer; | |
TimeStamp tsExpiry; | |
SECURITY_STATUS scRet; | |
PUCHAR IoBuffer; | |
BOOL fDoRead; | |
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | | |
ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; | |
// Allocate data buffer. | |
IoBuffer = LocalAlloc(LMEM_FIXED, IO_BUFFER_SIZE); | |
if(IoBuffer == NULL) { printf("**** Out of memory (1)\n"); return SEC_E_INTERNAL_ERROR; } | |
cbIoBuffer = 0; | |
fDoRead = fDoInitialRead; | |
// Loop until the handshake is finished or an error occurs. | |
scRet = SEC_I_CONTINUE_NEEDED; | |
while( scRet == SEC_I_CONTINUE_NEEDED || | |
scRet == SEC_E_INCOMPLETE_MESSAGE || | |
scRet == SEC_I_INCOMPLETE_CREDENTIALS ) | |
{ | |
if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) // Read data from server. | |
{ | |
if(fDoRead) | |
{ | |
cbData = recv(Socket, IoBuffer + cbIoBuffer, IO_BUFFER_SIZE - cbIoBuffer, 0 ); | |
if(cbData == SOCKET_ERROR) | |
{ | |
printf("**** Error %d reading data from server\n", WSAGetLastError()); | |
scRet = SEC_E_INTERNAL_ERROR; | |
break; | |
} | |
else if(cbData == 0) | |
{ | |
printf("**** Server unexpectedly disconnected\n"); | |
scRet = SEC_E_INTERNAL_ERROR; | |
break; | |
} | |
printf("%d bytes of handshake data received\n", cbData); | |
if(fVerbose) { PrintHexDump(cbData, IoBuffer + cbIoBuffer); printf("\n"); } | |
cbIoBuffer += cbData; | |
} | |
else | |
fDoRead = TRUE; | |
} | |
// Set up the input buffers. Buffer 0 is used to pass in data | |
// received from the server. Schannel will consume some or all | |
// of this. Leftover data (if any) will be placed in buffer 1 and | |
// given a buffer type of SECBUFFER_EXTRA. | |
InBuffers[0].pvBuffer = IoBuffer; | |
InBuffers[0].cbBuffer = cbIoBuffer; | |
InBuffers[0].BufferType = SECBUFFER_TOKEN; | |
InBuffers[1].pvBuffer = NULL; | |
InBuffers[1].cbBuffer = 0; | |
InBuffers[1].BufferType = SECBUFFER_EMPTY; | |
InBuffer.cBuffers = 2; | |
InBuffer.pBuffers = InBuffers; | |
InBuffer.ulVersion = SECBUFFER_VERSION; | |
// Set up the output buffers. These are initialized to NULL | |
// so as to make it less likely we'll attempt to free random | |
// garbage later. | |
OutBuffers[0].pvBuffer = NULL; | |
OutBuffers[0].BufferType= SECBUFFER_TOKEN; | |
OutBuffers[0].cbBuffer = 0; | |
OutBuffer.cBuffers = 1; | |
OutBuffer.pBuffers = OutBuffers; | |
OutBuffer.ulVersion = SECBUFFER_VERSION; | |
// Call InitializeSecurityContext. | |
scRet = g_pSSPI->InitializeSecurityContextA( phCreds, | |
phContext, | |
NULL, | |
dwSSPIFlags, | |
0, | |
SECURITY_NATIVE_DREP, | |
&InBuffer, | |
0, | |
NULL, | |
&OutBuffer, | |
&dwSSPIOutFlags, | |
&tsExpiry ); | |
// If InitializeSecurityContext was successful (or if the error was | |
// one of the special extended ones), send the contends of the output | |
// buffer to the server. | |
if(scRet == SEC_E_OK || | |
scRet == SEC_I_CONTINUE_NEEDED || | |
FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR)) | |
{ | |
if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) | |
{ | |
cbData = send(Socket, OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0 ); | |
if(cbData == SOCKET_ERROR || cbData == 0) | |
{ | |
printf( "**** Error %d sending data to server (2)\n", WSAGetLastError() ); | |
DisplayWinSockError( WSAGetLastError() ); | |
g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); | |
g_pSSPI->DeleteSecurityContext(phContext); | |
return SEC_E_INTERNAL_ERROR; | |
} | |
printf("%d bytes of handshake data sent\n", cbData); | |
if(fVerbose) { PrintHexDump(cbData, OutBuffers[0].pvBuffer); printf("\n"); } | |
// Free output buffer. | |
g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); | |
OutBuffers[0].pvBuffer = NULL; | |
} | |
} | |
// If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE, | |
// then we need to read more data from the server and try again. | |
if(scRet == SEC_E_INCOMPLETE_MESSAGE) continue; | |
// If InitializeSecurityContext returned SEC_E_OK, then the | |
// handshake completed successfully. | |
if(scRet == SEC_E_OK) | |
{ | |
// If the "extra" buffer contains data, this is encrypted application | |
// protocol layer stuff. It needs to be saved. The application layer | |
// will later decrypt it with DecryptMessage. | |
printf("Handshake was successful\n"); | |
if(InBuffers[1].BufferType == SECBUFFER_EXTRA) | |
{ | |
pExtraData->pvBuffer = LocalAlloc( LMEM_FIXED, InBuffers[1].cbBuffer ); | |
if(pExtraData->pvBuffer == NULL) { printf("**** Out of memory (2)\n"); return SEC_E_INTERNAL_ERROR; } | |
MoveMemory( pExtraData->pvBuffer, | |
IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), | |
InBuffers[1].cbBuffer ); | |
pExtraData->cbBuffer = InBuffers[1].cbBuffer; | |
pExtraData->BufferType = SECBUFFER_TOKEN; | |
printf( "%d bytes of app data was bundled with handshake data\n", pExtraData->cbBuffer ); | |
} | |
else | |
{ | |
pExtraData->pvBuffer = NULL; | |
pExtraData->cbBuffer = 0; | |
pExtraData->BufferType = SECBUFFER_EMPTY; | |
} | |
break; // Bail out to quit | |
} | |
// Check for fatal error. | |
if(FAILED(scRet)) { printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet); break; } | |
// If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS, | |
// then the server just requested client authentication. | |
if(scRet == SEC_I_INCOMPLETE_CREDENTIALS) | |
{ | |
// Busted. The server has requested client authentication and | |
// the credential we supplied didn't contain a client certificate. | |
// This function will read the list of trusted certificate | |
// authorities ("issuers") that was received from the server | |
// and attempt to find a suitable client certificate that | |
// was issued by one of these. If this function is successful, | |
// then we will connect using the new certificate. Otherwise, | |
// we will attempt to connect anonymously (using our current credentials). | |
GetNewClientCredentials(phCreds, phContext); | |
// Go around again. | |
fDoRead = FALSE; | |
scRet = SEC_I_CONTINUE_NEEDED; | |
continue; | |
} | |
// Copy any leftover data from the "extra" buffer, and go around again. | |
if ( InBuffers[1].BufferType == SECBUFFER_EXTRA ) | |
{ | |
MoveMemory( IoBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); | |
cbIoBuffer = InBuffers[1].cbBuffer; | |
} | |
else | |
cbIoBuffer = 0; | |
} | |
// Delete the security context in the case of a fatal error. | |
if(FAILED(scRet)) g_pSSPI->DeleteSecurityContext(phContext); | |
LocalFree(IoBuffer); | |
return scRet; | |
} | |
/*****************************************************************************/ | |
static SECURITY_STATUS PerformClientHandshake( SOCKET Socket, // in | |
PCredHandle phCreds, // in | |
LPSTR pszServerName, // in | |
CtxtHandle * phContext, // out | |
SecBuffer * pExtraData ) // out | |
{ | |
SecBufferDesc OutBuffer; | |
SecBuffer OutBuffers[1]; | |
DWORD dwSSPIFlags, dwSSPIOutFlags, cbData; | |
TimeStamp tsExpiry; | |
SECURITY_STATUS scRet; | |
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | | |
ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; | |
// Initiate a ClientHello message and generate a token. | |
OutBuffers[0].pvBuffer = NULL; | |
OutBuffers[0].BufferType = SECBUFFER_TOKEN; | |
OutBuffers[0].cbBuffer = 0; | |
OutBuffer.cBuffers = 1; | |
OutBuffer.pBuffers = OutBuffers; | |
OutBuffer.ulVersion = SECBUFFER_VERSION; | |
scRet = g_pSSPI->InitializeSecurityContextA( phCreds, | |
NULL, | |
pszServerName, | |
dwSSPIFlags, | |
0, | |
SECURITY_NATIVE_DREP, | |
NULL, | |
0, | |
phContext, | |
&OutBuffer, | |
&dwSSPIOutFlags, | |
&tsExpiry ); | |
if(scRet != SEC_I_CONTINUE_NEEDED) { printf("**** Error %d returned by InitializeSecurityContext (1)\n", scRet); return scRet; } | |
// Send response to server if there is one. | |
if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) | |
{ | |
cbData = send( Socket, OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0 ); | |
if( cbData == SOCKET_ERROR || cbData == 0 ) | |
{ | |
printf("**** Error %d sending data to server (1)\n", WSAGetLastError()); | |
g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); | |
g_pSSPI->DeleteSecurityContext(phContext); | |
return SEC_E_INTERNAL_ERROR; | |
} | |
printf("%d bytes of handshake data sent\n", cbData); | |
if(fVerbose) { PrintHexDump(cbData, OutBuffers[0].pvBuffer); printf("\n"); } | |
g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); // Free output buffer. | |
OutBuffers[0].pvBuffer = NULL; | |
} | |
return ClientHandshakeLoop(Socket, phCreds, phContext, TRUE, pExtraData); | |
} | |
/*****************************************************************************/ | |
static DWORD EncryptSend( SOCKET Socket, CtxtHandle * phContext, PBYTE pbIoBuffer, SecPkgContext_StreamSizes Sizes ) | |
// http://msdn.microsoft.com/en-us/library/aa375378(VS.85).aspx | |
// The encrypted message is encrypted in place, overwriting the original contents of its buffer. | |
{ | |
SECURITY_STATUS scRet; // unsigned long cbBuffer; // Size of the buffer, in bytes | |
SecBufferDesc Message; // unsigned long BufferType; // Type of the buffer (below) | |
SecBuffer Buffers[4]; // void SEC_FAR * pvBuffer; // Pointer to the buffer | |
DWORD cbMessage, cbData; | |
PBYTE pbMessage; | |
pbMessage = pbIoBuffer + Sizes.cbHeader; // Offset by "header size" | |
cbMessage = (DWORD)strlen(pbMessage); | |
printf("Sending %d bytes of plaintext:", cbMessage); PrintText(cbMessage, pbMessage); | |
if(fVerbose) { PrintHexDump(cbMessage, pbMessage); printf("\n"); } | |
// Encrypt the HTTP request. | |
Buffers[0].pvBuffer = pbIoBuffer; // Pointer to buffer 1 | |
Buffers[0].cbBuffer = Sizes.cbHeader; // length of header | |
Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; // Type of the buffer | |
Buffers[1].pvBuffer = pbMessage; // Pointer to buffer 2 | |
Buffers[1].cbBuffer = cbMessage; // length of the message | |
Buffers[1].BufferType = SECBUFFER_DATA; // Type of the buffer | |
Buffers[2].pvBuffer = pbMessage + cbMessage; // Pointer to buffer 3 | |
Buffers[2].cbBuffer = Sizes.cbTrailer; // length of the trailor | |
Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; // Type of the buffer | |
Buffers[3].pvBuffer = SECBUFFER_EMPTY; // Pointer to buffer 4 | |
Buffers[3].cbBuffer = SECBUFFER_EMPTY; // length of buffer 4 | |
Buffers[3].BufferType = SECBUFFER_EMPTY; // Type of the buffer 4 | |
Message.ulVersion = SECBUFFER_VERSION; // Version number | |
Message.cBuffers = 4; // Number of buffers - must contain four SecBuffer structures. | |
Message.pBuffers = Buffers; // Pointer to array of buffers | |
scRet = g_pSSPI->EncryptMessage(phContext, 0, &Message, 0); // must contain four SecBuffer structures. | |
if(FAILED(scRet)) { printf("**** Error 0x%x returned by EncryptMessage\n", scRet); return scRet; } | |
// Send the encrypted data to the server. len flags | |
cbData = send( Socket, pbIoBuffer, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer, 0 ); | |
printf("%d bytes of encrypted data sent\n", cbData); | |
if(fVerbose) { PrintHexDump(cbData, pbIoBuffer); printf("\n"); } | |
return cbData; // send( Socket, pbIoBuffer, Sizes.cbHeader + strlen(pbMessage) + Sizes.cbTrailer, 0 ); | |
} | |
/*****************************************************************************/ | |
static SECURITY_STATUS ReadDecrypt( SOCKET Socket, PCredHandle phCreds, CtxtHandle * phContext, PBYTE pbIoBuffer, DWORD cbIoBufferLength ) | |
// calls recv() - blocking socket read | |
// http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx | |
// The encrypted message is decrypted in place, overwriting the original contents of its buffer. | |
// http://msdn.microsoft.com/en-us/library/aa375211(VS.85).aspx | |
{ | |
SecBuffer ExtraBuffer; | |
SecBuffer *pDataBuffer, *pExtraBuffer; | |
SECURITY_STATUS scRet; // unsigned long cbBuffer; // Size of the buffer, in bytes | |
SecBufferDesc Message; // unsigned long BufferType; // Type of the buffer (below) | |
SecBuffer Buffers[4]; // void SEC_FAR * pvBuffer; // Pointer to the buffer | |
DWORD cbIoBuffer, cbData, length; | |
PBYTE buff; | |
int i; | |
// Read data from server until done. | |
cbIoBuffer = 0; | |
scRet = 0; | |
while(TRUE) // Read some data. | |
{ | |
if( cbIoBuffer == 0 || scRet == SEC_E_INCOMPLETE_MESSAGE ) // get the data | |
{ | |
cbData = recv(Socket, pbIoBuffer + cbIoBuffer, cbIoBufferLength - cbIoBuffer, 0); | |
if(cbData == SOCKET_ERROR) | |
{ | |
printf("**** Error %d reading data from server\n", WSAGetLastError()); | |
scRet = SEC_E_INTERNAL_ERROR; | |
break; | |
} | |
else if(cbData == 0) // Server disconnected. | |
{ | |
if(cbIoBuffer) | |
{ | |
printf("**** Server unexpectedly disconnected\n"); | |
scRet = SEC_E_INTERNAL_ERROR; | |
return scRet; | |
} | |
else | |
break; // All Done | |
} | |
else // success | |
{ | |
printf("%d bytes of (encrypted) application data received\n", cbData); | |
if(fVerbose) { PrintHexDump(cbData, pbIoBuffer + cbIoBuffer); printf("\n"); } | |
cbIoBuffer += cbData; | |
} | |
} | |
// Decrypt the received data. | |
Buffers[0].pvBuffer = pbIoBuffer; | |
Buffers[0].cbBuffer = cbIoBuffer; | |
Buffers[0].BufferType = SECBUFFER_DATA; // Initial Type of the buffer 1 | |
Buffers[1].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 2 | |
Buffers[2].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 3 | |
Buffers[3].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 4 | |
Message.ulVersion = SECBUFFER_VERSION; // Version number | |
Message.cBuffers = 4; // Number of buffers - must contain four SecBuffer structures. | |
Message.pBuffers = Buffers; // Pointer to array of buffers | |
scRet = g_pSSPI->DecryptMessage(phContext, &Message, 0, NULL); | |
if( scRet == SEC_I_CONTEXT_EXPIRED ) break; // Server signalled end of session | |
// if( scRet == SEC_E_INCOMPLETE_MESSAGE - Input buffer has partial encrypted record, read more | |
if( scRet != SEC_E_OK && | |
scRet != SEC_I_RENEGOTIATE && | |
scRet != SEC_I_CONTEXT_EXPIRED ) | |
{ printf("**** DecryptMessage "); | |
DisplaySECError((DWORD)scRet); | |
return scRet; } | |
// Locate data and (optional) extra buffers. | |
pDataBuffer = NULL; | |
pExtraBuffer = NULL; | |
for(i = 1; i < 4; i++) | |
{ | |
if( pDataBuffer == NULL && Buffers[i].BufferType == SECBUFFER_DATA ) pDataBuffer = &Buffers[i]; | |
if( pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA ) pExtraBuffer = &Buffers[i]; | |
} | |
// Display the decrypted data. | |
if(pDataBuffer) | |
{ | |
length = pDataBuffer->cbBuffer; | |
if( length ) // check if last two chars are CR LF | |
{ | |
buff = pDataBuffer->pvBuffer; // printf( "n-2= %d, n-1= %d \n", buff[length-2], buff[length-1] ); | |
printf("Decrypted data: %d bytes", length); PrintText( length, buff ); | |
if(fVerbose) { PrintHexDump(length, buff); printf("\n"); } | |
if( buff[length-2] == 13 && buff[length-1] == 10 ) break; // printf("Found CRLF\n"); | |
} | |
} | |
// Move any "extra" data to the input buffer. | |
if(pExtraBuffer) | |
{ | |
MoveMemory(pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer); | |
cbIoBuffer = pExtraBuffer->cbBuffer; // printf("cbIoBuffer= %d \n", cbIoBuffer); | |
} | |
else | |
cbIoBuffer = 0; | |
// The server wants to perform another handshake sequence. | |
if(scRet == SEC_I_RENEGOTIATE) | |
{ | |
printf("Server requested renegotiate!\n"); | |
scRet = ClientHandshakeLoop( Socket, phCreds, phContext, FALSE, &ExtraBuffer); | |
if(scRet != SEC_E_OK) return scRet; | |
if(ExtraBuffer.pvBuffer) // Move any "extra" data to the input buffer. | |
{ | |
MoveMemory(pbIoBuffer, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer); | |
cbIoBuffer = ExtraBuffer.cbBuffer; | |
} | |
} | |
} // Loop till CRLF is found at the end of the data | |
return SEC_E_OK; | |
} | |
/*****************************************************************************/ | |
static SECURITY_STATUS SMTPsession( SOCKET Socket, // in | |
PCredHandle phCreds, // in | |
CtxtHandle * phContext) // in | |
{ | |
SecPkgContext_StreamSizes Sizes; // unsigned long cbBuffer; // Size of the buffer, in bytes | |
SECURITY_STATUS scRet; // unsigned long BufferType; // Type of the buffer (below) | |
PBYTE pbIoBuffer; // void SEC_FAR * pvBuffer; // Pointer to the buffer | |
DWORD cbIoBufferLength, cbData; | |
// Read stream encryption properties. | |
scRet = g_pSSPI->QueryContextAttributes( phContext, SECPKG_ATTR_STREAM_SIZES, &Sizes ); | |
if(scRet != SEC_E_OK) | |
{ printf("**** Error 0x%x reading SECPKG_ATTR_STREAM_SIZES\n", scRet); return scRet; } | |
// Create a buffer. | |
cbIoBufferLength = Sizes.cbHeader + Sizes.cbMaximumMessage + Sizes.cbTrailer; | |
pbIoBuffer = LocalAlloc(LMEM_FIXED, cbIoBufferLength); | |
if(pbIoBuffer == NULL) { printf("**** Out of memory (2)\n"); return SEC_E_INTERNAL_ERROR; } | |
// Receive a Response | |
scRet = ReadDecrypt( Socket, phCreds, phContext, pbIoBuffer, cbIoBufferLength ); | |
if( scRet != SEC_E_OK ) return scRet; | |
// Build the request - must be < maximum message size | |
sprintf( pbIoBuffer+Sizes.cbHeader, "%s", "EHLO \r\n" ); // message begins after the header | |
// Send a request. | |
cbData = EncryptSend( Socket, phContext, pbIoBuffer, Sizes ); | |
if(cbData == SOCKET_ERROR || cbData == 0) | |
{ printf("**** Error %d sending data to server (3)\n", WSAGetLastError()); return SEC_E_INTERNAL_ERROR; } | |
// Receive a Response | |
scRet = ReadDecrypt( Socket, phCreds, phContext, pbIoBuffer, cbIoBufferLength ); | |
if( scRet != SEC_E_OK ) return scRet; | |
// Build the request - must be < maximum message size | |
sprintf( pbIoBuffer+Sizes.cbHeader, "%s", "QUIT \r\n" ); // message begins after the header | |
// Send a request. | |
cbData = EncryptSend( Socket, phContext, pbIoBuffer, Sizes ); | |
if(cbData == SOCKET_ERROR || cbData == 0) | |
{ printf("**** Error %d sending data to server (3)\n", WSAGetLastError()); return SEC_E_INTERNAL_ERROR; } | |
// Receive a Response | |
scRet = ReadDecrypt( Socket, phCreds, phContext, pbIoBuffer, cbIoBufferLength ); | |
if( scRet != SEC_E_OK ) return scRet; | |
return SEC_E_OK; | |
} | |
/*****************************************************************************/ | |
void _cdecl main( int argc, char *argv[] ) | |
{ | |
WSADATA WsaData; | |
SOCKET Socket = INVALID_SOCKET; | |
CredHandle hClientCreds; | |
CtxtHandle hContext; | |
BOOL fCredsInitialized = FALSE; | |
BOOL fContextInitialized = FALSE; | |
SecBuffer ExtraData; | |
SECURITY_STATUS Status; | |
PCCERT_CONTEXT pRemoteCertContext = NULL; | |
if( !LoadSecurityLibrary() ) | |
{ printf("Error initializing the security library\n"); goto cleanup; } // | |
printf("----- SSPI Initialized\n"); | |
// Initialize the WinSock subsystem. | |
if(WSAStartup(0x0101, &WsaData) == SOCKET_ERROR) // Winsock.h | |
{ printf("Error %d returned by WSAStartup\n", GetLastError()); goto cleanup; } // | |
printf("----- WinSock Initialized\n"); | |
// Create credentials. | |
if(CreateCredentials(pszUser, &hClientCreds)) | |
{ printf("Error creating credentials\n"); goto cleanup; } | |
fCredsInitialized = TRUE; // | |
printf("----- Credentials Initialized\n"); | |
// Connect to server. | |
if(ConnectToServer(pszServerName, iPortNumber, &Socket)) | |
{ printf("Error connecting to server\n"); goto cleanup; } // | |
printf("----- Connectd To Server\n"); | |
// Perform handshake | |
if( PerformClientHandshake( Socket, &hClientCreds, pszServerName, &hContext, &ExtraData ) ) | |
{ printf("Error performing handshake\n"); goto cleanup; } | |
fContextInitialized = TRUE; // | |
printf("----- Client Handshake Performed\n"); | |
// Authenticate server's credentials. Get server's certificate. | |
Status = g_pSSPI->QueryContextAttributes( &hContext, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext ); | |
if(Status != SEC_E_OK) | |
{ printf("Error 0x%x querying remote certificate\n", Status); goto cleanup; } // | |
printf("----- Server Credentials Authenticated \n"); | |
// Display server certificate chain. | |
DisplayCertChain( pRemoteCertContext, FALSE ); // | |
printf("----- Certificate Chain Displayed \n"); | |
// Attempt to validate server certificate. | |
Status = VerifyServerCertificate( pRemoteCertContext, pszServerName, 0 ); | |
if(Status) { printf("**** Error 0x%x authenticating server credentials!\n", Status); goto cleanup; } | |
// The server certificate did not validate correctly. At this point, we cannot tell | |
// if we are connecting to the correct server, or if we are connecting to a | |
// "man in the middle" attack server - Best to just abort the connection. | |
printf("----- Server Certificate Verified\n"); | |
// Free the server certificate context. | |
CertFreeCertificateContext(pRemoteCertContext); | |
pRemoteCertContext = NULL; // | |
printf("----- Server certificate context released \n"); | |
// Display connection info. | |
DisplayConnectionInfo(&hContext); // | |
printf("----- Secure Connection Info\n"); | |
// Send Request, recover response. LPSTR pszRequest = "EHLO"; | |
if( SMTPsession( Socket, &hClientCreds, &hContext ) ) | |
{ printf("Error SMTP Session \n"); goto cleanup; } // | |
printf("----- SMTP session Complete \n"); | |
// Send a close_notify alert to the server and close down the connection. | |
if(DisconnectFromServer(Socket, &hClientCreds, &hContext)) | |
{ printf("Error disconnecting from server\n"); goto cleanup; } | |
fContextInitialized = FALSE; | |
Socket = INVALID_SOCKET; // | |
printf("----- Disconnected From Server\n"); | |
cleanup: // | |
printf("----- Begin Cleanup\n"); | |
// Free the server certificate context. | |
if(pRemoteCertContext) | |
{ | |
CertFreeCertificateContext(pRemoteCertContext); | |
pRemoteCertContext = NULL; | |
} | |
// Free SSPI context handle. | |
if(fContextInitialized) | |
{ | |
g_pSSPI->DeleteSecurityContext(&hContext); | |
fContextInitialized = FALSE; | |
} | |
// Free SSPI credentials handle. | |
if(fCredsInitialized) | |
{ | |
g_pSSPI->FreeCredentialsHandle(&hClientCreds); | |
fCredsInitialized = FALSE; | |
} | |
// Close socket. | |
if(Socket != INVALID_SOCKET) closesocket(Socket); | |
// Shutdown WinSock subsystem. | |
WSACleanup(); | |
// Close "MY" certificate store. | |
if(hMyCertStore) CertCloseStore(hMyCertStore, 0); | |
UnloadSecurityLibrary(); | |
printf("----- All Done ----- \n"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment