Leaked source code of windows server 2003
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

/*++
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();
}