/*++ Copyright (c) 1997 Microsoft Corporation Module Name: ssocket.cxx Abstract: Contains secure sockets functions and ICSecureSocket methods Contents: SecurityPkgInitialize ReadCertificateIntoCertInfoStruct ChkCertificateCommonNameIsValid ChkCertificateExpired ICSecureSocket::ICSecureSocket ICSecureSocket::~ICSecureSocket ICSecureSocket::Connect CFsm_SecureConnect::RunSM ICSecureSocket::Connect_Fsm ICSecureSocket::SecureHandshakeWithServer CFsm_SecureHandshake::RunSM ICSecureSocket::SecureHandshake_Fsm ICSecureSocket::NegotiateSecConnection CFsm_SecureNegotiate::RunSM ICSecureSocket::SecureNegotiate_Fsm ICSecureSocket::SSPINegotiateLoop CFsm_NegotiateLoop::RunSM ICSecureSocket::NegotiateLoop_Fsm ICSecureSocket::Disconnect ICSecureSocket::Send CFsm_SecureSend::RunSM ICSecureSocket::Send_Fsm ICSecureSocket::Receive CFsm_SecureReceive::RunSM ICSecureSocket::Receive_Fsm ICSecureSocket::SetHostName (ICSecureSocket::EncryptData) (ICSecureSocket::DecryptData) (ICSecureSocket::TerminateSecConnection) ICSecureSocket::GetCertInfo Author: Richard L Firth (rfirth) 08-Apr-1997 Environment: Win32 user mode Revision History: 08-Apr-1997 rfirth Created from ixport.cxx --*/ #include #include #include extern "C" { #include #include #include #include #include #include #include #include } // // // List of encryption packages: PCT, SSL, etc // // // BUGBUG [arthurbi] The SSL and PCT package names // are hard coded into the stucture below. We need // to be more flexible in case someone write a FOO security // package. // // BUGBUG: Don't change the order of the packages below. some old SSL2 sites deny the UNISP // provider, and if we walk down the list to PCT1 or SSL3, things hang. struct _SEC_PROVIDER SecProviders[] = { UNISP_NAME, INVALID_CRED_VALUE , ENC_CAPS_PCT | ENC_CAPS_SSL | ENC_CAPS_SCHANNEL_CREDS, FALSE, SP_PROT_CLIENTS, NULL, UNISP_NAME, INVALID_CRED_VALUE , ENC_CAPS_SSL | ENC_CAPS_SCHANNEL_CREDS, FALSE, SP_PROT_SSL2_CLIENT, NULL, // PCT1SP_NAME, INVALID_CRED_VALUE , ENC_CAPS_PCT| ENC_CAPS_SCHANNEL_CREDS, FALSE, SP_PROT_PCT1_CLIENT, NULL, // SSL3SP_NAME, INVALID_CRED_VALUE , ENC_CAPS_SSL| ENC_CAPS_SCHANNEL_CREDS, FALSE, SP_PROT_SSL3_CLIENT, NULL, NULL, INVALID_CRED_VALUE , FALSE, FALSE, 0 }; // // dwEncFlags - Global Status of calling and initalizing the SCHANNEL and various // other encyrption support DLL & APIs. Failure in the process will // cause this to be set to an error state, success prevents re-initalizaiton // DWORD dwEncFlags = 0; // // GlobalSecureProtocolsCopy - Copy of the current protocols the user wants to use // changing them allows us to restrict to specific protocols // DWORD GlobalSecureProtocolsCopy = DEFAULT_SECURE_PROTOCOLS; #ifdef SECPKG_ATTR_PROTO_INFO PRIVATE LPTSTR ProtoInfoToString( IN const PSecPkgContext_ProtoInfo pProtoInfo); #endif // // general security package functions // BOOL SecurityPkgInitialize( BOOL fForce ) /*++ Routine Description: This function finds a list of security packages that are supported on the client's machine, check if pct or ssl is supported, and create a credential handle for each supported pkg. Arguments: None Return Value: TRUE if at least one security pkg is found; otherwise FALSE --*/ { TimeStamp tsExpiry; SECURITY_STATUS scRet; PSecPkgInfo pPackageInfo = NULL; ULONG cPackages; ULONG fCapabilities; ULONG i; ULONG j; DWORD cProviders = 0; SCHANNEL_CRED DefaultCredData = {SCHANNEL_CRED_VERSION, 0, NULL, 0, 0, NULL, 0, NULL, SP_PROT_CLIENTS, 0, 0, 0, SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS }; // // Set new DWORD for our copy of the global protocol settings. // bool fSame = (GlobalSecureProtocolsCopy==GlobalSecureProtocols); GlobalSecureProtocolsCopy = GlobalSecureProtocols; // // check if this routine has been called. if yes, return TRUE // if we've found a supported pkg; otherwise FALSE // if ( dwEncFlags == ENC_CAPS_NOT_INSTALLED ) return FALSE; else if ((dwEncFlags&ENC_CAPS_TYPE_MASK) && fSame && !fForce) return TRUE; // // Initialize dwEncFlags // dwEncFlags = ENC_CAPS_NOT_INSTALLED; // // Check if at least one security package is supported // scRet = g_EnumerateSecurityPackages( &cPackages, &pPackageInfo ); if ( scRet != STATUS_SUCCESS ) { DEBUG_PRINT(API, ERROR, ("EnumerateSecurityPackages failed, error %lx\n", scRet )); SetLastError( scRet ); return FALSE; } for ( i = 0; i < cPackages ; i++ ) { // // Use only if the package name is the PCT/SSL package // fCapabilities = pPackageInfo[i].fCapabilities; if ( fCapabilities & SECPKG_FLAG_STREAM ) { // // Check if the package supports server side authentication // and all recv/sent messages are tamper proof // if ( fCapabilities & SECPKG_FLAG_CLIENT_ONLY || !(fCapabilities & SECPKG_FLAG_PRIVACY )) { continue; } // // Check if the pkg matches one of our known packages // for ( j = 0; SecProviders[j].pszName != NULL; j++ ) { if ( !stricmp( pPackageInfo[i].Name, SecProviders[j].pszName ) ) { CredHandle OldCred; PVOID pCredData = NULL; // // Create a credential handle for each supported pkg // INET_ASSERT((SecProviders[j].dwFlags & ENC_CAPS_SCHANNEL_CREDS)); pCredData = &DefaultCredData; if (SecProviders[j].pCertCtxt != NULL) { DefaultCredData.cCreds = 1; DefaultCredData.paCred = &SecProviders[j].pCertCtxt; } // // Enable Supported protocols in the Default Cred Data, then acquire the Credential // DefaultCredData.grbitEnabledProtocols = (GlobalSecureProtocols & SecProviders[j].dwProtocolFlags); OldCred.dwUpper = SecProviders[j].hCreds.dwUpper; OldCred.dwLower = SecProviders[j].hCreds.dwLower; // Zero out previous credentials SecProviders[j].hCreds.dwUpper = SecProviders[j].hCreds.dwLower = 0; WRAP_REVERT_USER(g_AcquireCredentialsHandle, (NULL, SecProviders[j].pszName, // Package SECPKG_CRED_OUTBOUND, NULL, pCredData, NULL, NULL, &(SecProviders[j].hCreds), // Handle &tsExpiry), scRet); if(!IS_CRED_INVALID(&OldCred)) { WRAP_REVERT_USER_VOID(g_FreeCredentialsHandle, (&OldCred)); } DefaultCredData.cCreds = 0; DefaultCredData.paCred = NULL; if ( scRet != STATUS_SUCCESS ) { DEBUG_PRINT(API, WARNING, ("AcquireCredentialHandle failed, error %lx\n", scRet )); SecProviders[j].fEnabled = FALSE; SecProviders[j].hCreds.dwUpper = 0xffffffff; SecProviders[j].hCreds.dwLower = 0xffffffff; } else { DEBUG_PRINT( API, INFO, ("AcquireCredentialHandle() supports %s, acquires %x:%x\n", SecProviders[j].pszName, SecProviders[j].hCreds.dwUpper, SecProviders[j].hCreds.dwLower )); SecProviders[j].fEnabled = TRUE; cProviders++; dwEncFlags |= SecProviders[j].dwFlags; } } } } } if ( !cProviders ) { // // No security packages were found, return FALSE to caller // DEBUG_PRINT(API, ERROR, ("No security packages were found, error %lx\n", SEC_E_SECPKG_NOT_FOUND )); g_FreeContextBuffer( pPackageInfo ); SetLastError( (DWORD) SEC_E_SECPKG_NOT_FOUND ); return FALSE; } // // Successfully found a security package(s) // return TRUE; } DWORD QuerySecurityInfo( IN CtxtHandle *hContext, OUT LPINTERNET_SECURITY_INFO pInfo, IN LPDWORD lpdwStatusFlag) { SECURITY_STATUS scRet; scRet = g_QueryContextAttributes(hContext, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pInfo->pCertificate ); if (scRet == ERROR_SUCCESS) { scRet = g_QueryContextAttributes(hContext, SECPKG_ATTR_CONNECTION_INFO, &pInfo->dwProtocol ); if (scRet == ERROR_SUCCESS) { pInfo->dwSize = sizeof(INTERNET_SECURITY_INFO); } } if (scRet != ERROR_SUCCESS) { // // Map the SSPI error. // return MapInternetError((DWORD) scRet, lpdwStatusFlag); } return ERROR_SUCCESS; } // Helper function to detect Fortezza connections. BOOL IsCertificateFortezza(PCCERT_CONTEXT pCertContext) { INET_ASSERT(pCertContext != NULL); if (pCertContext == NULL) return FALSE; LPSTR pszOid = pCertContext->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId; if (pszOid) { if (strcmp(pszOid, szOID_INFOSEC_mosaicUpdatedSig) == 0 || strcmp(pszOid, szOID_INFOSEC_mosaicKMandUpdSig) == 0) { return TRUE; } } return FALSE; } LONG WinVerifySecureChannel(HWND hwnd, WINTRUST_DATA *pWTD) /*++ Routine Description: Wininet's wrapper for secure channel WinVerifyTrust calls. Arguments: hWnd - in case WinVerifyTrust needs to do UI. pWTD - pointer to WINTRUST_DATA containing details about the secure channel. Passed to WinVerifyTrust. Return Value: WIN32 error code. --*/ { LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo(); BOOL async; LONG lResult; BOOL bFortezza; GUID gHTTPS = HTTPSPROV_ACTION; if (lpThreadInfo != NULL) { async = _InternetGetAsync(lpThreadInfo); _InternetSetAsync(lpThreadInfo, FALSE); } bFortezza = IsCertificateFortezza(pWTD->pCert->psCertContext); if (bFortezza && g_CryptInstallDefaultContext == NULL) { // HACK: we have no way to verify a connection without // a crypt32 which has the new APIs exposed. Till IE5 picks up // the new crypto bits we will assume Fortezza connections // verify correctly. lResult = ERROR_SUCCESS; } else { HCRYPTDEFAULTCONTEXT hCryptDefaultContext = NULL; if (bFortezza) { if (!g_CryptInstallDefaultContext( GlobalFortezzaCryptProv, CRYPT_DEFAULT_CONTEXT_CERT_SIGN_OID, szOID_INFOSEC_mosaicUpdatedSig, // check with John Banes 0, // dwFlags NULL, // pvReserved &hCryptDefaultContext )) { lResult = GetLastError(); goto quit; } } WRAP_REVERT_USER(g_WinVerifyTrust, (hwnd, &gHTTPS, pWTD), lResult); DEBUG_PUT(("WinVerifyTrust returned: %x\n", lResult)); if (hCryptDefaultContext) { // Ignore error code while freeing since we can't do anything // meaningful about it here. BOOL bResult; bResult = g_CryptUninstallDefaultContext( hCryptDefaultContext, 0, NULL); INET_ASSERT(bResult); } } quit: if (lpThreadInfo != NULL) { _InternetSetAsync(lpThreadInfo, async); } return lResult; } // // ICSecureSocket methods // ICSecureSocket::ICSecureSocket(void) /*++ Routine Description: ICSecureSocket constructor Arguments: Return Value: None. --*/ { DEBUG_ENTER((DBG_OBJECTS, None, "ICSecureSocket::ICSecureSocket", "{%#x}", this )); SIGN_SECURE_SOCKET(); m_hContext.dwLower = m_hContext.dwUpper = 0; m_dwProviderIndex = 0; m_dwFlags |= SF_SECURE; m_lpszHostName = NULL; m_pdblbufBuffer = NULL; m_pSecurityInfo = NULL; m_pCertCache = NULL; DEBUG_LEAVE(0); } ICSecureSocket::~ICSecureSocket() /*++ Routine Description: ICSecureSocket destructor. Virtual function Arguments: None. Return Value: None. --*/ { DEBUG_ENTER((DBG_OBJECTS, None, "ICSecureSocket::~ICSecureSocket", "{%#x [%q, sock=%#x, port=%d]}", this, GetHostName(), GetSocket(), GetSourcePort() )); CHECK_SECURE_SOCKET(); if (IsSecure()) { if (m_pdblbufBuffer != NULL) { delete m_pdblbufBuffer; } // Free security context associated with this object if it's // still allocated. TerminateSecConnection(); /* SCLE ref */ SetSecurityEntry(NULL); if (m_lpszHostName != NULL) { m_lpszHostName = (LPSTR)FREE_MEMORY(m_lpszHostName); INET_ASSERT(m_lpszHostName == NULL); } //if ( _pCertChainList ) // delete _pCertChainList; } DEBUG_LEAVE(0); } DWORD ICSecureSocket::Connect( IN LONG Timeout, IN INT Retries, IN DWORD dwFlags ) /*++ Routine Description: Initiate secure connection with server Arguments: Timeout - maximum amount of time (mSec) to wait for connection Retries - maximum number of attempts to connect dwFlags - flags controlling request Return Value: DWORD Success - ERROR_SUCCESS ERROR_IO_PENDING Operation will complete asynchronously Failure - ERROR_NOT_ENOUGH_MEMORY Couldn't create FSM --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::Connect", "{%#x [%#x]} %d, %d, %#x", this, m_Socket, Timeout, Retries, dwFlags )); DWORD error; // might be http CONNECT that will morph into an established tunnel if (IsSecure()) { error = DoFsm(New CFsm_SecureConnect(Timeout, Retries, dwFlags, this )); } else { // thunk to non-secure connect error = ICSocket::Connect(Timeout, Retries, dwFlags ); } DEBUG_LEAVE(error); return error; } DWORD CFsm_SecureConnect::RunSM( IN CFsm * Fsm ) { DEBUG_ENTER((DBG_SOCKETS, Dword, "CFsm_SecureConnect::RunSM", "%#x", Fsm )); ICSecureSocket * pSecureSocket = (ICSecureSocket *)Fsm->GetContext(); CFsm_SecureConnect * stateMachine = (CFsm_SecureConnect *)Fsm; DWORD error; switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pSecureSocket->Connect_Fsm(stateMachine); break; default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::Connect_Fsm( IN CFsm_SecureConnect * Fsm ) { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::Connect_Fsm", "%#x", Fsm )); CFsm_SecureConnect & fsm = *Fsm; DWORD error = fsm.GetError(); if (fsm.GetState() != FSM_STATE_INIT) { switch (fsm.GetFunctionState()) { case FSM_STATE_2: goto connect_continue; case FSM_STATE_3: goto negotiate_continue; default: error = ERROR_WINHTTP_INTERNAL_ERROR; INET_ASSERT(FALSE); goto quit; } } m_dwProviderIndex = 0; // // Hack for SSL2 Client Hello, set to FALSE, // but if we fail on the first recv, fReOpenSocket // is set to TRUE. // do { // // Attempt to do the connect // fsm.SetFunctionState(FSM_STATE_2); error = ICSocket::Connect(fsm.m_Timeout, fsm.m_Retries, fsm.m_dwFlags); connect_continue: if (error != ERROR_SUCCESS) { break; } if (m_dwFlags & SF_ENCRYPT) { fsm.SetFunctionState(FSM_STATE_3); error = SecureHandshakeWithServer(fsm.m_dwFlags, &fsm.m_bAttemptReconnect); if (error == ERROR_IO_PENDING) { break; } negotiate_continue: // // SSL2 hack for old IIS servers. // We re-open the socket, and call again. // if ((error != ERROR_SUCCESS) && fsm.m_bAttemptReconnect) { Disconnect(fsm.m_dwFlags); } } } while (fsm.m_bAttemptReconnect); quit: if (error != ERROR_IO_PENDING) { fsm.SetDone(); if ((error != ERROR_SUCCESS) && IsOpen()) { Disconnect(fsm.m_dwFlags); } } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::SecureHandshakeWithServer( IN DWORD dwFlags, OUT LPBOOL lpbAttemptReconnect ) /*++ Routine Description: For SSL/PCT or some secure channel this function attempts to use an arbitrary Socket for handshaking with a server. The assumption is made that caller can recall this function on failure Arguments: dwFlags - lpbAttemptReconnect - Return Value: DWORD Success - ERROR_SUCCESS Failure - WSA error --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::SecureHandshakeWithServer", "%#x, %#x [%B]", dwFlags, lpbAttemptReconnect, *lpbAttemptReconnect )); INET_ASSERT(IsSecure()); DWORD error = DoFsm(New CFsm_SecureHandshake(dwFlags, lpbAttemptReconnect, this )); DEBUG_LEAVE(error); return error; } DWORD CFsm_SecureHandshake::RunSM( IN CFsm * Fsm ) { DEBUG_ENTER((DBG_SOCKETS, Dword, "CFsm_SecureHandshake::RunSM", "%#x", Fsm )); ICSecureSocket * pSecureSocket = (ICSecureSocket *)Fsm->GetContext(); CFsm_SecureHandshake * stateMachine = (CFsm_SecureHandshake *)Fsm; DWORD error; switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pSecureSocket->SecureHandshake_Fsm(stateMachine); break; default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::SecureHandshake_Fsm( IN CFsm_SecureHandshake * Fsm ) { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::SecureHandshake_Fsm", "%#x", Fsm )); CFsm_SecureHandshake & fsm = *Fsm; DWORD error = fsm.GetError(); DWORD dwSecureFlags; DWORD dwCertFlags; BOOL fErrorInvalidCa; if (fsm.GetState() != FSM_STATE_INIT) { switch (fsm.GetFunctionState()) { case FSM_STATE_2: goto negotiate_continue; default: error = ERROR_WINHTTP_INTERNAL_ERROR; INET_ASSERT(FALSE); goto quit; } } //INET_ASSERT(fsm.m_dwFlags & SF_ENCRYPT); INET_ASSERT(m_Socket != INVALID_SOCKET); *fsm.m_lpbAttemptReconnect = FALSE; error = ERROR_SUCCESS; // // Save Off Flags in our Internal Object. // Treat SF_ENCRYPT just like SF_DECRYPT // m_dwFlags |= SF_DECRYPT; m_dwFlags |= fsm.m_dwFlags; INET_ASSERT(!(m_dwFlags & ~(SF_NON_BLOCKING | SF_SECURE | SF_ENCRYPT | SF_DECRYPT | SF_INDICATE | SF_SENDING_DATA ))); // // Allocate Internal Buffer for SSL/PCT data. // if (m_pdblbufBuffer == NULL) { BOOL fInitSuccess; m_pdblbufBuffer = New DBLBUFFER(); if (m_pdblbufBuffer == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } fInitSuccess = m_pdblbufBuffer->InitBuffer(TRUE); if (!fInitSuccess) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } } // First make sure the security dlls are loaded. error = LoadSecurity(); if (error != ERROR_SUCCESS) { goto quit; } // If the user has the Fortezza CSP but has not logged on to the card yet. // return back an error to indicate that we need to put up additional UI. // if (IsFortezzaInstalled( ) && !AttemptedFortezzaLogin( )) //{ // error = ERROR_WINHTTP_FORTEZZA_LOGIN_NEEDED; // goto quit; //} // // dwEncFlags is a global flag set to the // supported security pkg mask // if (!LOCK_SECURITY()) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } if (dwEncFlags == ENC_CAPS_NOT_INSTALLED) { error = (DWORD)SEC_E_SECPKG_NOT_FOUND; } else if (dwEncFlags == 0) { // // first time thru, do the load. // DEBUG_PRINT(SOCKETS, INFO, ("Loading security dll\n" )); if ( !SecurityPkgInitialize() ) { error = GetLastError(); UNLOCK_SECURITY(); goto quit; } } UNLOCK_SECURITY(); if ( error != ERROR_SUCCESS ) { goto quit; } // // If we succeed in loading or and initalizing the Security DLLs, we // attempt to negotiate the connection // DEBUG_PRINT(SOCKETS, INFO, ("Negotiate secure channel\n" )); // // Turn of Encryption/Decryption before the handshake, // since the NegotiateSecConnection does its own Send and Recvs // of specialized data. // m_dwFlags &= ~(SF_ENCRYPT | SF_DECRYPT); fsm.SetFunctionState(FSM_STATE_2); error = NegotiateSecConnection(fsm.m_dwFlags, fsm.m_lpbAttemptReconnect ); if (error == ERROR_IO_PENDING) { goto quit; } negotiate_continue: m_dwFlags |= (SF_ENCRYPT | SF_DECRYPT); if (error != ERROR_SUCCESS) { goto quit; } // // Find out what size Key we're using, and set the flags // acordingly. // dwSecureFlags = 0; if (m_pSecurityInfo && !m_pSecurityInfo->InCache()) { error = VerifyTrust(); if (error != ERROR_SUCCESS) { goto quit; } } // // we've got a secure connection, set the flags. // SetSecure(); if(m_pSecurityInfo) { INTERNET_SECURITY_INFO ciInfo; m_pSecurityInfo->CopyOut(ciInfo); if(ciInfo.dwCipherStrength < 56) { SetSecureFlags(SECURITY_FLAG_STRENGTH_WEAK); } else if (ciInfo.dwCipherStrength==80 && (ciInfo.aiCipher == CALG_SKIPJACK || ciInfo.aiCipher==CALG_TEK)) { SetSecureFlags(SECURITY_FLAG_FORTEZZA); } else if(ciInfo.dwCipherStrength < 96) { SetSecureFlags(SECURITY_FLAG_STRENGTH_MEDIUM); } else { SetSecureFlags(SECURITY_FLAG_STRENGTH_STRONG); } if(ciInfo.pCertificate) { WRAP_REVERT_USER_VOID(CertFreeCertificateContext, (ciInfo.pCertificate)); ciInfo.pCertificate = NULL; } } quit: if (error != ERROR_IO_PENDING) { fsm.SetDone(); } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::VerifyTrust( ) /*++ Routine Description: This function establishes a secure channel with the server by performing the security handshake protocol. It will walk the list of installed security packages until it finds a package that succeeds in the security handshake. If one package fails it is up to the caller to re-call NegotiateSecConnection with a re-opened socket so another socket can attempt the connection. Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { // We've done our handshake, now update the security info INTERNET_SECURITY_INFO ciCert; DWORD dwCertFlags = 0; WINTRUST_DATA sWTD; WINTRUST_CERT_INFO sWTCI; HTTPSPolicyCallbackData polHttps; DWORD cbServerName; DWORD error; DWORD dwStatusFlag = 0; HINTERNET hInternet; HINTERNET hInternetMapped; LPINTERNET_THREAD_INFO lpThreadInfo = NULL; DWORD dwFlags = 0; // HTTPS policy flags to ignore errors DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::VerifyTrust", "{%#x", this )); // HACK HACK: 67640 // WinVerifyTrust can do a nested HttpSendRequest which causes the hObject's on the // thread to get messed up. This happens only when the ceritificate has a URL for // a CRL in it. We save and restore these values to workaround the problem. // Need to work out a better solution to handle this but it is too close to ship to // try anything with greater code impact. lpThreadInfo = InternetGetThreadInfo(); if (lpThreadInfo != NULL) { hInternet = lpThreadInfo->hObject; hInternetMapped = lpThreadInfo->hObjectMapped; } error = QuerySecurityInfo(&m_hContext, &ciCert, &dwStatusFlag); if (error != ERROR_SUCCESS) { if (m_pSecurityInfo) m_pSecurityInfo->SetStatusFlags(dwStatusFlag); goto quit; } if(m_pSecurityInfo) { *m_pSecurityInfo = &ciCert; // Other flags have been filtered already, but make sure revocation // flag is removed, so it isn't passed to WVT. dwCertFlags = m_pSecurityInfo->GetSecureFlags() & ~(SECURITY_FLAG_CHECK_REVOCATION | SECURITY_FLAG_BREAK_ON_STATUS_SECURE_FAILURE); } if (ciCert.pCertificate == NULL) { error = ERROR_WINHTTP_INTERNAL_ERROR; goto quit; } memset(&sWTD, 0x00, sizeof(WINTRUST_DATA)); sWTD.cbStruct = sizeof(WINTRUST_DATA); sWTD.dwUIChoice = WTD_UI_NONE; sWTD.pPolicyCallbackData = (LPVOID)&polHttps; sWTD.dwUnionChoice = WTD_CHOICE_CERT; sWTD.pCert = &sWTCI; sWTD.pwszURLReference = NULL; if (m_pSecurityInfo && (m_pSecurityInfo->GetSecureFlags() & SECURITY_FLAG_CHECK_REVOCATION)) { sWTD.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; } else { sWTD.fdwRevocationChecks = 0; } memset(&sWTCI, 0x00, sizeof(WINTRUST_CERT_INFO)); sWTCI.cbStruct = sizeof(WINTRUST_CERT_INFO); sWTCI.psCertContext = (CERT_CONTEXT *)ciCert.pCertificate; sWTCI.chStores = 1; sWTCI.pahStores = (HCERTSTORE *)&ciCert.pCertificate->hCertStore; memset(&polHttps, 0x00, sizeof(HTTPSPolicyCallbackData)); polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); polHttps.dwAuthType = AUTHTYPE_SERVER; polHttps.fdwChecks = dwCertFlags; cbServerName = MultiByteToWideChar(CP_ACP, 0, m_lpszHostName, -1, NULL, 0); polHttps.pwszServerName = New WCHAR[cbServerName+1]; if(polHttps.pwszServerName == 0) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } sWTCI.pcwszDisplayName = polHttps.pwszServerName; cbServerName = MultiByteToWideChar(CP_ACP, 0, m_lpszHostName, -1, polHttps.pwszServerName, cbServerName); error = LoadWinTrust(); if(ERROR_SUCCESS == error) { error = WinVerifySecureChannel(NULL, &sWTD); } error = MapInternetError(error, &dwStatusFlag); // // If there was problem with the certificate and the caller requested // combined SSL errors cycle through all possible certificate errors. // if (ERROR_SUCCESS != error && m_pSecurityInfo) { BOOL fCertError = FALSE; do { if (dwStatusFlag & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA) { polHttps.fdwChecks |= DLG_FLAGS_IGNORE_INVALID_CA; dwFlags |= DLG_FLAGS_INVALID_CA; m_pSecurityInfo->SetStatusFlags(WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA); if (!(dwCertFlags & SECURITY_FLAG_IGNORE_UNKNOWN_CA)) fCertError = TRUE; } else if (dwStatusFlag & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID) { polHttps.fdwChecks |= DLG_FLAGS_IGNORE_CERT_CN_INVALID; dwFlags |= DLG_FLAGS_SEC_CERT_CN_INVALID; m_pSecurityInfo->SetStatusFlags(WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID); if (!(dwCertFlags & INTERNET_FLAG_IGNORE_CERT_CN_INVALID)) fCertError = TRUE; } else if (dwStatusFlag & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID) { polHttps.fdwChecks |= DLG_FLAGS_IGNORE_CERT_DATE_INVALID; dwFlags |= DLG_FLAGS_SEC_CERT_DATE_INVALID; m_pSecurityInfo->SetStatusFlags(WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID); if (!(dwCertFlags & INTERNET_FLAG_IGNORE_CERT_DATE_INVALID)) fCertError = TRUE; } else if (dwStatusFlag & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED) { // In wininet, we break out on revoked and only pass it back. // This is because the UI is broken into only 2 buckets, and // revoked takes precedence. sWTD.fdwRevocationChecks = 0; m_pSecurityInfo->SetStatusFlags(WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED); fCertError = TRUE; } else if (dwStatusFlag & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED) { // In wininet, this was handled outside the loop as a special // case. There's no need for that now, so OR the error in and // continue. sWTD.fdwRevocationChecks = 0; dwFlags |= DLG_FLAGS_SEC_CERT_REV_FAILED; m_pSecurityInfo->SetStatusFlags(WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED); fCertError = TRUE; } else { // // Pass all other errors through. // break; } error = WinVerifySecureChannel(NULL, &sWTD); error = MapInternetError(error, &dwStatusFlag); } while (ERROR_SUCCESS != error); // // Change the error only if one of the known certifciate errors was // encountered. // if (m_pSecurityInfo->GetStatusFlags()) { // Just send a status notification if there's an error, // and let the app decide if this should be closed as a failure. if (lpThreadInfo) { WINHTTP_STATUS_CALLBACK appCallback = NULL; RGetStatusCallback(hInternetMapped, &appCallback); if (appCallback) { DWORD dwStatusFlags = m_pSecurityInfo->GetStatusFlags(); // Report SSL status of all errors found InternetIndicateStatus(WINHTTP_CALLBACK_STATUS_SECURE_FAILURE, &dwStatusFlags, sizeof(dwStatusFlags) ); } } // ...except if the client told us upfront to fail. if (fCertError && (m_pSecurityInfo->GetSecureFlags() & SECURITY_FLAG_BREAK_ON_STATUS_SECURE_FAILURE)) { error = ERROR_WINHTTP_SECURE_FAILURE; } else { error = SUCCESS; } m_pSecurityInfo->SetSecureFlags(dwFlags); } } delete polHttps.pwszServerName; if(ciCert.pCertificate) { WRAP_REVERT_USER_VOID(CertFreeCertificateContext, (ciCert.pCertificate)); } if (error != ERROR_SUCCESS) { goto quit; } if(m_pSecurityInfo && (!m_pSecurityInfo->InCache())) { // Add it to the cache if it's not already there. /* SCLE ref */ // Use global or session cache, depending on the settings // This was mapped when initializing the socket with SetHostName if (m_pCertCache) m_pCertCache->Add(m_pSecurityInfo); } quit: if (lpThreadInfo != NULL) { _InternetSetObjectHandle(lpThreadInfo, hInternet, hInternetMapped); } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::NegotiateSecConnection( IN DWORD dwFlags, OUT LPBOOL lpbAttemptReconnect ) /*++ Routine Description: This function establishes a secure channel with the server by performing the security handshake protocol. It will walk the list of installed security packages until it finds a package that succeeds in the security handshake. If one package fails it is up to the caller to re-call NegotiateSecConnection with a re-opened socket so another socket can attempt the connection. Arguments: dwFlags - Socket Flags that may need to be passed on to Socket Calls (needed to support Async I/O) lpbAttemptReconnect - on return, if this value is TRUE, the caller should call this function again, and it will try another protocol. Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::NegotiateSecConnection", "{%#x [%#x]} %#x, %#x [%B]", this, m_Socket, dwFlags, lpbAttemptReconnect, *lpbAttemptReconnect )); INET_ASSERT(IsSecure()); DWORD error = DoFsm(New CFsm_SecureNegotiate(dwFlags, lpbAttemptReconnect, this )); DEBUG_LEAVE(error); return error; } DWORD CFsm_SecureNegotiate::RunSM( IN CFsm * Fsm ) { DEBUG_ENTER((DBG_SOCKETS, Dword, "CFsm_SecureNegotiate::RunSM", "%#x", Fsm )); ICSecureSocket * pSecureSocket = (ICSecureSocket *)Fsm->GetContext(); CFsm_SecureNegotiate * stateMachine = (CFsm_SecureNegotiate *)Fsm; DWORD error; switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pSecureSocket->SecureNegotiate_Fsm(stateMachine); break; default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::SecureNegotiate_Fsm( IN CFsm_SecureNegotiate * Fsm ) { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::SecureNegotiate_Fsm", "%#x", Fsm )); CFsm_SecureNegotiate & fsm = *Fsm; DWORD error = fsm.GetError(); DWORD dwSSPIFlags = 0; DWORD dwStatusFlag = 0; if (fsm.GetState() != FSM_STATE_INIT) { switch (fsm.GetFunctionState()) { case FSM_STATE_2: goto send_continue; case FSM_STATE_3: goto negotiate_loop_continue; default: error = ERROR_WINHTTP_INTERNAL_ERROR; INET_ASSERT(FALSE); goto quit; } } INET_ASSERT(IsOpen()); *fsm.m_lpbAttemptReconnect = FALSE; // // set OutBuffer for InitializeSecurityContext call // fsm.m_OutBuffer.cBuffers = 1; fsm.m_OutBuffer.pBuffers = fsm.m_OutBuffers; fsm.m_OutBuffer.ulVersion = SECBUFFER_VERSION; if(GlobalSecureProtocols != GlobalSecureProtocolsCopy) { if (LOCK_SECURITY()) { //ReInit the credentials if our settings have changed. SecurityPkgInitialize(); UNLOCK_SECURITY(); } else { error = ERROR_NOT_ENOUGH_MEMORY; goto error_exit; } } // // Pick the provider we're going to use. // while ((SecProviders[GetProviderIndex()].pszName != NULL) && ( !SecProviders[GetProviderIndex()].fEnabled || !(SecProviders[GetProviderIndex()].dwProtocolFlags & GlobalSecureProtocols) ) ) { // // Next provider // SetProviderIndex(GetProviderIndex() + 1); } if (SecProviders[GetProviderIndex()].pszName == NULL) { // // BUGBUG shouldn't we error out here? // SetProviderIndex(0); goto error_exit; } DWORD i; i = GetProviderIndex(); DEBUG_PRINT(API, INFO, ("Starting handshake protocol with pkg %d - %s\n", i, SecProviders[i].pszName )); // // 1. initiate a client HELLO message and generate a token // fsm.m_OutBuffers[0].pvBuffer = NULL; fsm.m_OutBuffers[0].BufferType = SECBUFFER_TOKEN; SECURITY_STATUS scRet; DWORD ContextAttr; TimeStamp tsExpiry; fsm.m_bDoingClientAuth = FALSE; // Resynchronize the certificate store to catch // recently installed certificates if (g_hMyCertStore) { WRAP_REVERT_USER_VOID(CertControlStore, (g_hMyCertStore, 0, CERT_STORE_CTRL_AUTO_RESYNC, NULL)); } // // We need a credential handle, // if we're doing client do the magic to get a specialized // one otherwise use the standard global one. // if ( IsCredClear(fsm.m_hCreds) ) { fsm.m_hCreds = SecProviders[i].hCreds; if (GetCertContextArray()) { if (GetCertContextArray()->GetSelectedCertContext()) { error = CliAuthSelectCredential( &m_hContext, SecProviders[i].pszName, GetCertContextArray(), &fsm.m_hCreds, &dwStatusFlag); if (error != ERROR_SUCCESS) { goto quit; } fsm.m_bDoingClientAuth = TRUE; } dwSSPIFlags |= ISC_REQ_USE_SUPPLIED_CREDS; } } // Force a full handshake when per session and entry isn't in the cache. if (m_pCertCache != &GlobalCertCache && !m_pSecurityInfo->InCache()) dwSSPIFlags |= ISC_REQ_USE_SESSION_KEY; WRAP_REVERT_USER(g_InitializeSecurityContext, (&fsm.m_hCreds, NULL, (LPSTR)GetHostName(), ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY | dwSSPIFlags, 0, SECURITY_NATIVE_DREP, NULL, // default, don't do hack. 0, &m_hContext, &fsm.m_OutBuffer, // address where output data go &ContextAttr, &tsExpiry), scRet); DEBUG_PRINT(API, INFO, ("1. InitializeSecurityContext returned %s [%x]. hContext = %#x:%#x\n", InternetMapSSPIError((DWORD)scRet), scRet, m_hContext.dwUpper, m_hContext.dwLower )); if (scRet == SEC_E_INVALID_HANDLE) { SecProviders[i].fEnabled = FALSE; } if (scRet == SEC_E_INVALID_TOKEN) { error = ERROR_WINHTTP_CANNOT_CONNECT; } else { // // Turn the error in to one we understand */ // error = MapInternetError((DWORD)scRet, &dwStatusFlag); } if (scRet != SEC_I_CONTINUE_NEEDED) { goto error_exit; } DEBUG_PRINT(API, INFO, ("1. OutBuffer is <%x, %d, %x>\n", fsm.m_OutBuffers[0].pvBuffer, fsm.m_OutBuffers[0].cbBuffer, fsm.m_OutBuffers[0].BufferType )); if ((fsm.m_OutBuffers[0].cbBuffer != 0) && (fsm.m_OutBuffers[0].pvBuffer != NULL)) { // // Send response to server if there is one // fsm.SetFunctionState(FSM_STATE_2); error = ICSocket::Send(fsm.m_OutBuffers[0].pvBuffer, fsm.m_OutBuffers[0].cbBuffer, 0 ); if (error == ERROR_IO_PENDING) { goto quit; } send_continue: g_FreeContextBuffer(fsm.m_OutBuffers[0].pvBuffer); fsm.m_OutBuffers[0].pvBuffer = NULL; if (error != ERROR_SUCCESS) { // // We should deal with this better // goto error_exit; } } fsm.SetFunctionState(FSM_STATE_3); error = SSPINegotiateLoop(NULL, fsm.m_dwFlags, fsm.m_hCreds, TRUE, fsm.m_bDoingClientAuth); // // We're not actually deleting the handle, rather we're no longer keeping // a reference to the Credential handle in our fsm after we hand it off // if ( fsm.m_bDoingClientAuth ) { ClearCreds(fsm.m_hCreds); fsm.m_bDoingClientAuth = FALSE; } if (error == ERROR_IO_PENDING) { goto quit; } negotiate_loop_continue: error_exit: if (error == ERROR_WINHTTP_SECURE_FAILURE && m_pSecurityInfo) m_pSecurityInfo->SetStatusFlags(dwStatusFlag); if (error == ERROR_WINHTTP_CANNOT_CONNECT) { // // error was a CANNOT_CONNECT, so try the next protocol. // SetProviderIndex(GetProviderIndex() + 1); if (SecProviders[GetProviderIndex()].pszName == NULL) { SetProviderIndex(0); *fsm.m_lpbAttemptReconnect = FALSE; } else { *fsm.m_lpbAttemptReconnect = TRUE; } } quit: if (error != ERROR_IO_PENDING) { fsm.SetDone(); } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::SSPINegotiateLoop( OUT DBLBUFFER * pdblbufBuffer, IN DWORD dwFlags, CredHandle hCreds, IN BOOL bDoInitialRead, IN BOOL bDoingClientAuth ) /*++ Routine Description: This function completes the handshakes needed to establish a security protocol. The initial handshakes are either generated by NegotiateSecureConnection, when generating a new connection, or during a receive when a REDO request is received. Arguments: pdblbufBuffer - an input buffer into which to put any Extra data left over after the handshake. This data is assumed to be application data, and will be decrypted later. dwFlags - Socket Flags that may need to be passed on to Socket Calls (needed to support Async I/O) bDoInitialRead - if TRUE, this function will do a read before calling InitializeSecurityContext, otherwise, it passes in 0 bytes of data. Return Value: ERROR_SUCCESS - we successfully completed our connection. ERROR_WINHTTP_CANNOT_CONNECT - The connection was dropped on us, possibly because we used a bad protocol. Try the next protocol. ERROR_* - Other internet error, disconnect. Comments: BUGBUG (hack alert) [arthurbi] Do to a bug in IIS 1.0 Servers we cannot connect because we send a "Client SSL 3 Message". This message confuses the server and causes it to close the socket. The fix is to reopen the socket and send a "Client SSL 2 Message." Newer versions of the server will be fixed. --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::SSPINegotiateLoop", "{%#x [%#x]} %#x, %#x, %B", this, m_Socket, pdblbufBuffer, dwFlags, bDoInitialRead )); INET_ASSERT(IsSecure()); DWORD error = DoFsm(New CFsm_NegotiateLoop(pdblbufBuffer, dwFlags, bDoInitialRead, bDoingClientAuth, hCreds, this )); DEBUG_LEAVE(error); return error; } DWORD CFsm_NegotiateLoop::RunSM( IN CFsm * Fsm ) { DEBUG_ENTER((DBG_SOCKETS, Dword, "CFsm_NegotiateLoop::RunSM", "%#x", Fsm )); ICSecureSocket * pSecureSocket = (ICSecureSocket *)Fsm->GetContext(); CFsm_NegotiateLoop * stateMachine = (CFsm_NegotiateLoop *)Fsm; DWORD error; switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pSecureSocket->NegotiateLoop_Fsm(stateMachine); break; default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::NegotiateLoop_Fsm( IN CFsm_NegotiateLoop * Fsm ) { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::NegotiateLoop_Fsm", "%#x", Fsm )); CFsm_NegotiateLoop & fsm = *Fsm; DWORD error = fsm.GetError(); DWORD dwStatus = 0; if (fsm.GetState() != FSM_STATE_INIT) { switch (fsm.GetFunctionState()) { case FSM_STATE_2: goto receive_continue; case FSM_STATE_3: goto send_continue; default: error = ERROR_WINHTTP_INTERNAL_ERROR; INET_ASSERT(FALSE); goto quit; } } INET_ASSERT(IsOpen()); fsm.m_dwProviderIndex = GetProviderIndex(); fsm.m_dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY | ISC_RET_EXTENDED_ERROR; // // set OutBuffer for InitializeSecurityContext call // fsm.m_OutBuffer.cBuffers = 1; fsm.m_OutBuffer.pBuffers = fsm.m_OutBuffers; fsm.m_OutBuffer.ulVersion = SECBUFFER_VERSION; // // If we have a selected cert chain, then try to // generate a credential from that list. // if (IsCredClear(fsm.m_hCreds)) { fsm.m_hCreds = SecProviders[fsm.m_dwProviderIndex].hCreds; if ( GetCertContextArray() && GetCertContextArray()->GetSelectedCertContext() ) { error = CliAuthSelectCredential( &m_hContext, SecProviders[fsm.m_dwProviderIndex].pszName, GetCertContextArray(), &fsm.m_hCreds, &dwStatus); if (error != ERROR_SUCCESS) { goto quit; } fsm.m_bDoingClientAuth = TRUE; } } if (fsm.m_bDoingClientAuth || GetCertContextArray() ) { fsm.m_dwSSPIFlags |= ISC_REQ_USE_SUPPLIED_CREDS; } // Force a full handshake when per session and entry isn't in the cache. if (m_pCertCache == &GlobalCertCache && !m_pSecurityInfo->InCache()) fsm.m_dwSSPIFlags |= ISC_REQ_USE_SESSION_KEY; fsm.m_scRet = SEC_I_CONTINUE_NEEDED; while (fsm.m_scRet == SEC_I_CONTINUE_NEEDED || fsm.m_scRet == SEC_E_INCOMPLETE_MESSAGE || fsm.m_scRet == SEC_I_INCOMPLETE_CREDENTIALS) { // // send to target server // if we've got a SEC_E_INCOMPLETE_MESSAGE we need to do a read // again because we didn't get the entire message we expected from // the server. // // // receive response from server and pass token into security pkg // BUT only if we haven't already received extra data // from SSPI which we need to process in lu of actual data // data from WinSock, and if the package has not returned // one of the defined warnings that indicates that we should // pass the previous buffer again. // // Make sure fsm.m_lpszBuffer holds the input data to be passed // to initialize security context. There are 4 cases: // 1) We have Extra Data, so we don't need to do a socket receive // 2) We were called during a re-negotiate, so if this is the first // time through the loop, we have 0 bytes. // 3) We are recovering from a SEC_I_INCOMPLETE_CREDENTIALS, so // use the same buffer again. // 4) We do a SocketReceive // We'll indicate 1 and 3 by having the fsm.m_dwBytesReceived count being the number of bytes // left in the buffer to be re-sent or sent to the sspi call. // If bytes-received is zero, then either we are doing a Redo, or we need to receive // data. fsm.m_bDoRead let's us know if for some reason we should do or not do this read if ((0 == fsm.m_dwBytesReceived) || (fsm.m_scRet == SEC_E_INCOMPLETE_MESSAGE)) { if (fsm.m_bDoRead) { fsm.SetFunctionState(FSM_STATE_2); error = ICSocket::Receive((LPVOID *)&fsm.m_lpszBuffer, &fsm.m_dwBufferLength, &fsm.m_dwBufferLeft, &fsm.m_dwBytesReceived, 0, SF_EXPAND, &fsm.m_bEofReceive ); if (error == ERROR_IO_PENDING) { goto done; } receive_continue: if ((error != ERROR_SUCCESS) || fsm.m_bEofReceive) { DEBUG_PRINT(API, ERROR, ("SocketReceive failed\n" )); if (error == ERROR_SUCCESS) { error = ERROR_WINHTTP_CANNOT_CONNECT; } break; } } else { fsm.m_bDoRead = TRUE; } } if (fsm.m_scRet == SEC_I_INCOMPLETE_CREDENTIALS) { CERT_CONTEXT_ARRAY* pCertContextArray; // // If've already done Client Auth, and it fails again // then we fail. // if (fsm.m_bDoingClientAuth) { error = ERROR_CANCELLED; goto quit; } // // If we don't already have a cert chain list, // then get one, and make our selection // INET_ASSERT(!GetCertContextArray()); pCertContextArray = NULL; //delete pCertChainList; //SetCertChainList(NULL); error = CliAuthAcquireCertContexts( &m_hContext, SecProviders[fsm.m_dwProviderIndex].pszName, &pCertContextArray, &dwStatus ); SetCertContextArray(pCertContextArray); if (error == ERROR_SUCCESS) { error = ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED; } fsm.m_scRet = error; break; } // // InBuffers[1] is for getting extra data that // SSPI/SCHANNEL doesn't proccess on this // run around the loop. // fsm.m_InBuffers[0].pvBuffer = fsm.m_lpszBuffer; fsm.m_InBuffers[0].cbBuffer = fsm.m_dwBytesReceived; fsm.m_InBuffers[0].BufferType = SECBUFFER_TOKEN; fsm.m_InBuffers[1].pvBuffer = NULL; fsm.m_InBuffers[1].cbBuffer = 0; fsm.m_InBuffers[1].BufferType = SECBUFFER_EMPTY; // // Initialize these so if we fail, pvBuffer contains NULL, // so we don't try to free random garbage at the quit // fsm.m_OutBuffers[0].pvBuffer = NULL; fsm.m_OutBuffers[0].BufferType = SECBUFFER_TOKEN; fsm.m_OutBuffers[0].cbBuffer = 0; SecBufferDesc InBuffer; InBuffer.cBuffers = 2; InBuffer.pBuffers = fsm.m_InBuffers; InBuffer.ulVersion = SECBUFFER_VERSION; DWORD ContextAttr; TimeStamp tsExpiry; WRAP_REVERT_USER(g_InitializeSecurityContext, (&fsm.m_hCreds, &m_hContext, NULL, fsm.m_dwSSPIFlags, 0, SECURITY_NATIVE_DREP, &InBuffer, 0, NULL, &fsm.m_OutBuffer, &ContextAttr, &tsExpiry), fsm.m_scRet); DEBUG_PRINT(API, INFO, ("3. InitializeSecurityContext returned %s [%x]\n", InternetMapSSPIError((DWORD)fsm.m_scRet), fsm.m_scRet )); if (fsm.m_scRet == STATUS_SUCCESS || fsm.m_scRet == SEC_I_CONTINUE_NEEDED || (FAILED(fsm.m_scRet) && (0 != (ContextAttr & ISC_RET_EXTENDED_ERROR)))) { if (fsm.m_OutBuffers[0].cbBuffer != 0 && fsm.m_OutBuffers[0].pvBuffer != NULL ) { // // Send response to server if there is one // fsm.SetFunctionState(FSM_STATE_3); error = ICSocket::Send(fsm.m_OutBuffers[0].pvBuffer, fsm.m_OutBuffers[0].cbBuffer, 0 ); if (error == ERROR_IO_PENDING) { goto done; } send_continue: g_FreeContextBuffer(fsm.m_OutBuffers[0].pvBuffer); fsm.m_OutBuffers[0].pvBuffer = NULL; } } if ( fsm.m_scRet == STATUS_SUCCESS ) { DEBUG_PRINT(API, INFO, ("NegotiateSecConnection succeeded.\n")); if (fsm.m_pdblbufBuffer) { if ( fsm.m_InBuffers[1].BufferType == SECBUFFER_EXTRA ) { fsm.m_pdblbufBuffer->CopyIn( (LPBYTE) (fsm.m_lpszBuffer + (fsm.m_dwBytesReceived - fsm.m_InBuffers[1].cbBuffer)), fsm.m_InBuffers[1].cbBuffer ); } else { fsm.m_pdblbufBuffer->SetInputBufferSize(0); } } // // Bail out to quit // break; } else if (FAILED(fsm.m_scRet) && (fsm.m_scRet != SEC_E_INCOMPLETE_MESSAGE)) { // // free security context handle and delete the local // data structures associated with the handle and // try another pkg if available // DEBUG_PRINT(API, INFO, ("3. InitializeSecurityContext failed, %lx\n", fsm.m_scRet )); // Turn the error in to one we understand */ error = MapInternetError((DWORD)fsm.m_scRet, &dwStatus); TerminateSecConnection(); /* Break out to try next protocol */ break; } if ((fsm.m_scRet != SEC_E_INCOMPLETE_MESSAGE) && (fsm.m_scRet != SEC_I_INCOMPLETE_CREDENTIALS)) { DEBUG_PRINT(API, INFO, ("3. OutBuffer is <%x, %d, %x>\n", fsm.m_OutBuffers[0].pvBuffer, fsm.m_OutBuffers[0].cbBuffer, fsm.m_OutBuffers[0].BufferType )); if (fsm.m_InBuffers[1].BufferType == SECBUFFER_EXTRA) { // // skip next recv and set up buffers // so InitalizeSecurityContext pulls its // info from the Extra it returned previously. // DEBUG_PRINT(API, INFO, ("Got SECBUFFER_EXTRA, moving %d bytes to front of buffer\n", fsm.m_InBuffers[1].cbBuffer )); INET_ASSERT(fsm.m_InBuffers[1].cbBuffer > 0); MoveMemory( fsm.m_lpszBuffer, // dest fsm.m_lpszBuffer + (fsm.m_dwBytesReceived - fsm.m_InBuffers[1].cbBuffer), fsm.m_InBuffers[1].cbBuffer // size ); fsm.m_dwBytesReceived = fsm.m_InBuffers[1].cbBuffer; fsm.m_dwBufferLeft = fsm.m_dwBufferLength - fsm.m_dwBytesReceived; } else { // // prepare for next receive // fsm.m_dwBufferLeft = fsm.m_dwBufferLength; fsm.m_dwBytesReceived = 0; } } } quit: if (fsm.m_lpszBuffer != NULL) { fsm.m_lpszBuffer = (LPSTR)FREE_MEMORY(fsm.m_lpszBuffer); INET_ASSERT(fsm.m_lpszBuffer == NULL); } done: if (error != ERROR_IO_PENDING) { DWORD dwStatus; if (m_pSecurityInfo && (dwStatus = m_pSecurityInfo->GetStatusFlags())) { // Report SSL status of all errors found DWORD dwStatus = m_pSecurityInfo->GetStatusFlags(); InternetIndicateStatus(WINHTTP_CALLBACK_STATUS_SECURE_FAILURE, &dwStatus, sizeof(dwStatus) ); } fsm.SetDone(); } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::Disconnect( IN DWORD dwFlags ) /*++ Routine Description: Undoes the work of ConnectSocket - i.e. closes a connected socket. We make callbacks to inform the app that this socket is being closed Arguments: dwFlags - controlling operation Return Value: DWORD Success - ERROR_SUCCESS Failure - WSA error --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::Disconnect", "{%#x} %#x", m_Socket, dwFlags )); DWORD error = ICSocket::Disconnect(dwFlags); // // delete security context handle for the connection // if ((m_dwFlags & (SF_ENCRYPT | SF_DECRYPT)) && dwEncFlags != ENC_CAPS_NOT_INSTALLED) { TerminateSecConnection(); } // // Zero out the pending input buffer // if (m_pdblbufBuffer != NULL) { m_pdblbufBuffer->SetInputBufferSize(0); } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::Send( IN LPVOID lpBuffer, IN DWORD dwBufferLength, IN DWORD dwFlags ) /*++ Routine Description: Sends data over a secure connection Arguments: lpBuffer - pointer to user data to send dwBufferLength - length of user data dwFlags - flags controlling operation Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::Send", "{%#x [%#x]} %#x, %d, %#x", this, m_Socket, lpBuffer, dwBufferLength, dwFlags )); INET_ASSERT(lpBuffer != NULL); INET_ASSERT((int)dwBufferLength > 0); DWORD error; if (IsSecure()) // might be attempting http CONNECT { error = DoFsm(New CFsm_SecureSend(lpBuffer, dwBufferLength, dwFlags, this )); } else { // thunk to non-secure send error = ICSocket::Send(lpBuffer, dwBufferLength, dwFlags ); } DEBUG_LEAVE(error); return error; } DWORD CFsm_SecureSend::RunSM( IN CFsm * Fsm ) /*++ Routine Description: description-of-function. Arguments: Fsm - Return Value: DWORD --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "CFsm_SecureSend::RunSM", "%#x", Fsm )); ICSecureSocket * pSecureSocket = (ICSecureSocket *)Fsm->GetContext(); CFsm_SecureSend * stateMachine = (CFsm_SecureSend *)Fsm; DWORD error; switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pSecureSocket->Send_Fsm(stateMachine); break; default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::Send_Fsm( IN CFsm_SecureSend * Fsm ) /*++ Routine Description: description-of-function. Arguments: Fsm - Return Value: DWORD --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::Send_Fsm", "%#x", Fsm )); CFsm_SecureSend & fsm = *Fsm; DWORD error = fsm.GetError(); if (fsm.GetState() == FSM_STATE_INIT) { // // Log The Data BEFORE we Encrypt It ( if we do ) // DEBUG_DUMP_API(SOCKETS, "sending data:\n", fsm.m_lpBuffer, fsm.m_dwBufferLength ); } while (((int)fsm.m_dwBufferLength > 0) && (error == ERROR_SUCCESS)) { LPVOID lpBuffer; DWORD dwLength; DWORD dwBytes; if (m_dwFlags & SF_ENCRYPT) { DWORD dwBytesEncrypted; DEBUG_PRINT(SOCKETS, INFO, ("Encrypting data..\n" )); error = EncryptData(fsm.m_lpBuffer, fsm.m_dwBufferLength, &fsm.m_lpCryptBuffer, &fsm.m_dwCryptBufferLength, &dwBytesEncrypted ); if (error != ERROR_SUCCESS) { break; } INET_ASSERT(fsm.m_lpCryptBuffer != NULL); INET_ASSERT((int)fsm.m_dwCryptBufferLength > 0); INET_ASSERT(dwBytesEncrypted <= fsm.m_dwBufferLength); lpBuffer = fsm.m_lpCryptBuffer; dwLength = fsm.m_dwCryptBufferLength; dwBytes = dwBytesEncrypted; } else { lpBuffer = fsm.m_lpBuffer; dwLength = fsm.m_dwBufferLength; dwBytes = dwLength; } fsm.m_lpBuffer = (LPVOID)((LPBYTE)fsm.m_lpBuffer + dwBytes); fsm.m_dwBufferLength -= dwBytes; error = ICSocket::Send(lpBuffer, dwLength, fsm.m_dwFlags); if (error != ERROR_SUCCESS) { break; } } if (error != ERROR_IO_PENDING) { fsm.SetDone(); // // Free Encryption Buffer if doing SSL/PCT // if (fsm.m_lpCryptBuffer != NULL ) { fsm.m_lpCryptBuffer = (LPVOID)FREE_MEMORY(fsm.m_lpCryptBuffer); INET_ASSERT(fsm.m_lpCryptBuffer == NULL); } } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::Receive( IN OUT LPVOID* lplpBuffer, IN OUT LPDWORD lpdwBufferLength, IN OUT LPDWORD lpdwBufferRemaining, IN OUT LPDWORD lpdwBytesReceived, IN DWORD dwExtraSpace, IN DWORD dwFlags, OUT LPBOOL lpbEof ) /*++ Routine Description: Receives and decrypts data from a secure connection Arguments: lplpBuffer - see ICSocket::Receive lpdwBufferLength - lpdwBufferRemaining - lpdwBytesReceived - dwExtraSpace - dwFlags - lpbEof - Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { INET_ASSERT(lplpBuffer != NULL); INET_ASSERT(lpdwBufferLength != NULL); INET_ASSERT((*lpdwBufferLength == 0) ? (dwFlags & SF_EXPAND) : TRUE); DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::Receive", "%#x [%#x], %#x [%d], %#x [%d], %#x [%d], %d, %#x, %#x [%B]", lplpBuffer, *lplpBuffer, lpdwBufferLength, *lpdwBufferLength, lpdwBufferRemaining, *lpdwBufferRemaining, lpdwBytesReceived, *lpdwBytesReceived, dwExtraSpace, dwFlags, lpbEof, *lpbEof )); DWORD error; // Thunk to non-secure if establishing via a CONNECT if (IsSecure()) { error = DoFsm(New CFsm_SecureReceive(lplpBuffer, lpdwBufferLength, lpdwBufferRemaining, lpdwBytesReceived, dwExtraSpace, dwFlags, lpbEof, this )); } else { error = ICSocket::Receive(lplpBuffer, lpdwBufferLength, lpdwBufferRemaining, lpdwBytesReceived, dwExtraSpace, dwFlags, lpbEof ); } DEBUG_LEAVE(error); return error; } DWORD CFsm_SecureReceive::RunSM( IN CFsm * Fsm ) /*++ Routine Description: description-of-function. Arguments: Fsm - Return Value: DWORD --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "CFsm_SecureReceive::RunSM", "%#x", Fsm )); ICSecureSocket * pSecureSocket = (ICSecureSocket *)Fsm->GetContext(); CFsm_SecureReceive * stateMachine = (CFsm_SecureReceive *)Fsm; DWORD error; switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pSecureSocket->Receive_Fsm(stateMachine); break; default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::Receive_Fsm( IN CFsm_SecureReceive * Fsm ) /*++ Routine Description: description-of-function. Arguments: Fsm - Return Value: DWORD --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::Receive_Fsm", "%#x", Fsm )); //INET_ASSERT(m_dwFlags & SF_DECRYPT); CFsm_SecureReceive & fsm = *Fsm; DWORD error = fsm.GetError(); LPVOID * lplpBuffer; LPDWORD lpdwBufferLength; LPDWORD lpdwBufferLeft; LPDWORD lpdwBytesReceived; if (fsm.GetState() != FSM_STATE_INIT) { switch (fsm.GetFunctionState()) { case FSM_STATE_2: goto negotiate_continue; case FSM_STATE_3: goto receive_continue; default: error = ERROR_WINHTTP_INTERNAL_ERROR; INET_ASSERT(FALSE); goto quit; } } // // if we weren't given a buffer, but the caller told us its okay to resize // then we allocate the initial buffer // if ((fsm.m_dwBufferLength == 0) || (fsm.m_dwBufferLeft == 0)) { INET_ASSERT((fsm.m_dwBufferLength == 0) ? (fsm.m_dwBufferLeft == 0) : TRUE); if (fsm.m_dwFlags & SF_EXPAND) { // // allocate a fixed memory buffer // // // BUGBUG - the initial buffer size should come from the handle // object // fsm.m_dwBufferLeft = DEFAULT_RECEIVE_BUFFER_INCREMENT; if (fsm.m_dwBufferLength == 0) { fsm.m_bAllocated = TRUE; } fsm.m_dwBufferLength += fsm.m_dwBufferLeft; DEBUG_PRINT(SOCKETS, INFO, ("resizing %#x to %d\n", fsm.m_hBuffer, fsm.m_dwBufferLength )); fsm.m_hBuffer = ResizeBuffer(fsm.m_hBuffer, fsm.m_dwBufferLength, FALSE); if (fsm.m_hBuffer == (HLOCAL)NULL) { error = GetLastError(); INET_ASSERT(error != ERROR_SUCCESS); fsm.m_bAllocated = FALSE; } } else { // // the caller didn't say its okay to resize // error = ERROR_INSUFFICIENT_BUFFER; } } else if (fsm.m_hBuffer == (HLOCAL)NULL) { error = ERROR_INSUFFICIENT_BUFFER; } if (error != ERROR_SUCCESS) { goto quit; } // // keep the app informed (if requested to do so) // if (fsm.m_dwFlags & SF_INDICATE) { InternetIndicateStatus(WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE, NULL, 0 ); } fsm.m_dwReadFlags = fsm.m_dwFlags; // // Loop Through our Reads, assembling enough unencrypted bytes // to return back to the client. In the non-SSL/PCT case, we should // be able to quit after one iteration. // do { LPVOID * lplpReadBuffer; LPDWORD lpdwReadBufferLength; LPDWORD lpdwReadBufferLeft; LPDWORD lpdwReadBufferReceived; // // If we're attempting to read SSL/PCT data, we need examine, whether // we have all the bytes decrypted and read already in our scratch buffer. // if (m_dwFlags & SF_DECRYPT) { DEBUG_PRINT(SOCKETS, INFO, ("Decrypting data..\n" )); if (m_pdblbufBuffer != NULL) { DEBUG_DUMP_API(SOCKETS, "About to decrypt this data:\n", (LPBYTE)m_pdblbufBuffer->GetInputBufferPointer(), m_pdblbufBuffer->GetInputBufferSize() ); } fsm.m_dwDecryptError = DecryptData(&fsm.m_dwInputBytesLeft, (LPBYTE)fsm.m_hBuffer, &fsm.m_dwBufferLeft, &fsm.m_dwBytesReceived, &fsm.m_dwBytesRead ); if (fsm.m_dwDecryptError == SEC_E_INCOMPLETE_MESSAGE && fsm.m_bEof && m_pdblbufBuffer->GetInputBufferSize() > 0) { error = ERROR_HTTP_INVALID_SERVER_RESPONSE; goto error_exit; } else if (fsm.m_dwDecryptError == SEC_I_RENEGOTIATE) { CredHandle hDummyCreds; // // BUGBUG - don't have to do this - Receive() called from // SSPINegotiateLoop() won't come back through here // m_dwFlags &= ~(SF_ENCRYPT | SF_DECRYPT); ClearCreds(hDummyCreds); fsm.SetFunctionState(FSM_STATE_2); error = SSPINegotiateLoop(m_pdblbufBuffer, fsm.m_dwFlags, hDummyCreds, FALSE, FALSE); if (error == ERROR_IO_PENDING) { goto error_exit; } negotiate_continue: m_dwFlags |= (SF_ENCRYPT | SF_DECRYPT); if (error != ERROR_SUCCESS) { break; } fsm.m_dwDecryptError = (ULONG)SEC_E_INCOMPLETE_MESSAGE; // // If there was extra data, and it was shoved back into // dblbuffer, then we should redo the decryption, since // it now has extra input data to process. // if (m_pdblbufBuffer->GetInputBufferSize() > 0) { continue; } // // Okay, here we've received 0 bytes, so so we have to // receive more data, and process it. Do this by zero-ing // out the input buffer, and setting the decrypt_error to be // Incomplete. // } // // If we have no buffer left to fill, or the caller ask for a single recv // and we've managed to read something into the buffer, then return by breaking. // if ((fsm.m_dwBufferLeft == 0) || (!(fsm.m_dwFlags & SF_RECEIVE_ALL) && (fsm.m_dwBytesRead > 0))) { break; // we're done. } INET_ASSERT(error == ERROR_SUCCESS); // // BUGBUG [arthurbi] GetInputBufferSize needs to be called before getting // the pointer, because the pointer may be moved around while generating // the size. // DWORD remaining; DWORD inputSize; inputSize = m_pdblbufBuffer->GetInputBufferSize(); remaining = m_pdblbufBuffer->GetInputBufferRemaining(); fsm.m_dwBufferLengthDummy = inputSize + remaining; fsm.m_dwBufferLeftDummy = remaining; fsm.m_dwBufferReceivedDummy = inputSize; fsm.m_lpBufferDummy = m_pdblbufBuffer->GetInputBufferPointer(); // // We need to be careful, and only recv one block of data at a time // if we're not we break keep-alive by doing too many reads. // // So unless we know ( by the non-0 return ) exactly how many bytes // to read, we shut off SF_RECEIVE_ALL. // fsm.m_dwReadFlags &= ~(SF_RECEIVE_ALL | SF_INDICATE | SF_EXPAND | SF_COMPRESS ); if (fsm.m_dwInputBytesLeft != 0) { // // don't add RECEIVE_ALL if NO_WAIT already set by caller - they // are mutually exclusive // if (!(fsm.m_dwReadFlags & SF_NO_WAIT)) { fsm.m_dwReadFlags |= SF_RECEIVE_ALL; } fsm.m_dwBufferLeftDummy = min(fsm.m_dwInputBytesLeft, fsm.m_dwBufferLeftDummy); } lplpReadBuffer = (LPVOID *)&fsm.m_lpBufferDummy; lpdwReadBufferLength = &fsm.m_dwBufferLengthDummy; lpdwReadBufferLeft = &fsm.m_dwBufferLeftDummy; lpdwReadBufferReceived = &fsm.m_dwBufferReceivedDummy; } else { lplpReadBuffer = &fsm.m_hBuffer; lpdwReadBufferLength = &fsm.m_dwBufferLength; lpdwReadBufferLeft = &fsm.m_dwBufferLeft; lpdwReadBufferReceived = &fsm.m_dwBytesReceived; } // // receive some data, assuming the socket is not closed. // if (!fsm.m_bEof) { //fsm.m_dwBytesReceivedPre = *lpdwReadBufferReceived; fsm.SetFunctionState(FSM_STATE_3); error = ICSocket::Receive(lplpReadBuffer, lpdwReadBufferLength, lpdwReadBufferLeft, lpdwReadBufferReceived, fsm.m_dwExtraSpace, fsm.m_dwReadFlags, &fsm.m_bEof ); if (error == ERROR_IO_PENDING) { goto error_exit; } receive_continue: //fsm.m_dwBytesRead += fsm.m_dwByReceived - fsm.m_dwDCBufferRecvPre; if (error != ERROR_SUCCESS) { goto quit; } // // Once again, for SSL/PCT we need to update our input buffer after the read. // if (m_dwFlags & SF_DECRYPT) { m_pdblbufBuffer->SetInputBufferSize(fsm.m_dwBufferReceivedDummy); } } } while ((m_dwFlags & SF_DECRYPT) && (error == ERROR_SUCCESS) && (fsm.m_dwDecryptError == SEC_E_INCOMPLETE_MESSAGE) && (!fsm.m_bEof || (m_pdblbufBuffer->GetInputBufferSize() > 0))); if (error == ERROR_SUCCESS) { // // inform the app that we finished, and tell it how much we received // this time // if (fsm.m_dwFlags & SF_INDICATE) { DWORD dwBytesRead = fsm.m_dwBytesRead; InternetIndicateStatus(WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED, &dwBytesRead, sizeof(dwBytesRead) ); } // // if we received the entire response and the caller specified // SF_COMPRESS then we shrink the buffer to fit. We may end up growing // the buffer to contain dwExtraSpace if it is not zero and we just // happened to fill the current buffer // if (fsm.m_bEof && (fsm.m_dwFlags & SF_COMPRESS)) { fsm.m_dwBufferLeft = fsm.m_dwExtraSpace; // // include any extra that the caller required // fsm.m_dwBufferLength = fsm.m_dwBytesReceived + fsm.m_dwExtraSpace; DEBUG_PRINT(SOCKETS, INFO, ("shrinking buffer %#x to %d (%#x) bytes (includes %d extra)\n", fsm.m_hBuffer, fsm.m_dwBufferLength, fsm.m_dwBufferLength, fsm.m_dwExtraSpace )); fsm.m_hBuffer = ResizeBuffer(fsm.m_hBuffer, fsm.m_dwBufferLength, FALSE); INET_ASSERT((fsm.m_hBuffer == NULL) ? ((fsm.m_dwBytesReceived + fsm.m_dwExtraSpace) == 0) : TRUE ); } DEBUG_PRINT_API(SOCKETS, INFO, ("read %d bytes @ %#x from socket %#x\n", fsm.m_dwBytesRead, (LPBYTE)fsm.m_hBuffer + *fsm.m_lpdwBytesReceived, m_Socket )); DEBUG_DUMP_API(SOCKETS, "received data:\n", (LPBYTE)fsm.m_hBuffer + *fsm.m_lpdwBytesReceived, fsm.m_dwBytesRead ); } quit: // // if we failed but allocated a buffer then we need to free it (we were // leaking this buffer if the request was cancelled) // if ((error != ERROR_SUCCESS) && fsm.m_bAllocated && (fsm.m_hBuffer != NULL)) { //dprintf("SocketReceive() freeing allocated buffer %#x\n", hBuffer); fsm.m_hBuffer = (HLOCAL)FREE_MEMORY(fsm.m_hBuffer); INET_ASSERT(fsm.m_hBuffer == NULL); fsm.m_dwBufferLength = 0; fsm.m_dwBufferLeft = 0; fsm.m_dwBytesReceived = 0; fsm.m_bEof = TRUE; } DEBUG_PRINT(SOCKETS, INFO, ("returning: lpBuffer=%#x, bufferLength=%d, bufferLeft=%d, bytesReceived=%d\n", fsm.m_hBuffer, fsm.m_dwBufferLength, fsm.m_dwBufferLeft, fsm.m_dwBytesReceived )); // // update output parameters // *fsm.m_lplpBuffer = (LPVOID)fsm.m_hBuffer; *fsm.m_lpdwBufferLength = fsm.m_dwBufferLength; *fsm.m_lpdwBufferRemaining = fsm.m_dwBufferLeft; *fsm.m_lpdwBytesReceived = fsm.m_dwBytesReceived; // // Hack, we hide eof's from caller, since we may have buffered data sitting around // if ((m_dwFlags & SF_DECRYPT) && (fsm.m_dwBytesRead != 0)) { fsm.m_bEof = FALSE; } *fsm.m_lpbEof = fsm.m_bEof; // // map any sockets error to WinInet error // if (error != ERROR_SUCCESS) { error = MapInternetError(error); } error_exit: if (error != ERROR_IO_PENDING) { fsm.SetDone(); } DEBUG_LEAVE(error); return error; } DWORD ICSecureSocket::SetHostName( IN LPSTR lpszHostName, IN SECURITY_CACHE_LIST *pCertCache ) /*++ Routine Description: Set name of server we are connected to. Find or create a security cache entry for this name Arguments: lpszHostName - name to set pCertCache - SSL cert cache reference (global vs. session) Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_NOT_ENOUGH_MEMORY --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::SetHostName", "{%#x [%q %#x/%d]} %q, %#x", this, m_lpszHostName, GetSocket(), GetSourcePort(), lpszHostName, pCertCache )); INET_ASSERT(IsSecure()); INET_ASSERT((lpszHostName != NULL) || (m_lpszHostName == NULL)); DWORD error = ERROR_SUCCESS; if (lpszHostName != NULL) { if (m_lpszHostName != NULL) { m_lpszHostName = (LPSTR)FREE_MEMORY(m_lpszHostName); INET_ASSERT(m_lpszHostName == NULL); } m_pCertCache = pCertCache; m_lpszHostName = NewString(lpszHostName); if (m_lpszHostName == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; } else if (m_pSecurityInfo == NULL) { /* SCLE ref */ if (pCertCache) m_pSecurityInfo = pCertCache->Find(lpszHostName); if (m_pSecurityInfo == NULL) { /* SCLE ref */ m_pSecurityInfo = New SECURITY_CACHE_LIST_ENTRY(lpszHostName); } } } DEBUG_LEAVE(error); return error; } // // private ICSecureSocket methods // DWORD ICSecureSocket::EncryptData( IN LPVOID lpBuffer, IN DWORD dwInBufferLen, OUT LPVOID * lplpBuffer, OUT LPDWORD lpdwOutBufferLen, OUT LPDWORD lpdwInBufferBytesEncrypted ) /*++ Routine Description: This function encrypts data in the lplpbuffer. Arguments: lpBuffer - pointer to buffer containing unencrypted user data dwInBufferLen - length of input buffer lplpBuffer - pointer to pointer to encrypted user buffer lpdwOutBufferLen - pointer to length of output lplpbuffer lpdwInBufferBytesEncrypted - pointer to length of bytes read and encrypted in output buffer Return Value: Error Code --*/ { DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::EncryptData", "%#x, %d, %#x, %#x, %#x", lpBuffer, dwInBufferLen, lplpBuffer, lpdwOutBufferLen, lpdwInBufferBytesEncrypted )); SECURITY_STATUS scRet = STATUS_SUCCESS; SecBufferDesc Buffer; SecBuffer Buffers[3]; HLOCAL hBuffer; DWORD error; DWORD dwMaxDataBufferSize; DWORD dwExtraInputBufferLen; SecPkgContext_StreamSizes Sizes; INET_ASSERT(IsSecure()); INET_ASSERT(lpBuffer != NULL); INET_ASSERT(dwInBufferLen != 0); INET_ASSERT(lplpBuffer != NULL); INET_ASSERT(lpdwOutBufferLen != NULL); INET_ASSERT(lpdwInBufferBytesEncrypted != NULL); hBuffer = (HLOCAL) *lplpBuffer; *lpdwOutBufferLen = 0; *lpdwInBufferBytesEncrypted = 0; //INET_ASSERT(hBuffer == NULL ); // // find the header and trailer sizes // scRet = g_QueryContextAttributes(&m_hContext, SECPKG_ATTR_STREAM_SIZES, &Sizes ); if (scRet != ERROR_SUCCESS) { // // Map the SSPI error. // DEBUG_PRINT(API, INFO, ("QueryContextAttributes returned, %s [%x] (%s)\n", InternetMapSSPIError((DWORD)scRet), scRet, InternetMapError(scRet) )); error = MapInternetError((DWORD) scRet); goto quit; } else { DEBUG_PRINT(API, INFO, ("QueryContextAttributes returned header=%d, trailer=%d, maxmessage=%d\n", Sizes.cbHeader, Sizes.cbTrailer, Sizes.cbMaximumMessage )); } INET_ASSERT(Sizes.cbMaximumMessage > (Sizes.cbHeader + Sizes.cbTrailer)); // // Figure out the max SSL packet we can send over the wire. // If the data is too big to send, then remeber how much // we did send, and how much we didn't send. // dwMaxDataBufferSize = Sizes.cbMaximumMessage - (Sizes.cbHeader + Sizes.cbTrailer); dwExtraInputBufferLen = (dwMaxDataBufferSize < dwInBufferLen ) ? (dwInBufferLen - dwMaxDataBufferSize) : 0; dwInBufferLen = ( dwExtraInputBufferLen > 0 ) ? dwMaxDataBufferSize : dwInBufferLen; DEBUG_PRINT(API, INFO, ("resizing %#x to %d\n", hBuffer, dwInBufferLen + Sizes.cbHeader + Sizes.cbTrailer )); hBuffer = ResizeBuffer(hBuffer, dwInBufferLen + Sizes.cbHeader + Sizes.cbTrailer, FALSE ); if (hBuffer == (HLOCAL)NULL) { error = GetLastError(); INET_ASSERT(error != ERROR_SUCCESS); goto quit; } // // prepare data for SecBuffer // Buffers[0].pvBuffer = hBuffer; Buffers[0].cbBuffer = Sizes.cbHeader; Buffers[0].BufferType = SECBUFFER_TOKEN; Buffers[1].pvBuffer = (LPBYTE)hBuffer + Sizes.cbHeader; memcpy(Buffers[1].pvBuffer, lpBuffer, dwInBufferLen); Buffers[1].cbBuffer = dwInBufferLen; Buffers[1].BufferType = SECBUFFER_DATA; // // check if security pkg supports trailer: PCT does // if ( Sizes.cbTrailer ) { Buffers[2].pvBuffer = (LPBYTE)hBuffer + Sizes.cbHeader + dwInBufferLen; Buffers[2].cbBuffer = Sizes.cbTrailer; Buffers[2].BufferType = SECBUFFER_TOKEN; } else { Buffers[2].pvBuffer = NULL; Buffers[2].cbBuffer = 0; Buffers[2].BufferType = SECBUFFER_EMPTY; } Buffer.cBuffers = 3; Buffer.pBuffers = Buffers; Buffer.ulVersion = SECBUFFER_VERSION; scRet = g_SealMessage(&m_hContext, 0, &Buffer, 0); DEBUG_PRINT(API, INFO, ("SealMessage returned, %s [%x]\n", InternetMapSSPIError((DWORD)scRet), scRet )); if (scRet != ERROR_SUCCESS) { // // Map the SSPI error. // DEBUG_PRINT(API, ERROR, ("SealMessage returned, %s [%x]\n", InternetMapSSPIError((DWORD)scRet), scRet )); error = MapInternetError((DWORD) scRet); if (hBuffer != NULL) { FREE_MEMORY(hBuffer); } goto quit; } else { error = ERROR_SUCCESS; } *lplpBuffer = Buffers[0].pvBuffer; *lpdwOutBufferLen = Sizes.cbHeader + Buffers[1].cbBuffer + Buffers[2].cbBuffer; *lpdwInBufferBytesEncrypted = dwInBufferLen; DEBUG_PRINT(API, INFO, ("SealMessage returned Buffer = %x, EncryptBytes = %d, UnencryptBytes=%d\n", *lplpBuffer, *lpdwOutBufferLen, dwInBufferLen )); quit: DEBUG_LEAVE(error); return error; } #define SSLPCT_SMALLESTHEADERCHUNK 3 DWORD ICSecureSocket::DecryptData( OUT DWORD * lpdwBytesNeeded, OUT LPBYTE lpOutBuffer, IN OUT LPDWORD lpdwOutBufferLeft, IN OUT LPDWORD lpdwOutBufferReceived, IN OUT LPDWORD lpdwOutBufferBytesRead ) /*++ Routine Description: This function decrypts data into the lpOutBuffer. It attempts to fill lpOutBuffer. If it fails, it may do so because more bytes are needed to fill lplpEncDecBuffer or lplpEndDecBuffer is not big enough to fully contain a complete server generated SSL/PCT message. Return Value: Error Code --*/ { INET_ASSERT(IsSecure()); INET_ASSERT(lpOutBuffer); INET_ASSERT(lpdwOutBufferBytesRead); INET_ASSERT(lpdwBytesNeeded); DEBUG_ENTER((DBG_SOCKETS, Dword, "ICSecureSocket::DecryptData", "{%#x [%#x:%#x], %#x} %#x [%d], %#x, %#x [%d], %#x [%d], %#x [%d]", &m_hContext, m_hContext.dwUpper, m_hContext.dwLower, m_pdblbufBuffer, lpdwBytesNeeded, *lpdwBytesNeeded, lpOutBuffer, lpdwOutBufferLeft, *lpdwOutBufferLeft, lpdwOutBufferReceived, *lpdwOutBufferReceived, lpdwOutBufferBytesRead, *lpdwOutBufferBytesRead )); SecBufferDesc Buffer; SecBuffer Buffers[4]; // the 4 buffers are: header, data, trailer, extra DWORD scRet = ERROR_SUCCESS; *lpdwBytesNeeded = 0; // // HOW THIS THING WORKS: // We sit in a loop, attempting to fill our passed in buffer with // decrypted data. If there is no decrypted data we check to // see if there is encrypted data sitting in our buffer. // // Assuming there is enough we decrypt a chunk, and place it in the // output buffer of our double buffer class. We reloop and try to // copy it to our passed in byffer. // // If there is more encrypted data, and more space to fill in // the user buffer, we attempt to decrypt the next chunk of this. // // If we do not have enough data, we return with an error, and // expect a network read to be done. // do { // // Check to see if we can fill up User buffer. // m_pdblbufBuffer->CopyOut( lpOutBuffer, lpdwOutBufferLeft, lpdwOutBufferReceived, lpdwOutBufferBytesRead ); // // If we've filled our output buffer, than exit with ERROR_SUCCESS // if ( *lpdwOutBufferLeft == 0) { break; } // // If we've got less than ~3 bytes return so we can read more data. // if (m_pdblbufBuffer->GetInputBufferSize() < SSLPCT_SMALLESTHEADERCHUNK) { scRet = (DWORD) SEC_E_INCOMPLETE_MESSAGE; break; } // // prepare data the SecBuffer for a call to SSL/PCT decryption code. // Buffers[0].pvBuffer = m_pdblbufBuffer->GetInputBufferPointer( ); Buffers[0].cbBuffer = m_pdblbufBuffer->GetInputBufferSize(); // # of bytes to decrypt Buffers[0].BufferType = SECBUFFER_DATA; int i; for ( i = 1; i < 4; i++ ) { // // clear other 3 buffers for receving result from SSPI package // Buffers[i].pvBuffer = NULL; Buffers[i].cbBuffer = 0; Buffers[i].BufferType = SECBUFFER_EMPTY; } Buffer.cBuffers = 4; // the 4 buffers are: header, data, trailer, extra Buffer.pBuffers = Buffers; Buffer.ulVersion = SECBUFFER_VERSION; // // Decrypt the DATA !!! // scRet = g_UnsealMessage(&m_hContext, &Buffer, 0, NULL ); DEBUG_PRINT(API, INFO, ("UnsealMessage returned, %s [%x]\n", InternetMapSSPIError((DWORD)scRet), scRet )); if ( scRet != ERROR_SUCCESS && scRet != SEC_I_RENEGOTIATE) { DEBUG_PRINT(API, ERROR, ("UnsealMessage failed, error %lx\n", scRet )); INET_ASSERT( scRet != SEC_E_MESSAGE_ALTERED ); if ( scRet == SEC_E_INCOMPLETE_MESSAGE ) { DWORD dwAddlBufferNeeded = Buffers[1].cbBuffer; DEBUG_PRINT(API, INFO, ("UnsealMessage short of %d bytes\n", dwAddlBufferNeeded )); // // If we're missing data, return to get the missing data. // But make sure we have enough room first! // if (!m_pdblbufBuffer->ResizeBufferIfNeeded(dwAddlBufferNeeded)) { scRet = ERROR_NOT_ENOUGH_MEMORY; } *lpdwBytesNeeded = dwAddlBufferNeeded; break; } else if ( scRet == 0x00090317 /*SEC_I_CONTEXT_EXPIRED*/) { // // Ignore this error and treat this like a simple terminator // to end the connection. // scRet = ERROR_SUCCESS; } else { break; } } // // Success we decrypted a block // LPBYTE lpExtraBuffer; DWORD dwExtraBufferSize; LPBYTE lpDecryptedBuffer; DWORD dwDecryptedBufferSize; lpDecryptedBuffer = (LPBYTE) Buffers[1].pvBuffer; dwDecryptedBufferSize = Buffers[1].cbBuffer; // // BUGBUG [arthurbi] this is hack to work with the OLD SSLSSPI.DLL . // They return extra on the second buffer instead of the third. // if ( Buffers[2].BufferType == SECBUFFER_EXTRA ) { lpExtraBuffer = (LPBYTE) Buffers[2].pvBuffer; dwExtraBufferSize = Buffers[2].cbBuffer; } else if ( Buffers[3].BufferType == SECBUFFER_EXTRA ) { lpExtraBuffer = (LPBYTE) Buffers[3].pvBuffer; dwExtraBufferSize = Buffers[3].cbBuffer; } else { lpExtraBuffer = NULL; dwExtraBufferSize = 0; } m_pdblbufBuffer->SetOutputInputBuffer( lpDecryptedBuffer, dwDecryptedBufferSize, lpExtraBuffer, dwExtraBufferSize, FALSE // don't combine. ); if ( dwDecryptedBufferSize == 0 ) break; // No more data to process INET_ASSERT( *lpdwOutBufferLeft ); // don't expect to get here this way. } while ( *lpdwOutBufferLeft && scRet == ERROR_SUCCESS ); DEBUG_PRINT(API, INFO, ("DecryptData returning, " "OutBuffer = %x, DecryptBytesRecv = %d\n", lpOutBuffer, *lpdwOutBufferBytesRead )); DEBUG_LEAVE((DWORD)scRet); return ( scRet ); } VOID ICSecureSocket::TerminateSecConnection( VOID ) /*++ Routine Description: This function deletes the security context handle which result in deleting the local data structures with which they are associated. Arguments: None Return Value: None --*/ { DEBUG_ENTER((DBG_SOCKETS, None, "ICSecureSocket::TerminateSecConnection", "{%#x [%#x:%#x]}", this, m_hContext.dwUpper, m_hContext.dwLower )); INET_ASSERT(IsSecure()); //INET_ASSERT(m_hContext.dwLower != 0); //INET_ASSERT(m_hContext.dwUpper != 0); if (GlobalSecFuncTable) { if (!((m_hContext.dwLower == 0) && (m_hContext.dwUpper == 0))) { // There are cases where because of circular dependencies // schannel could get unloaded before wininet. In that case // this call could fault. This usually happens when the process // is shutting down. SAFE_WRAP_REVERT_USER_VOID(g_DeleteSecurityContext, (&m_hContext)); m_hContext.dwLower = m_hContext.dwUpper = 0; } } else { DEBUG_PRINT(API, ERROR, ("Attempting to Delete a security context, with a NULL SSPI func table!(missing SCHANNEL.DLL?)\n" )); } DEBUG_LEAVE(0); } #ifdef SECPKG_ATTR_PROTO_INFO /*++ ProtoInfoToString: This routine converts an SSPI SecPkgContext_ProtoInfo structure into a string. The returned string must be released via LocalFree. Arguments: pProtoInfo supplies the SecPkgContext_ProtoInfo structure to be converted to string representation. Return Value: Non-NULL is the address of the returned string. This must be freed via LocalFree once it is no longer needed. NULL implies no memory is available. Author: Doug Barlow (dbarlow) 4/23/1996 --*/ PRIVATE LPTSTR ProtoInfoToString( IN const PSecPkgContext_ProtoInfo pProtoInfo) { TCHAR szValue[32], szSep[8]; LPTSTR szFinal = NULL; DWORD length; length = GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, szSep, sizeof(szSep) / sizeof(TCHAR)); if (0 >= length) lstrcpy(szSep, TEXT(".")); length = wsprintf( szValue, TEXT("%d%s%d"), pProtoInfo->majorVersion, szSep, pProtoInfo->minorVersion); INET_ASSERT(sizeof(szValue) / sizeof(TCHAR) > length); length = lstrlen(pProtoInfo->sProtocolName); length += 2; // Space and Trailing NULL length += lstrlen(szValue); szFinal = (LPTSTR)ALLOCATE_MEMORY(LMEM_FIXED, length * sizeof(TCHAR)); if (NULL != szFinal) { lstrcpy(szFinal, pProtoInfo->sProtocolName); lstrcat(szFinal, TEXT(" ")); lstrcat(szFinal, szValue); } return szFinal; } #endif