You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2486 lines
68 KiB
2486 lines
68 KiB
/*++
|
|
|
|
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 <dbgutil.h>
|
|
#include <tcpdll.hxx>
|
|
#include <tcpsvcs.h>
|
|
#include <tcpdebug.h>
|
|
#include <tsvcinfo.hxx>
|
|
#include <inetdata.h>
|
|
*/
|
|
|
|
extern "C" {
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
}
|
|
|
|
#include <windows.h>
|
|
#include <wincrypt.h>
|
|
#include <stdlib.h>
|
|
#include <dbgtrace.h>
|
|
|
|
#include <inetinfo.h>
|
|
//
|
|
// SSL and SSPI related include files
|
|
//
|
|
|
|
#include <dbgutil.h>
|
|
#include <buffer.hxx>
|
|
#include <ole2.h>
|
|
#include <imd.h>
|
|
#include <iadm.h>
|
|
#include <mb.hxx>
|
|
|
|
#define DEFINE_SIMSSL_GLOBAL
|
|
|
|
extern "C" {
|
|
#define SECURITY_WIN32
|
|
#include <sspi.h>
|
|
#include <ntsecapi.h>
|
|
#include <spseal.h>
|
|
//#include <sslsp.h>
|
|
#include <schnlsp.h>
|
|
#include ".\credcach.hxx"
|
|
}
|
|
|
|
#include <certnotf.hxx>
|
|
//#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;i<inputBuffer.cBuffers;i++)
|
|
{
|
|
if ( inBuffers[i].BufferType == SECBUFFER_DATA )
|
|
{
|
|
*DecryptedMessage = (LPBYTE)inBuffers[i].pvBuffer;
|
|
*DecryptedMessageSize = inBuffers[i].cbBuffer;
|
|
|
|
DebugTrace( (LPARAM)this,
|
|
"unsealed ptr: 0x%08X, count: %d",
|
|
*DecryptedMessage, *DecryptedMessageSize );
|
|
|
|
//
|
|
// if the app wants to know the start of the next seal msg
|
|
//
|
|
if ( NextSealMessage != NULL )
|
|
{
|
|
for ( ;i<inputBuffer.cBuffers;i++ )
|
|
{
|
|
if ( inBuffers[i].BufferType == SECBUFFER_EXTRA )
|
|
{
|
|
*NextSealMessage = (LPBYTE)inBuffers[i].pvBuffer;
|
|
DebugTrace( (LPARAM)this,
|
|
"Found extra buffer: 0x%08X",
|
|
*NextSealMessage );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
else if( ss == SEC_E_INCOMPLETE_MESSAGE )
|
|
{
|
|
for( DWORD i=0; i<inputBuffer.cBuffers;i++ )
|
|
{
|
|
if( inBuffers[i].BufferType == SECBUFFER_MISSING )
|
|
{
|
|
*ExpectedMessageSize = inBuffers[i].cbBuffer;
|
|
}
|
|
}
|
|
}
|
|
SetLastError(ss);
|
|
}
|
|
return FALSE;
|
|
} // UnsealMessage
|
|
|
|
|
|
//
|
|
// set this define to allow schannel to allocate responses in InitializeSecurityContext
|
|
//
|
|
#define SSPI_ALLOCATE_MEMORY
|
|
|
|
|
|
DWORD
|
|
CEncryptCtx::EncryptConverse(
|
|
IN PVOID InBuffer,
|
|
IN DWORD InBufferSize,
|
|
OUT LPBYTE OutBuffer,
|
|
IN OUT PDWORD OutBufferSize,
|
|
OUT PBOOL MoreBlobsExpected,
|
|
IN CredHandle* pCredHandle,
|
|
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 ?
|
|
pCredHandle: ptr to the credential handle to use
|
|
pcbExtra: Sometimes, even after the handshake succeeds, all the data in InBuffer
|
|
may not be used up. This is because the other side may have started sending
|
|
non-handshake (application) data. The param returns the length of this unprocessed
|
|
"tail" which should be processed using Decrypt functions.
|
|
|
|
Return Value:
|
|
|
|
TRUE if negotiation succeeded.
|
|
FALSE if negotiation failed.
|
|
|
|
--*/
|
|
SECURITY_STATUS ss;
|
|
DWORD error = NO_ERROR;
|
|
SecBufferDesc inBuffDesc;
|
|
SecBuffer inSecBuff[2];
|
|
SecBufferDesc outBuffDesc;
|
|
SecBuffer outSecBuff;
|
|
PCtxtHandle pCtxtHandle;
|
|
DWORD contextAttributes = 0 ;
|
|
TimeStamp lifeTime;
|
|
DWORD dwMaxBuffer = *OutBufferSize;
|
|
SECURITY_STATUS sc;
|
|
SECURITY_STATUS scR;
|
|
HANDLE hSSPToken = NULL;
|
|
PCCERT_CONTEXT pClientCert = NULL;
|
|
|
|
// Init vars used to check/return the number of bytes unprocessed by SSL handshake
|
|
_ASSERT (pcbExtra);
|
|
inSecBuff[1].BufferType = SECBUFFER_EMPTY;
|
|
*pcbExtra = 0;
|
|
|
|
BOOL fCert = TRUE;
|
|
SecPkgContext_StreamSizes sizes;
|
|
#ifdef DEBUG
|
|
SecPkgContext_ProtoInfo spcPInfo;
|
|
SECURITY_STATUS ssProto;
|
|
#endif
|
|
SecPkgContext_KeyInfo spcKInfo;
|
|
SECURITY_STATUS ssInfo;
|
|
|
|
//
|
|
// See if we have enough data
|
|
//
|
|
|
|
TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::EncryptConverse");
|
|
|
|
pCtxtHandle = &m_hSealCtxt;
|
|
|
|
_ASSERT(OutBuffer != NULL && "Must pass in an OutBuffer");
|
|
if(NULL == OutBuffer) {
|
|
ss = E_INVALIDARG;
|
|
goto error_exit;
|
|
}
|
|
|
|
ScanNextPacket:
|
|
outBuffDesc.ulVersion = 0;
|
|
outBuffDesc.cBuffers = 1;
|
|
outBuffDesc.pBuffers = &outSecBuff;
|
|
|
|
//
|
|
// need to set cbBuffer to zero because sslsspi will leave it
|
|
// uninitialized when the converse completes
|
|
//
|
|
outSecBuff.cbBuffer = dwMaxBuffer;
|
|
outSecBuff.BufferType = SECBUFFER_TOKEN;
|
|
outSecBuff.pvBuffer = OutBuffer;
|
|
|
|
//
|
|
// Prepare our Input buffer - Note the server is expecting the client's
|
|
// negotiation packet on the first call
|
|
//
|
|
|
|
if ( ARGUMENT_PRESENT(InBuffer) )
|
|
{
|
|
inBuffDesc.ulVersion = 0;
|
|
inBuffDesc.cBuffers = 2;
|
|
inBuffDesc.pBuffers = &inSecBuff[0];
|
|
|
|
inSecBuff[0].cbBuffer = InBufferSize;
|
|
inSecBuff[0].BufferType = SECBUFFER_TOKEN;
|
|
inSecBuff[0].pvBuffer = InBuffer;
|
|
|
|
inSecBuff[1].cbBuffer = 0;
|
|
inSecBuff[1].BufferType = SECBUFFER_EMPTY;
|
|
inSecBuff[1].pvBuffer = NULL;
|
|
}
|
|
|
|
if ( m_IsClient ) {
|
|
|
|
DWORD contextAttributes;
|
|
LPVOID pvBuffer;
|
|
DWORD cbBuffer;
|
|
|
|
//
|
|
// Note the client will return success when its done but we still
|
|
// need to send the out buffer if there are bytes to send
|
|
//
|
|
|
|
#ifdef SSPI_ALLOCATE_MEMORY
|
|
pvBuffer = outSecBuff.pvBuffer;
|
|
cbBuffer = outSecBuff.cbBuffer;
|
|
|
|
outSecBuff.pvBuffer = NULL;
|
|
outSecBuff.cbBuffer = 0;
|
|
#endif
|
|
|
|
DWORD dwIscReq = ISC_REQ_STREAM |
|
|
ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_EXTENDED_ERROR |
|
|
ISC_REQ_MANUAL_CRED_VALIDATION | // to remove implicit call to WinVerifyTRust
|
|
#ifdef SSPI_ALLOCATE_MEMORY
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
#endif
|
|
ISC_REQ_CONFIDENTIALITY;
|
|
|
|
if( ( m_dwSslAccessPerms & MD_ACCESS_NEGO_CERT ) ||
|
|
( m_dwSslAccessPerms & MD_ACCESS_REQUIRE_CERT) ||
|
|
( m_dwSslAccessPerms & MD_ACCESS_MAP_CERT ) ) {
|
|
dwIscReq |= ISC_REQ_MUTUAL_AUTH;
|
|
}
|
|
|
|
if (m_IsNewSSLSession) {
|
|
dwIscReq |= ISC_REQ_USE_SUPPLIED_CREDS;
|
|
}
|
|
|
|
ss = g_InitializeSecurityContext(
|
|
pCredHandle,
|
|
m_IsNewSSLSession ? NULL : pCtxtHandle,
|
|
wszServiceName,
|
|
dwIscReq,
|
|
0,
|
|
SECURITY_NATIVE_DREP,
|
|
m_IsNewSSLSession ? NULL : &inBuffDesc,
|
|
0,
|
|
pCtxtHandle,
|
|
&outBuffDesc,
|
|
&contextAttributes,
|
|
&lifeTime
|
|
);
|
|
|
|
#ifdef SSPI_ALLOCATE_MEMORY
|
|
if ( NT_SUCCESS( ss ) && outSecBuff.pvBuffer )
|
|
{
|
|
DebugTrace( (LPARAM)this,
|
|
"Output %d bytes, Maximum %d bytes",
|
|
outSecBuff.cbBuffer,
|
|
cbBuffer );
|
|
|
|
if ( outSecBuff.cbBuffer <= cbBuffer )
|
|
{
|
|
CopyMemory( pvBuffer, outSecBuff.pvBuffer, outSecBuff.cbBuffer );
|
|
}
|
|
else
|
|
{
|
|
ss = SEC_E_INSUFFICIENT_MEMORY;
|
|
}
|
|
|
|
g_FreeContextBuffer( outSecBuff.pvBuffer );
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
|
|
//
|
|
// This is the server side
|
|
//
|
|
|
|
DWORD dwAscReq = ASC_REQ_STREAM |
|
|
ASC_REQ_CONFIDENTIALITY |
|
|
ASC_REQ_EXTENDED_ERROR |
|
|
ASC_REQ_SEQUENCE_DETECT |
|
|
ASC_REQ_REPLAY_DETECT;
|
|
|
|
//
|
|
// Set the mutual auth attribute if we are configured
|
|
// to negotiate, require or map client certs
|
|
//
|
|
|
|
if( ( m_dwSslAccessPerms & MD_ACCESS_NEGO_CERT ) ||
|
|
( m_dwSslAccessPerms & MD_ACCESS_REQUIRE_CERT) ||
|
|
( m_dwSslAccessPerms & MD_ACCESS_MAP_CERT ) ) {
|
|
dwAscReq |= ASC_REQ_MUTUAL_AUTH;
|
|
}
|
|
|
|
ss = g_AcceptSecurityContext(
|
|
pCredHandle,
|
|
m_IsNewSSLSession ? NULL : pCtxtHandle,
|
|
&inBuffDesc,
|
|
dwAscReq,
|
|
SECURITY_NATIVE_DREP,
|
|
pCtxtHandle,
|
|
&outBuffDesc,
|
|
&contextAttributes,
|
|
&lifeTime
|
|
);
|
|
|
|
DebugTrace((LPARAM)this,
|
|
"AcceptSecurityContext returned win32 error %d (%x)",
|
|
ss, ss);
|
|
|
|
if( outSecBuff.pvBuffer != OutBuffer && outSecBuff.cbBuffer ) {
|
|
//
|
|
// SSPI workaround - if the buffer got allocated by SSPI
|
|
// copy it over and free it..
|
|
//
|
|
|
|
if ( outSecBuff.cbBuffer <= dwMaxBuffer )
|
|
{
|
|
CopyMemory( OutBuffer, outSecBuff.pvBuffer, outSecBuff.cbBuffer );
|
|
}
|
|
else
|
|
{
|
|
ss = SEC_E_INSUFFICIENT_MEMORY;
|
|
}
|
|
|
|
g_FreeContextBuffer( outSecBuff.pvBuffer );
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Negotiation succeeded, there is extra stuff left in the buffer
|
|
// Return the number of the unused bytes.
|
|
//
|
|
if( ss == SEC_E_OK && inSecBuff[1].BufferType == SECBUFFER_EXTRA ) {
|
|
*pcbExtra = inSecBuff[1].cbBuffer;
|
|
|
|
} else if( ss == SEC_I_CONTINUE_NEEDED && inBuffDesc.pBuffers[1].cbBuffer ) {
|
|
//
|
|
// Need to process next SSL packet by calling Init/AcceptSecurityContext again
|
|
// Should ASSERT that InBuffer <= OrigInBuffer + OrigInBufferSize
|
|
//
|
|
_ASSERT( !outSecBuff.cbBuffer );
|
|
InBuffer = (LPSTR)InBuffer + InBufferSize - inBuffDesc.pBuffers[1].cbBuffer;
|
|
InBufferSize = inBuffDesc.pBuffers[1].cbBuffer;
|
|
goto ScanNextPacket;
|
|
|
|
} else if ( ss == SEC_E_INCOMPLETE_MESSAGE ) {
|
|
//
|
|
// Not enough data from server... need to read more before proceeding
|
|
// If there is unconsumed data, the new data is to be appended to it
|
|
//
|
|
*pcbExtra = InBufferSize;
|
|
|
|
} else if ( !NT_SUCCESS( ss ) ) {
|
|
|
|
ErrorTrace( (LPARAM)this,"%s failed with %x\n",
|
|
m_IsClient ?
|
|
"InitializeSecurityContext" :
|
|
"AcceptSecurityContext",
|
|
ss );
|
|
|
|
if ( ss == SEC_E_LOGON_DENIED ) {
|
|
ss = ERROR_LOGON_FAILURE;
|
|
}
|
|
goto error_exit;
|
|
}
|
|
|
|
//
|
|
// Only query the context attributes if this is a new session, and the
|
|
// Accept/Initialize have returned SEC_E_OK (ie, the channel has been fully
|
|
// established)
|
|
//
|
|
if( ss == SEC_E_OK ) {
|
|
|
|
ssInfo = g_QueryContextAttributes(
|
|
pCtxtHandle,
|
|
SECPKG_ATTR_KEY_INFO,
|
|
&spcKInfo
|
|
);
|
|
|
|
if ( ssInfo != SEC_E_OK ) {
|
|
ErrorTrace( (LPARAM)this,
|
|
"Cannot get SSL\\PCT Key Info. Err %x",ssInfo );
|
|
//goto error_exit;
|
|
} else {
|
|
// Note the key size
|
|
m_dwKeySize = spcKInfo.KeySize;
|
|
if ( spcKInfo.sSignatureAlgorithmName )
|
|
g_FreeContextBuffer( spcKInfo.sSignatureAlgorithmName );
|
|
if ( spcKInfo.sEncryptAlgorithmName )
|
|
g_FreeContextBuffer( spcKInfo.sEncryptAlgorithmName );
|
|
}
|
|
}
|
|
|
|
m_haveSSLCtxtHandle = TRUE;
|
|
|
|
//
|
|
// Now we just need to complete the token (if requested) and prepare
|
|
// it for shipping to the other side if needed
|
|
//
|
|
|
|
if ( (ss == SEC_I_COMPLETE_NEEDED) ||
|
|
(ss == SEC_I_COMPLETE_AND_CONTINUE) ) {
|
|
|
|
ss = g_CompleteAuthToken( pCtxtHandle, &outBuffDesc );
|
|
|
|
if ( !NT_SUCCESS( ss )) {
|
|
ErrorTrace( (LPARAM)this,
|
|
"CompleteAuthToken failed. Err %x",ss );
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
*OutBufferSize = outSecBuff.cbBuffer;
|
|
|
|
*MoreBlobsExpected= (ss == SEC_I_CONTINUE_NEEDED) ||
|
|
(ss == SEC_I_COMPLETE_AND_CONTINUE) ||
|
|
(ss == SEC_E_INCOMPLETE_MESSAGE);
|
|
|
|
if ( *MoreBlobsExpected == FALSE )
|
|
{
|
|
|
|
//
|
|
// HACK: SSLSSPI leaves outSecBuff.cbBuffer uninitialized
|
|
// after final successful call for client side connections
|
|
//
|
|
if ( m_IsClient && *OutBufferSize == dwMaxBuffer )
|
|
{
|
|
*OutBufferSize = 0;
|
|
}
|
|
|
|
//
|
|
// we're done so get the SSPI header/trailer sizes for SealMessage
|
|
//
|
|
ss = g_QueryContextAttributes(
|
|
pCtxtHandle,
|
|
SECPKG_ATTR_STREAM_SIZES,
|
|
&sizes
|
|
);
|
|
|
|
if ( ss != SEC_E_OK ) {
|
|
ErrorTrace( (LPARAM)this,
|
|
"Cannot get SSL\\PCT Header Length. Err %x",ss );
|
|
goto error_exit;
|
|
}
|
|
|
|
m_cbSealHeaderSize = sizes.cbHeader;
|
|
m_cbSealTrailerSize = sizes.cbTrailer;
|
|
|
|
DebugTrace( (LPARAM)this, "Header: %d, Trailer: %d",
|
|
m_cbSealHeaderSize, m_cbSealTrailerSize );
|
|
|
|
if(!m_IsClient)
|
|
{
|
|
scR = g_QueryContextAttributes( pCtxtHandle,
|
|
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
|
|
&pClientCert );
|
|
|
|
if ( !NT_SUCCESS( scR ) || !pClientCert )
|
|
{
|
|
fCert = FALSE;
|
|
}
|
|
else
|
|
{
|
|
CertFreeCertificateContext( pClientCert);
|
|
DebugTrace((LPARAM)this, "[OnAuthorizationInfo] Certificate available!\n");
|
|
}
|
|
|
|
//
|
|
// check if client authentication available
|
|
//
|
|
|
|
if ( 1 /*|| pssc->IsMap()*/ )
|
|
{
|
|
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<cbCreds; i++, pCredArray++ )
|
|
{
|
|
if ( EncryptConverse(InBuffer,
|
|
InBufferSize,
|
|
OutBuffer,
|
|
OutBufferSize,
|
|
MoreBlobsExpected,
|
|
pCredArray,
|
|
pcbExtra ) )
|
|
{
|
|
if ( m_IsNewSSLSession )
|
|
{
|
|
//
|
|
// if the first time remember which credential succeeded.
|
|
//
|
|
m_phCredInUse = pCredArray;
|
|
m_iCredInUse = i;
|
|
|
|
m_IsNewSSLSession = FALSE;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We failed
|
|
//
|
|
|
|
error_exit:
|
|
|
|
if(OutBuffer)
|
|
*OutBuffer = 0;
|
|
|
|
if(OutBufferSize)
|
|
*OutBufferSize = 0;
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
if ( error == NO_ERROR ) {
|
|
error = ERROR_ACCESS_DENIED;
|
|
}
|
|
return error;
|
|
|
|
} // Converse
|
|
|
|
|
|
|
|
//+---------------------------------------------------------------
|
|
//
|
|
// Function: DecryptInputBuffer
|
|
//
|
|
// Synopsis: decrypted input read from the client
|
|
//
|
|
// Arguments: pBuffer: ptr to the input/output buffer
|
|
// cbInBuffer: initial input length of the buffer
|
|
// pcbOutBuffer: total length of decrypted/remaining
|
|
// data. pcbOutBuffer - pcbParsable is
|
|
// the length of offset for next read
|
|
// pcbParsable: length of decrypted data
|
|
// pcbExpected: length of remaining unread data for
|
|
// full SSL message
|
|
//
|
|
// Returns: DWORD Win32/SSPI error core
|
|
//
|
|
//----------------------------------------------------------------
|
|
DWORD CEncryptCtx::DecryptInputBuffer(
|
|
IN LPBYTE pBuffer,
|
|
IN DWORD cbInBuffer,
|
|
OUT DWORD* pcbOutBuffer,
|
|
OUT DWORD* pcbParsable,
|
|
OUT DWORD* pcbExpected
|
|
)
|
|
{
|
|
LPBYTE pDecrypted;
|
|
DWORD cbDecrypted;
|
|
DWORD cbParsable = 0;
|
|
LPBYTE pNextSeal;
|
|
LPBYTE pStartBuffer = pBuffer;
|
|
BOOL fRet;
|
|
|
|
//
|
|
// initialize to zero so app does not inadvertently post large read
|
|
//
|
|
*pcbExpected = 0;
|
|
|
|
TraceFunctEnterEx( (LPARAM)this, "CEncryptCtx::DecryptInputBuffer" );
|
|
|
|
while( fRet = UnsealMessage(pBuffer,
|
|
cbInBuffer,
|
|
&pDecrypted,
|
|
&cbDecrypted,
|
|
pcbExpected,
|
|
&pNextSeal ) )
|
|
{
|
|
DebugTrace( (LPARAM)this,
|
|
"Decrypted %d bytes at offset %d",
|
|
cbDecrypted,
|
|
pDecrypted - pStartBuffer );
|
|
|
|
//
|
|
// move the decrypted data to the front of buffer
|
|
//
|
|
MoveMemory( pStartBuffer + cbParsable,
|
|
pDecrypted,
|
|
cbDecrypted );
|
|
|
|
//
|
|
// increment where the next parsing should take place
|
|
//
|
|
cbParsable += cbDecrypted;
|
|
|
|
//
|
|
// move to the next potential seal buffer
|
|
//
|
|
if ( pNextSeal != NULL )
|
|
{
|
|
_ASSERT( pNextSeal >= 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();
|
|
}
|
|
|
|
|