// TLS1.3.cpp : This file contains the 'main' function. Program execution begins and ends there. // #define _CRT_SECURE_NO_WARNINGS #pragma warning(disable : 4996) #include // 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 #include #include #include #include #include #include #include #include "schannel.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 = 992; // gmail TLS LPSTR pszServerName = (LPSTR)"10.134.54.46";//(LPSTR)"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_3; ALG_ID aiKeyExch = 0; //BOOL fUseProxy = FALSE; //LPSTR pszProxyServer = (LPSTR)"proxy"; //INT iProxyPort = 992; HCERTSTORE hMyCertStore = NULL; HMODULE g_hSecurity = NULL; //SCHANNEL_CRED SchannelCred; //SCH_CREDENTIALS SchannelCred; SCH_CREDENTIALS SchannelCred = { 0 }; PSecurityFunctionTable g_pSSPI; //TlsParameters.grbitDisabledProtocols = (DWORD)~SP_PROT_TLS1_3_CLIENT; //SchannelCred.cTlsParameters = 1; /*****************************************************************************/ static void DisplayWinVerifyTrustError(DWORD Status) { LPCSTR 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) { LPCSTR 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) { LPCSTR 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_TLS1_2_CLIENT: printf("Protocol: TLS12\n"); break; case SP_PROT_TLS1_3_CLIENT: printf("Protocol: TLS1.3\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> 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; CHAR 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 (!GetVersionExA(&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; LPCSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH, szOID_SERVER_GATED_CRYPTO, szOID_SGC_NETSCAPE }; LPSTR rAgpszUsageIdentifier = NULL; DWORD cUsages = sizeof(rgszUsages) / sizeof(LPCSTR); 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 = (PWSTR)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; lstrcpy(rAgpszUsageIdentifier, (LPCSTR)rgszUsages); ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = &rAgpszUsageIdentifier; 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; TLS_PARAMETERS TlsParameters = { 0 }; // 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; SchannelCred.dwVersion = SCH_CREDENTIALS_VERSION; if(pCertContext) { SchannelCred.cCreds = 1; SchannelCred.paCred = &pCertContext; } TlsParameters.grbitDisabledProtocols = ~dwProtocol; SchannelCred.pTlsParameters = &TlsParameters; 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 (SEC_CHAR*)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(); } 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(); } *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 = (PBYTE)OutBuffers[0].pvBuffer; cbMessage = OutBuffers[0].cbBuffer; // Send the close notify message to the server. if(pbMessage != NULL && cbMessage != 0) { cbData = send(Socket,(LPCSTR)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->AcquireCredentialsHandle(NULL, // Name of principal (SEC_CHAR*)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; BYTE* 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 = (byte*)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, (CHAR*)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, (PBYTE)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 = (VOID SEC_FAR *)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, (const char*)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,(PBYTE) 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((CHANGER_ELEMENT*)IoBuffer, (const void*)(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, (LPCSTR)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, (PBYTE)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((LPCTSTR)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,(LPCTSTR) 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, (CHAR*)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 = (PBYTE)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 =(byte*) 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((char* const)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((char* const)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 = 0; 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"); }