/*++ Copyright (c) 1995 Microsoft Corporation Module Name: security.cpp Abstract: This module contains definition for the CSecurityCtx class. Author: Johnson Apacible (JohnsonA) 18-Sept-1995 Revision History: --*/ #if !defined(dllexp) #define dllexp __declspec( dllexport ) #endif // !defined( dllexp ) /* #include #include #include #include #include #include */ extern "C" { #include #include #include } #include #include #include #include #include // // SSL and SSPI related include files // #include #include #include #include #include #include #define DEFINE_SIMSSL_GLOBAL extern "C" { #define SECURITY_WIN32 #include #include #include //#include #include #include ".\credcach.hxx" } #include //#include "sslmsgs.h" #include "simssl2.h" #define CORE_SSL_KEY_LIST_SECRET L"%S_KEY_LIST" // // try/finally macros // #define START_TRY __try { #define END_TRY } #define TRY_EXCEPT } __except(EXCEPTION_EXECUTE_HANDLER) { #define START_FINALLY } __finally { // // tracing // #define ENTER( _x_ ) TraceFunctEnter( _x_ ); #define LEAVE TraceFunctLeave( ); #define MAX_SECRET_NAME 255 #define MAX_ADDRESS_LEN 64 typedef VOID (WINAPI FAR *PFN_SCHANNEL_INVALIDATE_CACHE)( VOID ); VOID WINAPI NotifySslChanges( DWORD dwNotifyType, LPVOID pInstance ); // // The list of encryption packages we support. PCT goes first since it's a // superset of SSL // struct _ENC_PROVIDER EncProviders[] = { UNISP_NAME_W, ENC_CAPS_PCT|ENC_CAPS_SSL, FALSE, PCT1SP_NAME_W, ENC_CAPS_PCT, FALSE, SSL3SP_NAME_W, ENC_CAPS_SSL, FALSE, SSL2SP_NAME_W, ENC_CAPS_SSL, FALSE, NULL, FALSE, FALSE }; struct _ENC_PROVIDER EncLsaProviders[] = { UNISP_NAME_W L" X", ENC_CAPS_PCT|ENC_CAPS_SSL, FALSE, PCT1SP_NAME_W L" X", ENC_CAPS_PCT, FALSE, SSL3SP_NAME_W L" X", ENC_CAPS_SSL, FALSE, SSL2SP_NAME_W L" X", ENC_CAPS_SSL, FALSE, NULL, FALSE, FALSE }; struct _ENC_PROVIDER* pEncProviders = EncProviders; // // service specific string names // WCHAR CEncryptCtx::wszServiceName[16]; //char CEncryptCtx::szLsaPrefix[16]; BOOL CEncryptCtx::m_IsSecureCapable = FALSE; // // hSecurity - NULL when security.dll/secur32.dll is not loaded // HINSTANCE CEncryptCtx::m_hSecurity = NULL; HINSTANCE CEncryptCtx::m_hLsa = NULL; PVOID CEncryptCtx::m_psmcMapContext = NULL; // // g_pSecFuncTable - Pointer to Global Structure of Pointers that are used // for storing the entry points into the SCHANNEL.dll // PSecurityFunctionTableW g_pSecFuncTableW = NULL; HINSTANCE g_hSchannel = NULL; // // NB : Under NT 5, the SslEmptyCache function is no longer supported // PFN_SCHANNEL_INVALIDATE_CACHE g_pfnFlushSchannelCache = NULL; #if 0 LSAOPENPOLICY g_LsaOpenPolicy = NULL; LSARETRIEVEPRIVATEDATA g_LsaRetrievePrivateData = NULL; LSACLOSE g_LsaClose = NULL; LSANTSTATUSTOWINERROR g_LsaNtStatusToWinError = NULL; LSAFREEMEMORY g_LsaFreeMemory = NULL; #endif VOID AsciiStringToUnicode( IN LPWSTR UnicodeString, IN LPSTR AsciiString ) { while ( (*UnicodeString++ = (WCHAR)*AsciiString++) != (WCHAR)'\0'); } // AsciiStringToUnicode BOOL CEncryptCtx::Initialize( LPSTR pszServiceName, IMDCOM* pImdcom, PVOID psmcMapContext, PVOID pvAdminBase //LPSTR pszLsaPrefix ) /*++ Routine Description: Activates the security package Arguments: None. Return Value: TRUE, if successful. FALSE, otherwise. --*/ { ENTER("CEncryptCtx::Initialize") BOOL fSuccess = FALSE; DWORD cb; SECURITY_STATUS ss; PSecPkgInfoW pPackageInfo = NULL; ULONG cPackages; ULONG i; ULONG fCaps; DWORD dwEncFlags = ENC_CAPS_DEFAULT; DWORD cProviders = 0; #if 0 UNICODE_STRING* punitmp; WCHAR achSecretName[MAX_SECRET_NAME+1]; #endif OSVERSIONINFO os; PSERVICE_MAPPING_CONTEXT psmc = (PSERVICE_MAPPING_CONTEXT)psmcMapContext; extern IMDCOM* pMDObject ; extern IMSAdminBaseW* pAdminObject ; pMDObject = pImdcom ; m_psmcMapContext = psmcMapContext ; pAdminObject = (IMSAdminBaseW*)pvAdminBase ; // // deal with different security packages DLL on different platforms // INITSECURITYINTERFACE pfInitSecurityInterfaceW = NULL; _ASSERT( m_hSecurity == NULL ); _ASSERT( m_hLsa == NULL ); // // load dll. // os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); _VERIFY( GetVersionEx( &os ) ); if ( os.dwPlatformId == VER_PLATFORM_WIN32_NT ) { m_hSecurity = LoadLibrary("security"); } else { m_hSecurity = LoadLibrary("secur32"); } if ( m_hSecurity == NULL ) { ErrorTrace( 0, "LoadLibrary failed: %d", GetLastError() ); goto quit; } // // only on NT get the LSA function pointers // if ( os.dwPlatformId == VER_PLATFORM_WIN32_NT ) { #if 0 m_hLsa = LoadLibrary("advapi32"); if ( m_hLsa == NULL ) { ErrorTrace( 0, "LoadLibrary ADVAPI32 failed: %d", GetLastError() ); goto quit; } g_LsaOpenPolicy = (LSAOPENPOLICY) GetProcAddress( m_hLsa, "LsaOpenPolicy" ); if ( g_LsaOpenPolicy == NULL ) { ErrorTrace( 0, "LsaOpenPolicy GetProcAddress failed: %d", GetLastError() ); goto quit; } g_LsaRetrievePrivateData = (LSARETRIEVEPRIVATEDATA) GetProcAddress( m_hLsa, "LsaRetrievePrivateData" ); if ( g_LsaRetrievePrivateData == NULL ) { ErrorTrace( 0, "LsaRetrievePrivateData GetProcAddress failed: %d", GetLastError() ); goto quit; } g_LsaClose = (LSACLOSE) GetProcAddress( m_hLsa, "LsaClose" ); if ( g_LsaClose == NULL ) { ErrorTrace( 0, "LsaClose GetProcAddress failed: %d", GetLastError() ); goto quit; } g_LsaNtStatusToWinError = (LSANTSTATUSTOWINERROR) GetProcAddress( m_hLsa, "LsaNtStatusToWinError" ); if ( g_LsaNtStatusToWinError == NULL ) { ErrorTrace( 0, "LsaNtStatusToWinError GetProcAddress failed: %d", GetLastError() ); goto quit; } g_LsaFreeMemory = (LSAFREEMEMORY) GetProcAddress( m_hLsa, "LsaFreeMemory" ); if ( g_LsaFreeMemory == NULL ) { ErrorTrace( 0, "LsaFreeMemory GetProcAddress failed: %d", GetLastError() ); goto quit; } #endif } // // get function addresses for ansi entries. // pfInitSecurityInterfaceW = (INITSECURITYINTERFACE) GetProcAddress( m_hSecurity, SECURITY_ENTRYPOINTW ); if ( pfInitSecurityInterfaceW == NULL ) { ErrorTrace( 0, "GetProcAddress failed: %d", GetLastError() ); goto quit; } g_pSecFuncTableW = (SecurityFunctionTableW*)((*pfInitSecurityInterfaceW)()); if ( g_pSecFuncTableW == NULL ) { ErrorTrace( 0, "SecurityFunctionTable failed: %d", GetLastError() ); goto quit; } // // Initialize cached credential data // InitializeCriticalSection( &csGlobalLock ); InitCredCache(); if ( g_hSchannel = LoadLibrary( "schannel.dll" ) ) { g_pfnFlushSchannelCache = (PFN_SCHANNEL_INVALIDATE_CACHE)GetProcAddress( g_hSchannel, "SslEmptyCache" ); } // // Client implementations do not require Lsa secrets // if ( pszServiceName ) { cb = lstrlen( pszServiceName ) + 1; if ( cb*2 > sizeof( wszServiceName ) ) { ErrorTrace( 0, "szServiceName too long" ); goto quit; } //CopyMemory( szServiceName, pszServiceName, cb ); AsciiStringToUnicode( wszServiceName, pszServiceName ); } #if 0 if ( pszLsaPrefix ) { cb = lstrlen( pszLsaPrefix ) + 1; if ( cb > sizeof( szLsaPrefix ) ) { ErrorTrace( 0, "szLsaPrefix too long" ); goto quit; } CopyMemory( szLsaPrefix, pszLsaPrefix, cb ); } #endif // // Get the list of security packages on this machine // ss = g_EnumerateSecurityPackages( &cPackages, &pPackageInfo ); if ( ss != STATUS_SUCCESS ) { ErrorTrace( 0, "g_EnumerateSecurityPackages failed: 0x%08X", ss ); SetLastError( ERROR_NOT_ENOUGH_MEMORY ); goto quit; } for ( i = 0; i < cPackages ; i++ ) { // // We'll only use the security package if it supports connection // oriented security and it supports the appropriate side (client // or server) // fCaps = pPackageInfo[i].fCapabilities; if ( fCaps & SECPKG_FLAG_STREAM ) { if ( fCaps & SECPKG_FLAG_CLIENT_ONLY || !(fCaps & SECPKG_FLAG_PRIVACY )) { continue; } // // Does it match one of our known packages and are we configured // to use it? // for ( int j = 0; pEncProviders[j].pszName != NULL; j++ ) { if ( !wcscmp( pPackageInfo[i].Name, pEncProviders[j].pszName ) && pEncProviders[j].dwFlags & dwEncFlags ) { pEncProviders[j].fEnabled = TRUE; cProviders++; } } } } g_FreeContextBuffer( pPackageInfo ); if ( !cProviders ) { // // The package wasn't found, fail this filter's load // ErrorTrace( 0, "No security packages were found" ); SetLastError( (DWORD) SEC_E_SECPKG_NOT_FOUND ); // // not a fatal error // fSuccess = TRUE; goto quit; } #if 0 // // The package is installed. Check to see if there are any keys // installed // if ( os.dwPlatformId == VER_PLATFORM_WIN32_NT && pszLsaPrefix ) { wsprintfW( achSecretName, CORE_SSL_KEY_LIST_SECRET, szLsaPrefix ); if ( !GetSecretW( achSecretName, &punitmp ) ) { ErrorTrace( 0, "GetSecretW returned error %d", GetLastError() ); // // Looks like no secrets are installed, fail to load, don't log an // event // SetLastError( NO_ERROR ); // // not a fatal error // fSuccess = TRUE; goto quit; } g_LsaFreeMemory( punitmp ); } #endif if ( psmc ) { if (!psmc->ServerSupportFunction( NULL, (LPVOID)NotifySslChanges, (UINT)SIMSSL_NOTIFY_MAPPER_CERT11_CHANGED ) || !psmc->ServerSupportFunction( NULL, (LPVOID)NotifySslChanges, (UINT)SIMSSL_NOTIFY_MAPPER_CERTW_CHANGED ) || !psmc->ServerSupportFunction( NULL, (LPVOID)NotifySslChanges, (UINT)SIMSSL_NOTIFY_MAPPER_SSLKEYS_CHANGED )) { _ASSERT( FALSE ); fSuccess = FALSE; goto quit; } } // // if we got here everything is cool for secure communications // fSuccess = m_IsSecureCapable = TRUE; quit: if ( fSuccess == FALSE ) { if ( m_hSecurity != NULL ) { FreeLibrary( m_hSecurity ); m_hSecurity = NULL; } if ( m_hLsa != NULL ) { FreeLibrary( m_hLsa ); m_hLsa = NULL; } } LEAVE return fSuccess; } // Initialize VOID CEncryptCtx::Terminate( VOID ) /*++ Routine Description: Terminates the security package Arguments: None. Return Value: None. --*/ { ENTER("CEncryptCtx::Terminate") PSERVICE_MAPPING_CONTEXT psmc = (PSERVICE_MAPPING_CONTEXT)m_psmcMapContext; // // Close cached credential handles // FreeCredCache(); // // NB : Under NT 5, the SslEmptyCache function is no longer supported // #if 0 if ( g_pfnFlushSchannelCache ) { (g_pfnFlushSchannelCache)(); } #endif if ( g_hSchannel ) { FreeLibrary( g_hSchannel ); } if ( psmc ) { if (!psmc->ServerSupportFunction( NULL, (LPVOID)NULL, (UINT)SIMSSL_NOTIFY_MAPPER_CERT11_CHANGED ) || !psmc->ServerSupportFunction( NULL, (LPVOID)NULL, (UINT)SIMSSL_NOTIFY_MAPPER_CERTW_CHANGED ) || !psmc->ServerSupportFunction( NULL, (LPVOID)NULL, (UINT)SIMSSL_NOTIFY_MAPPER_SSLKEYS_CHANGED )) { _ASSERT( FALSE ); } } DeleteCriticalSection( &csGlobalLock ); if ( m_hSecurity != NULL ) { FreeLibrary( m_hSecurity ); m_hSecurity = NULL; } if ( m_hLsa != NULL ) { FreeLibrary( m_hLsa ); m_hLsa = NULL; } LEAVE return; } // Terminate CEncryptCtx::CEncryptCtx( BOOL IsClient, DWORD dwSslAccessPerms ) : m_IsClient( IsClient ), m_haveSSLCtxtHandle( FALSE ), m_cbSealHeaderSize( 0 ), m_cbSealTrailerSize( 0 ), m_IsAuthenticated( FALSE ), m_IsNewSSLSession( TRUE ), m_IsEncrypted( FALSE ), m_phCredInUse( NULL ), m_iCredInUse( 0 ), m_phCreds( NULL ), m_dwSslAccessPerms( dwSslAccessPerms ), m_hSSPToken( NULL ), m_dwKeySize( 0 ) /*++ Routine Description: Class constructor Arguments: None. Return Value: None --*/ { ZeroMemory( (PVOID)&m_hSealCtxt, sizeof(m_hSealCtxt) ); } // CEncryptCtx CEncryptCtx::~CEncryptCtx( VOID ) /*++ Routine Description: Class destructor Arguments: None. Return Value: None --*/ { Reset(); } // ~CEncryptCtx VOID CEncryptCtx::Reset( VOID ) /*++ Routine Description: resets the instance to reauth user Arguments: None. Return Value: None --*/ { TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::Reset" ); m_cbSealHeaderSize = 0; m_cbSealTrailerSize = 0; m_IsAuthenticated = FALSE; m_IsNewSSLSession = TRUE; m_IsEncrypted = FALSE; m_phCredInUse = NULL; m_iCredInUse = 0; if ( m_haveSSLCtxtHandle == TRUE ) { g_DeleteSecurityContext( &m_hSealCtxt ); m_haveSSLCtxtHandle = FALSE; } ZeroMemory( (PVOID)&m_hSealCtxt, sizeof(m_hSealCtxt) ); //mikeswa 4/1/99 //According to JBanes, it is not legal to Free the credentials //handle before deleting the associated security context. //Moving release to after delete just in case this is the //final release. if (m_phCreds != NULL) { ((CRED_CACHE_ITEM *) m_phCreds)->Release(); } m_phCreds = NULL; // // Close the NT token obtained during cert mapping // if( m_hSSPToken ) { _ASSERT( m_dwSslAccessPerms & MD_ACCESS_MAP_CERT ); _VERIFY( CloseHandle( m_hSSPToken ) ); m_hSSPToken = NULL; } } // ~CEncryptCtx BOOL CEncryptCtx::SealMessage( IN LPBYTE Message, IN DWORD cbMessage, OUT LPBYTE pBuffOut, OUT DWORD *pcbBuffOut ) /*++ Routine Description: Encrypt message Arguments: Message - message to be encrypted cbMessage - size of message to be encrypted Return Value: Status of operation --*/ { SECURITY_STATUS ss = ERROR_NOT_SUPPORTED; SecBufferDesc inputBuffer; SecBuffer inBuffers[3]; DWORD encryptedLength; DWORD iBuff = 0; TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::SealMessage"); if ( m_haveSSLCtxtHandle ) { encryptedLength = cbMessage + GetSealHeaderSize() + GetSealTrailerSize(); DebugTrace( (LPARAM)this, "InBuf: 0x%08X, OutBuf: 0x%08X, in: %d, max: %d, out: %d", Message, pBuffOut, cbMessage, *pcbBuffOut, encryptedLength ); // // don't do the MoveMemory if the app is SSL/PCT buffer aware // if ( Message != pBuffOut + GetSealHeaderSize() ) { MoveMemory( pBuffOut + GetSealHeaderSize(), Message, cbMessage ); } if ( GetSealHeaderSize() ) { inBuffers[iBuff].pvBuffer = pBuffOut; inBuffers[iBuff].cbBuffer = GetSealHeaderSize(); inBuffers[iBuff].BufferType = SECBUFFER_TOKEN; iBuff++; } inBuffers[iBuff].pvBuffer = pBuffOut + GetSealHeaderSize(); inBuffers[iBuff].cbBuffer = cbMessage; inBuffers[iBuff].BufferType = SECBUFFER_DATA; iBuff++; if ( GetSealTrailerSize() ) { inBuffers[iBuff].pvBuffer = pBuffOut + GetSealHeaderSize() + cbMessage; inBuffers[iBuff].cbBuffer = GetSealTrailerSize(); inBuffers[iBuff].BufferType = SECBUFFER_TOKEN; iBuff++; } inputBuffer.cBuffers = iBuff; inputBuffer.pBuffers = inBuffers; inputBuffer.ulVersion = SECBUFFER_VERSION; ss = g_SealMessage( &m_hSealCtxt, 0, &inputBuffer, 0 ); *pcbBuffOut = encryptedLength; DebugTrace( (LPARAM)this, "SealMessage returned: %d, 0x%08X", ss, ss ); } SetLastError(ss); return (ss == STATUS_SUCCESS); } // SealMessage BOOL CEncryptCtx::UnsealMessage( IN LPBYTE Message, IN DWORD cbMessage, OUT LPBYTE *DecryptedMessage, OUT PDWORD DecryptedMessageSize, OUT PDWORD ExpectedMessageSize, OUT LPBYTE *NextSealMessage ) /*++ Routine Description: Decrypt message Arguments: Message - message to be encrypted cbMessage - size of message to be encrypted Return Value: Status of operation --*/ { SECURITY_STATUS ss; SecBufferDesc inputBuffer; SecBuffer inBuffers[4]; DWORD qOP; // // if the app wants to know the start of the next seal msg init to NULL // if ( NextSealMessage != NULL ) { *NextSealMessage = NULL; } TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::UnsealMessage"); DebugTrace( (LPARAM)this, "initial ptr: 0x%08X, count: %d", Message, cbMessage ); if ( m_haveSSLCtxtHandle ) { inBuffers[0].pvBuffer = Message; inBuffers[0].cbBuffer = cbMessage; inBuffers[0].BufferType = SECBUFFER_DATA; inBuffers[1].pvBuffer = NULL; inBuffers[1].cbBuffer = 0; inBuffers[1].BufferType = SECBUFFER_EMPTY; inBuffers[2].pvBuffer = NULL; inBuffers[2].cbBuffer = 0; inBuffers[2].BufferType = SECBUFFER_EMPTY; inBuffers[3].pvBuffer = NULL; inBuffers[3].cbBuffer = 0; inBuffers[3].BufferType = SECBUFFER_EMPTY; // // one for the data and one for the head and/or tail // inputBuffer.cBuffers = 4; // if ( GetSealHeaderSize() ) inputBuffer.cBuffers++; // if ( GetSealTrailerSize() ) inputBuffer.cBuffers++; // if ( NextSealMessage ) inputBuffer.cBuffers++; inputBuffer.pBuffers = inBuffers; inputBuffer.ulVersion = SECBUFFER_VERSION; ss = g_UnsealMessage( &m_hSealCtxt, &inputBuffer, 0, &qOP ); if(ss == SEC_I_RENEGOTIATE) { // // We do not support renegotiation. Renegotiation makes no sense // in the context of an SMTP/NNTP client - because there should be // no need to change TLS session parameters in mid-stream. Only // applications that require different security-levels for different // transactions within the same session have use for renegotiation. // SetLastError(ss); TraceFunctLeaveEx((LPARAM)this); return FALSE; } if ( NT_SUCCESS(ss) ) { for (DWORD i=0;iIsMap()*/ ) { sc = g_QuerySecurityContextToken( pCtxtHandle, &hSSPToken ); if ( !NT_SUCCESS( sc ) || (hSSPToken == (HANDLE)0x00000001) ) { hSSPToken = NULL; } } if ( !fCert && hSSPToken != NULL ) { CloseHandle( hSSPToken ); hSSPToken = NULL; } if( !(m_dwSslAccessPerms & MD_ACCESS_MAP_CERT) && hSSPToken != NULL ) { DebugTrace( (LPARAM)this,"NT token not required - closing"); CloseHandle( hSSPToken ); hSSPToken = NULL; } if( (m_dwSslAccessPerms & MD_ACCESS_REQUIRE_CERT) && !fCert ) { // // We require client cert - bail ! // Converse will return ERROR_ACCESS_DENIED // _ASSERT( !hSSPToken ); return FALSE; } if( hSSPToken ) { _ASSERT( fCert ); m_hSSPToken = hSSPToken; m_IsAuthenticated = TRUE; } else if(m_dwSslAccessPerms & MD_ACCESS_MAP_CERT) { // // We need to map cert to token - but token is NULL // return FALSE; } } } return TRUE; error_exit: SetLastError(ss); return FALSE; } // EncryptConverse DWORD CEncryptCtx::Converse( IN PVOID InBuffer, IN DWORD InBufferSize, OUT LPBYTE OutBuffer, OUT PDWORD OutBufferSize, OUT PBOOL MoreBlobsExpected, IN LPSTR LocalIpAddr, IN LPSTR LocalPort, IN LPVOID lpvInstance, IN DWORD dwInstance, OUT PULONG pcbExtra ) /*++ Routine Description: Internal private routine for attempting to use a given protocol Arguments: InBuffer: ptr to apps input buffer InBufferSize: count of input buffer OutBuffer: ptr to apps output buffer OutBuffer: ptr to apps max size of output buffer and resultant output count MoreBlobsExpected: expect more data from the client ? LocalIpAddr: stringized local IP addr for the connection pcbExtra: See description of EncryptConverse Return Value: Win32/SSPI error code --*/ { TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::Converse"); DWORD dwMaxBuffer = *OutBufferSize; DWORD i, cbCreds; CredHandle* pCredArray = NULL; if ( m_IsNewSSLSession ) { if ( m_IsClient ) { // // Get the credentials for the client // LockGlobals(); if ( !LookupClientCredential( wszServiceName, (BOOL)m_dwSslAccessPerms, (CRED_CACHE_ITEM**)&m_phCreds ) ) { ErrorTrace( (LPARAM)this, "LookupClientCredential failed, error 0x%lx", GetLastError() ); UnlockGlobals(); goto error_exit; } UnlockGlobals(); } else { DebugTrace( (LPARAM)this, "LookupCredential for %S on %s", wszServiceName, LocalIpAddr ); // // Get the credentials for this IP address // LockGlobals(); if ( !LookupFullyQualifiedCredential( wszServiceName, LocalIpAddr, lstrlen(LocalIpAddr), LocalPort, lstrlen(LocalPort), lpvInstance, (CRED_CACHE_ITEM**)&m_phCreds, m_psmcMapContext, dwInstance )) { ErrorTrace( (LPARAM)this, "LookupCredential failed, error 0x%lx", GetLastError() ); UnlockGlobals(); goto error_exit; } UnlockGlobals(); } // // run thru all initialized credentials look for a package which // will accept this connection // CRED_CACHE_ITEM* phCreds = (CRED_CACHE_ITEM*)m_phCreds; // // For server: Need to use SSL access perms // to figure out whether to use CredMap or Cred // if( !m_IsClient && (m_dwSslAccessPerms & MD_ACCESS_MAP_CERT) ) { cbCreds = phCreds->m_cCredMap; pCredArray = phCreds->m_ahCredMap; } else { cbCreds = phCreds->m_cCred; pCredArray = phCreds->m_ahCred; } } else { // // hack to only allow one pass thru the loop // cbCreds = 1; pCredArray = m_phCredInUse; } // // Do the conversation // for ( i=0; i= pStartBuffer ); _ASSERT( pNextSeal <= pBuffer + cbInBuffer ); // // remove header, body and trailer from input buffer length // cbInBuffer -= (DWORD)(pNextSeal - pBuffer); pBuffer = pNextSeal; } else { // // in this case we received a seal message at the boundary // of the IO buffer // cbInBuffer = 0; break; } } *pcbParsable = cbParsable; *pcbOutBuffer= cbParsable + cbInBuffer; if ( fRet == FALSE ) { DWORD dwError = GetLastError(); DebugTrace( (LPARAM)this, "UnsealMessage returned: 0x%08X", GetLastError() ); // // deal with seal fragments at the end of the IO buffer // if ( dwError == SEC_E_INCOMPLETE_MESSAGE ) { _ASSERT( cbInBuffer != 0 ); // // move the remaining memory forward // MoveMemory( pStartBuffer + cbParsable, pBuffer, cbInBuffer ); DebugTrace( (LPARAM)this, "Seal fragment remaining: %d bytes", cbInBuffer ); } else { return dwError; } } return NO_ERROR; } //+--------------------------------------------------------------- // // Function: IsEncryptionPermitted // // Synopsis: This routine checks whether encryption is getting // the system default LCID and checking whether the // country code is CTRY_FRANCE. // // Arguments: void // // Returns: BOOL: supported // //---------------------------------------------------------------- BOOL IsEncryptionPermitted(void) { LCID DefaultLcid; CHAR CountryCode[10]; CHAR FranceCode[10]; DefaultLcid = GetSystemDefaultLCID(); // // Check if the default language is Standard French // if ( LANGIDFROMLCID( DefaultLcid ) == 0x40c ) { return FALSE; } // // Check if the users's country is set to FRANCE // if ( GetLocaleInfo( DefaultLcid, LOCALE_ICOUNTRY, CountryCode, sizeof(CountryCode) ) == 0 ) { return FALSE; } wsprintf( FranceCode, "%d", CTRY_FRANCE ); // // if the country codes matches France return FALSE // return lstrcmpi( CountryCode, FranceCode ) == 0 ? FALSE : TRUE ; } //+--------------------------------------------------------------- // // Function: GetAdminInfoEncryptCaps // // Synopsis: sets the magical buts to send the IIS admin program // // Arguments: PDWORD: ptr to the dword bitmask // // Returns: void // //---------------------------------------------------------------- VOID CEncryptCtx::GetAdminInfoEncryptCaps( PDWORD pdwEncCaps ) { *pdwEncCaps = 0; if ( m_IsSecureCapable == FALSE ) { *pdwEncCaps |= (IsEncryptionPermitted() ? ENC_CAPS_NOT_INSTALLED : ENC_CAPS_DISABLED ); } else { // // for all enabled encryption providers set the flags bit // for ( int j = 0; EncProviders[j].pszName != NULL; j++ ) { if ( TRUE == EncProviders[j].fEnabled ) { *pdwEncCaps |= EncProviders[j].dwFlags; } } } } ///////////////////////////////////////////////////////////////////////////// // // From here to the end of the file is code stolen from the Athena group from // the file called thorsspi.cpp // // minor mods have been made to incorporate msntrace functionality and to // fit in the CEncryptCtx class definition // ///////////////////////////////////////////////////////////////////////////// // // CompareDNStoCommonName() // // Description: // Compare a DNS name to a common name field value // // Parameters: // pDNS - string containing DNS name - *WARNING* will be munged // pCN - string containing common name field value // // Return: // TRUE if they match // // Comments: // There are two ways for these two strings to match. The first is that // they contain exactly the same characters. The second involved the use // of a single wildcard character in the common name field value. This // wildcard character ('*') can only be used once, and if used must be // the first character of the field. // // ASSUMES: the caller will allow pDNS and pCN to be uppercased and changed. // BOOL CompareDNStoCommonName(LPSTR pDNS, LPSTR pCN) { int nCountPeriods = 1; // start of DNS amount to virtual '.' as prefix BOOL fExactMatch = TRUE; LPSTR pBakDNS = pDNS; LPSTR pBakCN = pCN; _ASSERT(pDNS); _ASSERT(pCN); CharUpper(pDNS); CharUpper(pCN); while ((*pDNS == *pCN || *pCN == '*') && *pDNS && *pCN) { if (*pDNS != *pCN) fExactMatch = FALSE; if (*pCN == '*') { nCountPeriods = 0; if (*pDNS == '.') pCN++; else pDNS++; } else { if (*pDNS == '.') nCountPeriods++; pDNS++; pCN++; } } // if they are sized 0, we make sure not to say they match. if (pBakDNS == pDNS || pBakCN == pCN) fExactMatch = FALSE; return (*pDNS == 0) && (*pCN == 0) && ((nCountPeriods >= 2) || fExactMatch); } ///////////////////////////////////////////////////////////////////////////// // // CompareDNStoMultiCommonName() // // Description: // Compare a DNS name to a comma delimited list of common name fields. // // Parameters: // pDNS - string containing DNS name - *WARNING* will munge // pCN - string containing common name field value - *WARNING* will munge // // Return: // TRUE if they match // // ASSUMES: the caller will allow pDNS and pCN to be uppercased and changed. // BOOL CompareDNStoMultiCommonName(LPSTR pDNS, LPSTR pCN) { LPSTR pComma; LPSTR lpszCommonName; BOOL retval = FALSE; // assume we won't find a match BOOL done = FALSE; // assume we're not done lpszCommonName = pCN; do { // If there is a space, turn it into a null terminator to isolate the first // DNS name in the string lpszCommonName = strstr(lpszCommonName, "CN="); if (lpszCommonName) { // jump past "CN=" string lpszCommonName += 3; pComma = strchr(lpszCommonName, ','); if (pComma) *pComma = 0; // See if this component is a match retval = CompareDNStoCommonName(pDNS, lpszCommonName); // Now restore the comma (if any) that was overwritten if (pComma) { *pComma = ','; lpszCommonName = pComma + 1; } else { // If there were no more commas, then we're done done = TRUE; } } } while (!retval && !done && lpszCommonName && *lpszCommonName); return retval; } //----------------------------------------------------------------------------- // Description: // This function checks if pDns is a subdomain of pCn. // Basic rule: A wildcard character '*' at the beginning of a DNS name // matches any number of components. i.e. a wildcard implies that we will // match all subdomains of a given domain. // // microsoft.com == microsoft.com // *.microsoft.com == microsoft.com // *.microsoft.com == foo.microsoft.com // *.microsoft.com == foo.bar.microsoft.com // // Note that the arguments are modified (converted to uppercase). // Arguments: // pDns - DNS name to which we are trying to connect // pCn - Common name in certificate // Returns: // TRUE if subdomain, FALSE otherwise //----------------------------------------------------------------------------- BOOL MatchSubDomain (LPSTR pDns, LPSTR pCn) { LPSTR pCnBegin = NULL; int cbDns = 0; int cbCn = 0; _ASSERT (pDns); _ASSERT (pCn); CharUpper(pDns); CharUpper(pCn); cbDns = lstrlen (pDns); cbCn = lstrlen (pCn); // check if we have an initial wildcard: this is only allowed as "*.restofdomain" if (cbCn >= 2 && *pCn == '*') { pCn++; // we have a wildcard, try to get parent domain if (*pCn != '.') return FALSE; // Bad syntax, '.' must follow wildcard else pCn++; // Skip wildcard, get to parent domain part cbCn -= 2; // Update string length } if (cbDns < cbCn) // subdomains must be >= parent domains in length return FALSE; // // If subdomain is > parent domain, verify that there is a '.' between // the subdomain part and parent domain part. This is to guard from matching // *.microsoft.com with foobarmicrosoft.com since all we do after this // line of code is check that the parent is a substring of the subdomain // at the end. // if (cbDns != cbCn && pDns[cbDns - cbCn - 1] != '.') return FALSE; pCnBegin = pCn; pCn += cbCn; pDns += cbDns; // Walk backwards doing matching for (; pCnBegin <= pCn && *pCn == *pDns; pCn--, pDns--); // // Check if we terminated without a mismatch, // return (pCnBegin > pCn); } #define CompareCertTime(ft1, ft2) (((*(LONGLONG *)&ft1) > (*(LONGLONG *)&ft2)) \ ? 1 \ : (((*(LONGLONG *)&ft1) == (*(LONGLONG *)&ft2)) ? 0 : -1 )) //+--------------------------------------------------------------- // // Function: CheckCertificateCommonName // // Synopsis: verifies the intended host name matches the // the name contained in the certificate // This function, checks a given hostname against the current certificate // stored in an active SSPI Context Handle. If the certificate containts // a common name, and it matches the passed in hostname, this function // will return TRUE // // Arguments: IN LPSTR: DNS host name // // Returns: BOOL // //---------------------------------------------------------------- BOOL CEncryptCtx::CheckCertificateCommonName( IN LPSTR pszHostName ) { DWORD dwErr; BOOL fIsCertGood = FALSE; SecPkgContext_Names CertNames; TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::CheckCertificateCommonName" ); CertNames.sUserName = NULL; if ( !pszHostName ) { goto quit; } dwErr = g_QueryContextAttributes(&m_hSealCtxt, SECPKG_ATTR_NAMES, (PVOID)&CertNames); if (dwErr != ERROR_SUCCESS) { ErrorTrace( (LPARAM)this, "QueryContextAttributes failed to retrieve CN, returned %#x", dwErr ); goto quit; } DebugTrace( (LPARAM)this, "QueryContextAttributes returned CN=%.200s", CertNames.sUserName ); fIsCertGood = CompareDNStoMultiCommonName(pszHostName, CertNames.sUserName); quit: if ( CertNames.sUserName ) { LocalFree( CertNames.sUserName ); } return fIsCertGood; } //------------------------------------------------------------------------- // Description: // Verifies that the subject of the certificate matches the FQDN of // the server we are talking to. Does some wildcard matching if there // are '*' characters at the beginning of the cert subject. // Arguments: // pCtxtHandle The context of an established SSL connection // pszServerFqdn FQDN of server to which we are talking (from who we // received the certificate). // Returns: // TRUE match found, FALSE unmatched //------------------------------------------------------------------------- BOOL CEncryptCtx::CheckCertificateSubjectName (IN LPSTR pszServerFqdn) { CHAR pszSubjectStackBuf[256]; LPSTR pszSubject = NULL; PCCERT_CONTEXT pCertContext = NULL; DWORD dwErr = ERROR_SUCCESS; DWORD cSize = 0; DWORD cSubject = 0; BOOL fRet = FALSE; TraceFunctEnterEx ((LPARAM) this, "CEncryptCtx::VerifySubject"); _ASSERT (pszServerFqdn); dwErr = g_QueryContextAttributes( &m_hSealCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pCertContext); if (dwErr != SEC_E_OK) { StateTrace ((LPARAM) this, "Cannot get Context Handle %x", dwErr); goto Exit; } // // Check the size of the buffer needed, if it's small enough we'll // just use the fixed size stack buffer, otherwise allocate on heap. // cSize = CertGetNameString ( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0); if (cSize <= sizeof(pszSubjectStackBuf)) { pszSubject = pszSubjectStackBuf; cSubject = sizeof (pszSubjectStackBuf); } else { pszSubject = new CHAR [cSize]; if (NULL == pszSubject) { ErrorTrace ((LPARAM) this, "No memory to alloc subject string."); goto Exit; } cSubject = cSize; } // // Get the subject of the certificate // cSize = CertGetNameString ( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, pszSubject, cSubject); if (cSize == 1 && pszSubject[0] == '\0') { // // If the CERT_NAME_SIMPLE_DISPLAY_TYPE could not be found in the cert, // the API returns a zero length NULL terminated string. // StateTrace ((LPARAM) this, "Certificate subject not found"); goto Exit; } StateTrace ((LPARAM) this, "Certificate subject: %s, FQDN: %s", pszSubject, pszServerFqdn); if (MatchSubDomain(pszServerFqdn, pszSubject)) { // // Certificate matches the server FQDN we're talking to // fRet = TRUE; } Exit: // // Delete the Subject buffer if we were using the heap // if (pszSubject != pszSubjectStackBuf) delete [] pszSubject; if (pCertContext) CertFreeCertificateContext (pCertContext); StateTrace ((LPARAM) this, "Returning: %s", fRet ? "TRUE" : "FALSE"); TraceFunctLeaveEx ((LPARAM) this); return fRet; } //--------------------------------------------------------------------- // Description: // Checks if the certificate for this CEncryptCtx chains up to a // trusted CA. // Returns: // TRUE if certificate is trusted. // FALSE if the certificate is untrusted or if trust could not be // verified (temporary errors can cause this). // Source: // MSDN sample //--------------------------------------------------------------------- BOOL CEncryptCtx::CheckCertificateTrust () { BOOL fRet = FALSE; DWORD dwErr = SEC_E_OK; DWORD dwFlags = 0; PCCERT_CONTEXT pCertContext = NULL; PCCERT_CHAIN_CONTEXT pChainContext = NULL; CERT_ENHKEY_USAGE EnhkeyUsage; CERT_USAGE_MATCH CertUsage; CERT_CHAIN_PARA ChainPara; TraceFunctEnterEx ((LPARAM) this, "CEncryptCtx::CheckCertificateTrust"); dwErr = g_QueryContextAttributes ( &m_hSealCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pCertContext); if (SEC_E_OK != dwErr) { ErrorTrace ((LPARAM) this, "g_QueryContextAttributes failed, err - %8x", dwErr); fRet = FALSE; goto Exit; } // // ChainPara is a struct used to match specific certificates using OIDs // We don't need this and initialize it to empty (no OIDs) // EnhkeyUsage.cUsageIdentifier = 0; EnhkeyUsage.rgpszUsageIdentifier = NULL; CertUsage.dwType = USAGE_MATCH_TYPE_AND; CertUsage.Usage = EnhkeyUsage; ChainPara.cbSize = sizeof(CERT_CHAIN_PARA); ChainPara.RequestedUsage = CertUsage; dwFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN | CERT_CHAIN_CACHE_END_CERT; fRet = CertGetCertificateChain ( NULL, // Use the default chaining engine pCertContext, // The end certificate to be checked NULL, // Expiration checking... use currenttime NULL, // Additional cert stores: none &ChainPara, // Chaining criteria: none, this is an empty struct dwFlags, // Options: how to check chain NULL, // reserved param &pChainContext);// Returned chain context if (!fRet) { dwErr = GetLastError (); ErrorTrace ((LPARAM) this, "Unable to create certificate chain, err - %8x", dwErr); goto Exit; } DebugTrace ((LPARAM) this, "Status of certificate chain - %8x", pChainContext->TrustStatus.dwErrorStatus); if (CERT_TRUST_NO_ERROR == pChainContext->TrustStatus.dwErrorStatus) { DebugTrace ((LPARAM) this, "Certificate trust verified"); fRet = TRUE; } else { ErrorTrace ((LPARAM) this, "Certificate is untrusted, status - %8x", pChainContext->TrustStatus.dwErrorStatus); fRet = FALSE; } Exit: if (pCertContext) CertFreeCertificateContext (pCertContext); if (pChainContext) CertFreeCertificateChain (pChainContext); TraceFunctLeaveEx ((LPARAM) this); return fRet; } //+--------------------------------------------------------------- // // Function: CheckCertificateExpired // // Synopsis: verifies the ccertificate has not expired // returns TRUE if the cert is valid // // Arguments: void // // Returns: BOOL cert is good // //---------------------------------------------------------------- BOOL CEncryptCtx::CheckCertificateExpired( void ) { SYSTEMTIME st; FILETIME ftCurTime; DWORD dwErr; SecPkgContext_Lifespan CertLifeSpan; TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::CheckCertificateExpired" ); GetSystemTime(&st); if (!SystemTimeToFileTime(&st, &ftCurTime)) return FALSE; dwErr = g_QueryContextAttributes(&m_hSealCtxt, SECPKG_ATTR_LIFESPAN, (PVOID)&CertLifeSpan); if ( dwErr != ERROR_SUCCESS ) { ErrorTrace( (LPARAM)this, "QueryContextAttributes failed to retrieve cert lifespan, returned %#x", dwErr); return FALSE; } return CompareCertTime( CertLifeSpan.tsStart, ftCurTime ) < 0 && CompareCertTime( CertLifeSpan.tsExpiry, ftCurTime) > 0 ; } //+---------------------------------------------------------------------------- // // Function: CheckServerCert // // Synopsis: Checks to see if a server cert has been installed. // // Arguments: [LocalIpAddr] -- IP Address of virtual server // [LocalPort] -- Port of virtual server // [lpvInstance] -- Pointer to IIS_SERVER_INSTANCE object // [dwInstance] -- Virtual server id // // Returns: TRUE if there is a cert for this virtual server // //----------------------------------------------------------------------------- BOOL CEncryptCtx::CheckServerCert( IN LPSTR LocalIpAddr, IN LPSTR LocalPort, IN LPVOID lpvInstance, IN DWORD dwInstance) { TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::CheckServerCert" ); BOOL fRet = FALSE; CRED_CACHE_ITEM *pCCI; DebugTrace( (LPARAM)this, "CheckServerCert for %S on %s", wszServiceName, LocalIpAddr ); // // Get the credentials for this IP address // LockGlobals(); if ( fRet = LookupFullyQualifiedCredential( wszServiceName, LocalIpAddr, lstrlen(LocalIpAddr), LocalPort, lstrlen(LocalPort), lpvInstance, &pCCI, m_psmcMapContext, dwInstance )) { IIS_SERVER_CERT *pCert = pCCI->m_pSslInfo->GetCertificate(); // Log the status of the cert if we got one if ( pCert ) { fRet = TRUE; } else fRet = FALSE; pCCI->Release(); } else { ErrorTrace( (LPARAM)this, "LookupCredential failed, error 0x%lx", GetLastError() ); } UnlockGlobals(); TraceFunctLeave(); return( fRet ); } //+--------------------------------------------------------------- // // Function: NotifySslChanges // // Synopsis: This is called when SSL settings change // // Arguments: dwNotifyType // pInstance // // Returns: VOID // //---------------------------------------------------------------- VOID WINAPI NotifySslChanges( DWORD dwNotifyType, LPVOID pInstance ) { LockGlobals(); if ( dwNotifyType == SIMSSL_NOTIFY_MAPPER_SSLKEYS_CHANGED ) { FreeCredCache(); // // NB : Under NT 5, the SslEmptyCache function is no longer supported // #if 0 if ( g_pfnFlushSchannelCache ) { (g_pfnFlushSchannelCache)(); } #endif } else if ( dwNotifyType == SIMSSL_NOTIFY_MAPPER_CERT11_CHANGED || dwNotifyType == SIMSSL_NOTIFY_MAPPER_CERTW_CHANGED ) { FreeCredCache(); } else if ( dwNotifyType == SIMSSL_NOTIFY_MAPPER_CERT11_TOUCHED ) { // // NB : Under NT 5, the SslEmptyCache function is no longer supported // #if 0 if ( g_pfnFlushSchannelCache ) { (g_pfnFlushSchannelCache)(); } #endif //SSPI_FILTER_CONTEXT::FlushOnDelete(); } else { _ASSERT( FALSE ); } UnlockGlobals(); }