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.
 
 
 
 
 
 

2691 lines
78 KiB

/*++
Copyright (c) 1998 Microsoft Corporation
Module Name :
sslcontext.cxx
Abstract:
SSL stream context
Author:
Bilal Alam (BAlam) 29-March-2000
Environment:
Win32 - User Mode
Project:
Stream Filter Worker Process
--*/
#include "precomp.hxx"
//static
ALLOC_CACHE_HANDLER * SSL_STREAM_CONTEXT::sm_pachSslStreamContexts = NULL;
//
// By default we don't allow CAPI to automatically download intermediate certificates
// off the network
//
//static
BOOL SSL_STREAM_CONTEXT::sm_fCertChainCacheOnlyUrlRetrieval = TRUE;
enum SSL_STREAM_CONTEXT::INIT_STATE
SSL_STREAM_CONTEXT::s_InitState = INIT_NONE;
//static
HRESULT
SSL_STREAM_CONTEXT::Initialize(
VOID
)
/*++
Routine Description:
Initialize all SSL global data
Arguments:
None
Return Value:
HRESULT
--*/
{
HRESULT hr = S_OK;
ALLOC_CACHE_CONFIGURATION acConfig;
HKEY hKeyParam = NULL;
DWORD dwType = 0;
DWORD dwValue = 0;
hr = CERT_STORE::Initialize();
if ( FAILED( hr ) )
{
goto Finished;
}
s_InitState = INIT_CERT_STORE;
hr = SERVER_CERT::Initialize();
if ( FAILED( hr ) )
{
goto Finished;
}
s_InitState = INIT_SERVER_CERT;
hr = IIS_CTL::Initialize();
if ( FAILED( hr ) )
{
goto Finished;
}
s_InitState = INIT_IIS_CTL;
hr = SITE_CREDENTIALS::Initialize();
if ( FAILED( hr ) )
{
goto Finished;
}
s_InitState = INIT_SITE_CREDENTIALS;
//
// ENDPOINT_CONFIG uses
// SERVER_CERT,
// SITE_CREDENTIALS and
//
hr = ENDPOINT_CONFIG::Initialize();
if ( FAILED( hr ) )
{
goto Finished;
}
s_InitState = INIT_ENDPOINT_CONFIG;
//
// Setup allocation lookaside
//
acConfig.nConcurrency = 1;
acConfig.nThreshold = 100;
acConfig.cbSize = sizeof( SSL_STREAM_CONTEXT );
DBG_ASSERT( sm_pachSslStreamContexts == NULL );
sm_pachSslStreamContexts = new ALLOC_CACHE_HANDLER( "SSL_STREAM_CONTEXT",
&acConfig );
if ( sm_pachSslStreamContexts == NULL )
{
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
goto Finished;
}
s_InitState = INIT_ACACHE;
//
// Read registry parameters
//
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
REGISTRY_KEY_HTTPFILTER_PARAMETERS_W,
0,
KEY_READ,
&hKeyParam ) == NO_ERROR )
{
DWORD dwBytes = sizeof( dwValue );
DWORD dwErr = RegQueryValueExW( hKeyParam,
SZ_REG_CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL,
NULL,
&dwType,
( LPBYTE )&dwValue,
&dwBytes
);
if ( ( dwErr == ERROR_SUCCESS ) &&
( dwType == REG_DWORD ) )
{
//
// Do double negation to convert DWORD to BOOL
//
sm_fCertChainCacheOnlyUrlRetrieval = !!dwValue;
}
RegCloseKey( hKeyParam );
}
Finished:
if ( FAILED( hr ) )
{
Terminate();
}
return hr;
}
//static
VOID
SSL_STREAM_CONTEXT::Terminate(
VOID
)
/*++
Routine Description:
Terminate SSL
Arguments:
None
Return Value:
None
--*/
{
// This call must happen after the main threadpool has shutdown
// However there still may be threads executing because of the change notification
// callbacks
//
// ENDPOINT_CONFIG gets notified on http config changes but callbacks touch only
// the ENDPOINT_CONFIG itself
//
// CERT_STORE gets notified on CAPI store changes. Callback function will
// cause access to CERT_STORE, SERVER_CERT, IIS_CTL, ENDPOINT_CONFIG
// That's why all the change notification handling for CERT_STORE
// must be completed before all of the classes mentioned above are Terminated
// Cleanup of ENDPOINT_CONFIG, IIS_CTL, SERVER_CERT and CERT_STORE will
// assure that when Terminate is called, there are no outstanding threads partying
// on any data owned by those classes and so it is indeed safe to terminate
// Cleanup phase
// Note: all INIT_* tags must be present in both Cleanup and Termination part
switch ( s_InitState )
{
case INIT_ACACHE:
case INIT_ENDPOINT_CONFIG:
ENDPOINT_CONFIG::Cleanup();
case INIT_SITE_CREDENTIALS:
case INIT_IIS_CTL:
IIS_CTL::Cleanup();
case INIT_SERVER_CERT:
SERVER_CERT::Cleanup();
case INIT_CERT_STORE:
CERT_STORE::Cleanup();
case INIT_NONE:
break;
default:
DBG_ASSERT( FALSE );
}
// Termination phase
switch ( s_InitState )
{
case INIT_ACACHE:
if ( sm_pachSslStreamContexts != NULL )
{
delete sm_pachSslStreamContexts;
sm_pachSslStreamContexts = NULL;
}
case INIT_ENDPOINT_CONFIG:
ENDPOINT_CONFIG::Terminate();
case INIT_SITE_CREDENTIALS:
SITE_CREDENTIALS::Terminate();
case INIT_IIS_CTL:
IIS_CTL::Terminate();
case INIT_SERVER_CERT:
SERVER_CERT::Terminate();
case INIT_CERT_STORE:
CERT_STORE::Terminate();
case INIT_NONE:
break;
default:
DBG_ASSERT( FALSE );
}
}
SSL_STREAM_CONTEXT::SSL_STREAM_CONTEXT(
FILTER_CHANNEL_CONTEXT * pFiltChannelContext
)
: STREAM_CONTEXT( pFiltChannelContext ),
_pEndpointConfig( NULL ),
_sslState( SSL_STATE_HANDSHAKE_START ),
_fRenegotiate( FALSE ),
_fExpectRenegotiationFromClient( FALSE ),
_fValidContext( FALSE ),
_cbToBeProcessedOffset( 0 ),
_pClientCert( NULL ),
_fDoCertMap( FALSE ),
_cbDecrypted( 0 )
{
//
// Initialize security buffer structs
//
//
// Setup buffer to hold incoming raw data
//
DBG_ASSERT( s_InitState != INIT_NONE );
ZeroMemory( &_hContext, sizeof( _hContext ) );
ZeroMemory( &_ulSslInfo, sizeof( _ulSslInfo ) );
ZeroMemory( &_ulCertInfo, sizeof( _ulCertInfo ) );
_ulCertInfo.Token = NULL;
}
SSL_STREAM_CONTEXT::~SSL_STREAM_CONTEXT()
{
if ( _fValidContext )
{
DeleteSecurityContext( &_hContext );
_fValidContext = FALSE;
}
if ( _pEndpointConfig != NULL )
{
_pEndpointConfig->DereferenceEndpointConfig();
_pEndpointConfig = NULL;
}
if( _ulCertInfo.Token != NULL )
{
CloseHandle( _ulCertInfo.Token );
_ulCertInfo.Token = NULL;
}
if( _pClientCert != NULL )
{
CertFreeCertificateContext( _pClientCert );
_pClientCert = NULL;
}
}
HRESULT
SSL_STREAM_CONTEXT::ProcessNewConnection(
CONNECTION_INFO * pConnectionInfo,
ENDPOINT_CONFIG * pEndpointConfig
)
/*++
Routine Description:
Handle a new raw connection
Arguments:
pConnectionInfo - The magic connection information (not used)
pEndpointConfig - endpoint configuration
Return Value:
HRESULT
--*/
{
BOOL fLocalEndpointConfigReference = FALSE;
DBG_ASSERT( _sslState == SSL_STATE_HANDSHAKE_START );
if ( pEndpointConfig == NULL )
{
HRESULT hr = ENDPOINT_CONFIG::GetEndpointConfig( pConnectionInfo,
&pEndpointConfig );
if ( SUCCEEDED( hr ) )
{
fLocalEndpointConfigReference = TRUE;
}
else if ( hr == HRESULT_FROM_WIN32 ( ERROR_FILE_NOT_FOUND ) )
{
//
// not found means that SSL is not enabled
//
QueryFiltChannelContext()->SetIsSecure( FALSE );
return S_OK;
}
else if ( FAILED( hr ) )
{
return hr;
}
}
DBG_ASSERT( pEndpointConfig != NULL );
if ( !pEndpointConfig->QuerySslConfigured() )
{
// no endpoint config was found
// or endpoint config was found built doesn't contain SSL stuff
//
QueryFiltChannelContext()->SetIsSecure( FALSE );
}
else
{
QueryFiltChannelContext()->SetIsSecure( TRUE );
//
// Store away the site config for this connection
//
pEndpointConfig->ReferenceEndpointConfig();
_pEndpointConfig = pEndpointConfig;
}
if ( fLocalEndpointConfigReference )
{
// Local Endpoint Config lookup was made
// within this function.
// release the reference
//
pEndpointConfig->DereferenceEndpointConfig();
pEndpointConfig = NULL;
}
return S_OK;
}
HRESULT
SSL_STREAM_CONTEXT::ProcessRawReadData(
RAW_STREAM_INFO * pRawStreamInfo,
BOOL * pfReadMore,
BOOL * pfComplete
)
/*++
Routine Description:
Handle an SSL read completion off the wire
Arguments:
pRawStreamInfo - Points to input stream and size
pfReadMore - Set to TRUE if we should read more
pfComplete - Set to TRUE if we should disconnect
Return Value:
HRESULT
--*/
{
HRESULT hr = S_OK;
BOOL fExtraData = FALSE;
//
// Do we have site config? If not, then this isn't an SSL connection
//
if ( _pEndpointConfig == NULL )
{
return S_OK;
}
//
// Loop for extra data
// Sometimes one RawStreamInfo buffer may contain multiple blobs
// some to be processed by DoHandshake() and some by DoDecrypt()
// The do-while loop enables switching between these 2 functions as needed
//
do
{
fExtraData = FALSE;
*pfReadMore = FALSE;
*pfComplete = FALSE;
//
// Either continue handshake or immediate decrypt data
//
switch ( _sslState )
{
case SSL_STATE_HANDSHAKE_START:
case SSL_STATE_HANDSHAKE_IN_PROGRESS:
hr = DoHandshake( pRawStreamInfo,
pfReadMore,
pfComplete,
&fExtraData );
break;
case SSL_STATE_HANDSHAKE_COMPLETE:
hr = DoDecrypt( pRawStreamInfo,
pfReadMore,
pfComplete,
&fExtraData );
break;
default:
DBG_ASSERT( FALSE );
}
if ( FAILED( hr ) )
{
break;
}
//
// Is there still some extra data to be processed?
//
}while( fExtraData );
return hr;
}
HRESULT
SSL_STREAM_CONTEXT::ProcessRawWriteData(
RAW_STREAM_INFO * pRawStreamInfo,
BOOL * pfComplete
)
/*++
Routine Description:
Called on read completion from app. Data received
from application must be encrypted and sent to client
using RawWrite.
Application may have also requested renegotiation
(with or without mapping) if client certificate
is not yet present
Arguments:
pRawStreamInfo - Points to input stream and size
pfComplete - Set to TRUE if we should disconnect
Return Value:
HRESULT
--*/
{
HRESULT hr;
HTTP_FILTER_BUFFER_TYPE bufferType;
if ( _pEndpointConfig == NULL )
{
//
// We never found SSL to be relevent for this connection. Do nothing
//
return S_OK;
}
if ( _sslState != SSL_STATE_HANDSHAKE_COMPLETE )
{
//
// HttpFilter is not able to reliably handle state
// where renegotiation is in progress and response data is
// requested to be sent to client
// Ideally HttpFilter should not have any pending AppReads
// for the duration of the renegotiation handshake
// however in that case http.sys would not be able to inform
// us about client closing connection.
// So there are 2 ways around this problem
//
// a) the easy and cheesy is to detect that SSL handshake is
// not complete and close connection
//
// b) we could buffer data to be sent and send it after handshake is done
//
// Option b) is ultimately more decent but due to the fact that
// there is not much practical need for sending response data while renegotiating
// we will implement the option a)
//
// One example of the scenario where data to be sent to client is received while in
// the middle of the renegotiation is the following
// a) Client sends Post with part of the response body
// b) Http.sys gets headers and part of the response body and
// passes it to IIS
// c) Http.sys will keep receiving response
// d) IIS will ask for renegotiation (because URL requires client cert)
// e) while in the middle of renegotiation, http.sys finds out that
// client closed it's end of the socket before all the bytes of the
// response were sent.
// f) Http.sys sends "400 Bad Request"
// g) HttpFilter will eventually not be able to send the response because
// we are in the middle of renegotiation. However there is not much
// loss of the information if HttpFilter simply decides to close connection
// because specific error will be stored in the http error log
IF_DEBUG ( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"Error: HttpFilter doesn't allow responses while in the middle of ssl handshake (connection will be closed)\n"
));
}
return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
}
bufferType = QueryFiltChannelContext()->QueryFilterBufferType();
//
// Is this data from the application, or a request for renegotiation?
//
if ( bufferType == HttpFilterBufferSslRenegotiate ||
bufferType == HttpFilterBufferSslRenegotiateAndMap )
{
//
// If we have already renegotiated a client certificate, then there
// is nothing to do, but read again for stream data
//
if ( _fRenegotiate )
{
hr = S_OK;
}
else
{
if ( bufferType == HttpFilterBufferSslRenegotiateAndMap )
{
_fDoCertMap = TRUE;
}
hr = DoRenegotiate();
}
}
else if ( bufferType == HttpFilterBufferHttpStream )
{
hr = DoEncrypt( pRawStreamInfo,
pfComplete );
}
else
{
DBG_ASSERT( FALSE );
hr = E_FAIL;
}
return hr;
}
HRESULT
SSL_STREAM_CONTEXT::SendDataBack(
RAW_STREAM_INFO * pRawStreamInfo
)
/*++
Routine Description:
Send back data (different then ProcessRawWrite because in this case
the data was not received by the application, but rather a raw filter)
Arguments:
pRawStreamInfo - Points to input stream and size
Return Value:
HRESULT
--*/
{
BOOL fComplete;
HRESULT hr;
if ( pRawStreamInfo == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
if ( _pEndpointConfig != NULL )
{
//
// We must have completed the handshake to get here, since this path
// is only invoked by ISAPI filters
//
DBG_ASSERT( _sslState == SSL_STATE_HANDSHAKE_COMPLETE );
hr = DoEncrypt( pRawStreamInfo,
&fComplete );
if ( FAILED( hr ) )
{
return hr;
}
}
//
// caller is responsible to send data
//
return S_OK;
}
//static
HRESULT
SSL_STREAM_CONTEXT::OnHandshakeRawWriteCompletion(
PVOID pParam
)
/*++
Routine Description:
perform cleanup after RawWrite Completion
Arguments:
pParam - parameter passed by the caller of DoRawWrite along with completion function
Return Value:
HRESULT
--*/
{
if ( pParam != NULL )
{
FreeContextBuffer( pParam );
pParam = NULL;
}
return S_OK;
}
HRESULT
SSL_STREAM_CONTEXT::DoHandshake(
RAW_STREAM_INFO * pRawStreamInfo,
BOOL * pfReadMore,
BOOL * pfComplete,
BOOL * pfExtraData
)
/*++
Routine Description:
Do the handshake thing with AcceptSecurityContext()
Arguments:
pRawStreamInfo - Raw data buffer
pfReadMore - Set to true if more data should be read
pfComplete - Set to true if we should disconnect
Return Value:
HRESULT
--*/
{
SECURITY_STATUS secStatus = SEC_E_OK;
HRESULT hr = E_FAIL;
DWORD dwFlags = SSL_ASC_FLAGS;
DWORD dwContextAttributes;
TimeStamp tsExpiry;
CtxtHandle hOutContext;
// Buffers used for SSL Handshake (incoming handshake blob)
// 4 is the schannel magic number
SecBufferDesc MessageIn;
SecBuffer InBuffers[ 4 ];
// Buffers used for SSL Handshake (outgoing handshake blob)
// 4 is the schannel magic number
SecBufferDesc MessageOut;
SecBuffer OutBuffers[ 4 ];
if ( pRawStreamInfo == NULL ||
pfReadMore == NULL ||
pfComplete == NULL ||
pfExtraData == NULL )
{
DBG_ASSERT( FALSE );
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto ExitPoint;
}
*pfReadMore = FALSE;
*pfComplete = FALSE;
*pfExtraData = FALSE;
IF_DEBUG( SCHANNEL_CALLS )
{
DBGPRINTF(( DBG_CONTEXT,
"DoHandshake(): _cbDecrypted = %d, _cbToBeProcessedOffset=%d\n",
_cbDecrypted,
_cbToBeProcessedOffset
));
}
DBG_ASSERT( _pEndpointConfig != NULL );
if( pRawStreamInfo->cbData < _cbToBeProcessedOffset )
{
//
// Inconsisent state of the SSL_STREAM_CONTEXT
// This should never really happen, but because we
// execute in lsass we don't want to see wrong offset calculations
// cause much trouble in lsass
//
IF_DEBUG ( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"Error: HttpFilter detected unexpected offset in it's internal data (connection will be closed)\n"
));
}
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
}
//
// Setup input & output buffers for AcceptSecurityContext call
//
MessageIn.ulVersion = SECBUFFER_VERSION;
MessageIn.cBuffers = 4;
MessageIn.pBuffers = InBuffers;
InBuffers[0].BufferType = SECBUFFER_TOKEN;
InBuffers[0].pvBuffer = pRawStreamInfo->pbBuffer + _cbToBeProcessedOffset;
InBuffers[0].cbBuffer = pRawStreamInfo->cbData - _cbToBeProcessedOffset;
InBuffers[1].BufferType = SECBUFFER_EMPTY;
InBuffers[2].BufferType = SECBUFFER_EMPTY;
InBuffers[3].BufferType = SECBUFFER_EMPTY;
MessageOut.ulVersion = SECBUFFER_VERSION;
MessageOut.cBuffers = 4;
MessageOut.pBuffers = OutBuffers;
//
// Note: OutBuffers[ 0 ].pvBuffer may get changed in AcceptSecurityContext
// even if error is returned. In that case _OutBuffers[ 0 ].pvBuffer must
// not be freed
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].cbBuffer = NULL;
OutBuffers[0].BufferType = SECBUFFER_EMPTY;
OutBuffers[1].BufferType = SECBUFFER_EMPTY;
OutBuffers[2].BufferType = SECBUFFER_EMPTY;
OutBuffers[3].BufferType = SECBUFFER_EMPTY;
//
// Are we renegotiating for client cert?
// if _pEndpointConfig->QueryNegotiateClientCert() is TRUE
// it means that Client certificates are enabled on root level
// of the site. In that case we enable optimization where
// client certificates are negotiated right away to eliminate
// expensive renegotiation
DBG_ASSERT( _pEndpointConfig != NULL );
if ( _fRenegotiate || _pEndpointConfig->QueryNegotiateClientCert() )
{
dwFlags |= ASC_REQ_MUTUAL_AUTH;
}
if ( _sslState == SSL_STATE_HANDSHAKE_START )
{
ConditionalAddWorkerThread();
secStatus = AcceptSecurityContext( QueryCredentials(),
NULL,
&MessageIn,
dwFlags,
SECURITY_NATIVE_DREP,
&_hContext,
&MessageOut,
&dwContextAttributes,
&tsExpiry );
ConditionalRemoveWorkerThread();
IF_DEBUG( SCHANNEL_CALLS )
{
DBGPRINTF(( DBG_CONTEXT,
"AcceptSecurityContext() secStatus=0x%x\n",
secStatus
));
}
if ( SUCCEEDED( secStatus ) )
{
_cbHeader = 0;
_cbTrailer = 0;
_cbBlockSize = 0;
_cbMaximumMessage = 0;
_fValidContext = TRUE;
_sslState = SSL_STATE_HANDSHAKE_IN_PROGRESS;
}
}
else
{
DBG_ASSERT( _sslState == SSL_STATE_HANDSHAKE_IN_PROGRESS );
//
// We already have a valid context. We can directly call
// AcceptSecurityContext()!
//
hOutContext = _hContext;
DBG_ASSERT( _fValidContext );
ConditionalAddWorkerThread();
secStatus = AcceptSecurityContext( QueryCredentials(),
&_hContext,
&MessageIn,
dwFlags,
SECURITY_NATIVE_DREP,
&hOutContext,
&MessageOut,
&dwContextAttributes,
&tsExpiry );
ConditionalRemoveWorkerThread();
IF_DEBUG( SCHANNEL_CALLS )
{
DBGPRINTF(( DBG_CONTEXT,
"AcceptSecurityContext() secStatus=0x%x\n",
secStatus
));
}
if ( SUCCEEDED( secStatus ) )
{
if ( memcmp(&_hContext,
&hOutContext,
sizeof( _hContext ) != 0 ) )
{
//
// we always expect schannel to return same context handle
// if it doesn't then we better bail out
//
IF_DEBUG( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"AcceptSecurityContext() return context different from the one on input\n"
));
}
DBG_ASSERT( FALSE );
hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
goto ExitPoint;
}
}
}
//
// Either way, the secStatus tells us how to proceed
//
if ( SUCCEEDED( secStatus ) )
{
//
// AcceptSecurityContext succeeded
//
//
// We haven't failed yet. But we may not be complete. First
// send back any data to the client
//
if ( OutBuffers[ 0 ].pvBuffer != NULL &&
OutBuffers[ 0 ].cbBuffer != 0 )
{
//
// the following asynchronous Write will not do anything
// upon completion other then to simply delete OVERLAPPED_CONTEXT
// There will be no outstanding RawRead (that would cause trouble)
// because we are in RawRead completion handling phase
// That's why it is safe to continue with execution
// (typically after async there is return and all the cleanup
// or whatever needs to be done happens in completion)
// This async call was originally synchronous and by making
// it ASYNC we eliminate long blocking problem with
// slow client while leaving the rest of original synchronous
// execution intact.
//
//
// FreeContextBuffer will have to be called in the callback function
// upon completion
//
hr = QueryFiltChannelContext()->DoRawWrite(
UL_CONTEXT_FLAG_ASYNC |
UL_CONTEXT_FLAG_COMPLETION_CALLBACK,
OutBuffers[ 0 ].pvBuffer,
OutBuffers[ 0 ].cbBuffer,
NULL, // pcbWritten
OnHandshakeRawWriteCompletion,
OutBuffers[ 0 ].pvBuffer );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
else
{
//
// DoRawWrite completion will take care of cleanup
//
OutBuffers[ 0 ].pvBuffer = NULL;
OutBuffers[ 0 ].cbBuffer = 0;
}
}
if ( secStatus == SEC_E_OK )
{
//
// We must be done with handshake
//
hr = DoHandshakeCompleted();
if ( FAILED( hr ) )
{
goto ExitPoint;
}
}
//
// If the input buffer has more info to be SChannel'ized, then do
// so now. If we haven't completed handshake, then call DoHandShake
// again. Otherwise, call DoDecrypt
//
if ( InBuffers[ 1 ].BufferType == SECBUFFER_EXTRA )
{
IF_DEBUG( SCHANNEL_CALLS )
{
for ( int i = 1; i < 4; i++ )
{
if( InBuffers[ i ].BufferType != 0 )
{
DBGPRINTF(( DBG_CONTEXT,
"AcceptSecurityContext returned extra buffer"
" - %d bytes buffer type: %d\n",
InBuffers[ i ].cbBuffer,
InBuffers[ i ].BufferType
));
}
}
}
//
// We better have valid extra data
// only cbBuffer is used, pvBuffer is not used with SECBUFFER_EXTRA
//
DBG_ASSERT( InBuffers[ 1 ].cbBuffer != 0 );
//
// Move extra data right after decrypted data (if any)
//
memmove( pRawStreamInfo->pbBuffer + _cbDecrypted,
pRawStreamInfo->pbBuffer + pRawStreamInfo->cbData
- InBuffers[ 1 ].cbBuffer,
InBuffers[ 1 ].cbBuffer
);
//
// Now we have to adjust pRawStreamInfo->cbData and _cbToBeProcessedOffset
//
pRawStreamInfo->cbData = ( _cbDecrypted +
InBuffers[ 1 ].cbBuffer );
_cbToBeProcessedOffset = _cbDecrypted;
*pfExtraData = TRUE;
//
// caller has to detect that some data is
// still in the buffer not processed and
//
hr = S_OK;
goto ExitPoint;
}
else // no extra buffer
{
//
// There is no extra data to be processed
// If we got here as the result of renegotiation
// there may be some decrypted data in StreamInfo buffer already
//
// (without renegotiation _cbDecryted must always be 0
// because SEC_I_RENEGOTIATE is the only way to get
// from DoDecrypt() to DoHandshake() )
//
DBG_ASSERT ( _fRenegotiate || _cbDecrypted == 0 );
pRawStreamInfo->cbData = _cbDecrypted;
_cbToBeProcessedOffset = _cbDecrypted;
if ( _sslState != SSL_STATE_HANDSHAKE_COMPLETE )
{
//
// If we have no more data, and we still haven't completed the
// handshake, then read some more data
//
*pfReadMore = TRUE;
hr = S_OK;
goto ExitPoint;
}
}
//
// final return from DoHandshake on handshake completion
// Cleanup _cbDecrypted and _cbToBeProcessedOffset to make
// sure that next ProcessRawReadData() will work fine
//
_cbToBeProcessedOffset = 0;
_cbDecrypted = 0;
hr = S_OK;
goto ExitPoint;
}
else
{
//
// Note: _OutBuffers[ 0 ].pvBuffer may be changed in AcceptSecurityContext
// even if error is returned.
// Per JBanes, FreeContextBuffer() should be called in the case of error
// only if ASC_RET_EXTENDED_ERROR flag is set in ContextAttributes.
// Otherwise value should be ignored. We will NULL it out to prevent problems
//
if ( !( dwContextAttributes & ASC_RET_EXTENDED_ERROR ) )
{
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].cbBuffer = 0;
}
//
// AcceptSecurityContext() failed!
//
if ( secStatus == SEC_E_INCOMPLETE_MESSAGE )
{
*pfReadMore = TRUE;
hr = S_OK;
goto ExitPoint;
}
else
{
IF_DEBUG( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"AcceptSecurityContext() failed with secStatus=0x%x\n",
secStatus
));
}
//
// Maybe we can send a more useful message to the client
//
if ( dwContextAttributes & ASC_RET_EXTENDED_ERROR )
{
if ( OutBuffers[ 0 ].pvBuffer!= NULL &&
OutBuffers[ 0 ].cbBuffer != 0 )
{
//
// FreeContextBuffer will have to be called in the callback function
// upon completion
//
hr = QueryFiltChannelContext()->DoRawWrite(
UL_CONTEXT_FLAG_ASYNC |
UL_CONTEXT_FLAG_COMPLETION_CALLBACK,
OutBuffers[ 0 ].pvBuffer,
OutBuffers[ 0 ].cbBuffer,
NULL, // pcbWritten
OnHandshakeRawWriteCompletion,
OutBuffers[ 0 ].pvBuffer );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
else
{
//
// DoRawWrite completion will take care of cleanup
//
OutBuffers[ 0 ].pvBuffer = NULL;
OutBuffers[ 0 ].cbBuffer = 0;
}
}
}
}
hr = secStatus;
}
ExitPoint:
if ( OutBuffers[ 0 ].pvBuffer != NULL )
{
FreeContextBuffer( OutBuffers[ 0 ].pvBuffer );
OutBuffers[ 0 ].pvBuffer = NULL;
OutBuffers[ 0 ].cbBuffer = 0;
}
return hr;
}
HRESULT
SSL_STREAM_CONTEXT::DoHandshakeCompleted()
{
HRESULT hr = S_OK;
SECURITY_STATUS secStatus = SEC_E_OK;
SecPkgContext_StreamSizes StreamSizes;
HTTP_FILTER_BUFFER ulFilterBuffer;
_sslState = SSL_STATE_HANDSHAKE_COMPLETE;
//
// Get some buffer size info for this connection. We only need
// to do this on completion of the initial handshake, and NOT
// subsequent renegotiation handshake (if any)
//
if ( !_cbHeader && !_cbTrailer )
{
secStatus = QueryContextAttributes( &_hContext,
SECPKG_ATTR_STREAM_SIZES,
&StreamSizes );
if ( FAILED( secStatus ) )
{
return secStatus;
}
_cbHeader = StreamSizes.cbHeader;
_cbTrailer = StreamSizes.cbTrailer;
_cbBlockSize = StreamSizes.cbBlockSize;
_cbMaximumMessage = StreamSizes.cbMaximumMessage;
}
//
// Build up a message for the application indicating stuff
// about the negotiated connection
//
// If this is a renegotiate request, then we have already sent
// and calculated the SSL_INFO, so just send the CERT_INFO
//
if ( !_fRenegotiate )
{
//
// send SSL info only if not in renegotiation loop
// because otherwise http.sys has the info already
//
hr = BuildSslInfo();
if ( FAILED( hr ) )
{
return hr;
}
ulFilterBuffer.BufferType = HttpFilterBufferSslInitInfo;
ulFilterBuffer.pBuffer = (PBYTE) &_ulSslInfo;
ulFilterBuffer.BufferSize = sizeof( _ulSslInfo );
hr = QueryFiltChannelContext()->DoAppWrite( UL_CONTEXT_FLAG_SYNC,
&ulFilterBuffer,
NULL );
if ( FAILED( hr ) )
{
return hr;
}
}
//
// Pass client certificate information to http.sys
// if client certificates are required on the site level then
// inform about client certificate even in the case when
// client cert is not present not to cause double negotiation
//
if ( _pClientCert != NULL ||
_fRenegotiate ||
_pEndpointConfig->QueryNegotiateClientCert() )
{
//
// Renegotiation time is monitored by timer
// because http.sys doesn't monitor this particular
// code path because of the way filter works
//
QueryFiltChannelContext()->StopTimeoutTimer();
if ( SUCCEEDED( RetrieveClientCertAndToken() ) )
{
hr = BuildClientCertInfo();
if ( FAILED( hr ) )
{
return hr;
}
}
//
// CODEWORK:
// It would be better to eliminate HttpFilterBufferSslClientCertAndMap
// and let HTTP.SYS to check on token value. If it is NULL then mapping
// did not happen
//
if ( _ulCertInfo.Token != NULL )
{
ulFilterBuffer.BufferType = HttpFilterBufferSslClientCertAndMap;
}
else
{
ulFilterBuffer.BufferType = HttpFilterBufferSslClientCert;
}
//
// If client certificate was not negotiated, then structure will be empty.
// HTTP.SYS is able to handle it.
//
ulFilterBuffer.pBuffer = (PBYTE) &_ulCertInfo;
ulFilterBuffer.BufferSize = sizeof( _ulCertInfo );
//
// Write the client certificate information to the application
// (it will be cached by HTTP.sys for the lifetime of the connection)
//
hr = QueryFiltChannelContext()->DoAppWrite( UL_CONTEXT_FLAG_SYNC,
&ulFilterBuffer,
NULL );
if ( FAILED( hr ) )
{
return hr;
}
}
return hr;
}
HRESULT
SSL_STREAM_CONTEXT::DoRenegotiate(
VOID
)
/*++
Routine Description:
Trigger a renegotiate for a client certificate
Arguments:
None
Return Value:
HRESULT
--*/
{
SECURITY_STATUS secStatus = SEC_E_OK;
CtxtHandle hOutContext;
DWORD dwContextAttributes = 0;
TimeStamp tsExpiry;
DWORD dwFlags = SSL_ASC_FLAGS |
ASC_REQ_MUTUAL_AUTH;
HRESULT hr = S_OK;
// Buffers used for SSL Handshake (incoming handshake blob)
// 4 is the schannel magic number
SecBufferDesc MessageIn;
SecBuffer InBuffers[ 4 ];
// Buffers used for SSL Handshake (outgoing handshake blob)
// 4 is the schannel magic number
SecBufferDesc MessageOut;
SecBuffer OutBuffers[ 4 ];
DBG_ASSERT( _pEndpointConfig != NULL );
//
// Remember that we're renegotiating since we now have to pass the
// MUTUAL_AUTH flag into AcceptSecurityContext() from here on out. Also
// we can only request renegotiation once per connection
//
DBG_ASSERT( _fRenegotiate == FALSE );
_fRenegotiate = TRUE;
QueryFiltChannelContext()->StartTimeoutTimer();
//
// Try to get the client certificate. If we don't, that's OK. We will
// renegotiate
//
hr = RetrieveClientCertAndToken();
if ( SUCCEEDED ( hr ) )
{
//
// we have client certificate available for this session
// there is no need to continue with renegotiation
//
hr = DoHandshakeCompleted();
return hr;
}
//
// Reset the HRESULT
// Previous error failing to retrieve client certificate is OK,
// it just means that renegotiation is necessary since
// no client certificate is currently available
hr = S_OK;
//
// Restart the handshake
//
//
// Setup input & output buffers for AcceptSecurityContext call
//
MessageIn.ulVersion = SECBUFFER_VERSION;
MessageIn.cBuffers = 4;
MessageIn.pBuffers = InBuffers;
InBuffers[0].BufferType = SECBUFFER_TOKEN;
InBuffers[0].pvBuffer = "";
InBuffers[0].cbBuffer = 0;
InBuffers[1].BufferType = SECBUFFER_EMPTY;
InBuffers[2].BufferType = SECBUFFER_EMPTY;
InBuffers[3].BufferType = SECBUFFER_EMPTY;
MessageOut.ulVersion = SECBUFFER_VERSION;
MessageOut.cBuffers = 4;
MessageOut.pBuffers = OutBuffers;
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].cbBuffer = NULL;
OutBuffers[0].BufferType = SECBUFFER_EMPTY;
OutBuffers[1].BufferType = SECBUFFER_EMPTY;
OutBuffers[2].BufferType = SECBUFFER_EMPTY;
OutBuffers[3].BufferType = SECBUFFER_EMPTY;
hOutContext = _hContext;
ConditionalAddWorkerThread();
secStatus = AcceptSecurityContext( QueryCredentials(),
&_hContext,
&MessageIn,
dwFlags,
SECURITY_NATIVE_DREP,
&hOutContext,
&MessageOut,
&dwContextAttributes,
&tsExpiry );
ConditionalRemoveWorkerThread();
IF_DEBUG( SCHANNEL_CALLS )
{
DBGPRINTF(( DBG_CONTEXT,
"AcceptSecurityContext() secStatus=0x%x\n",
secStatus
));
}
if ( secStatus == SEC_E_UNSUPPORTED_FUNCTION )
{
//
// Renegotiation is not suppported for current protocol
// (SSL2 would be example of such protocol)
// Change state to HandhakeCompleted
//
hr = DoHandshakeCompleted();
}
else if ( SUCCEEDED( secStatus ) )
{
if ( memcmp( &_hContext,
&hOutContext,
sizeof( _hContext ) != 0 ) )
{
//
// we always expect schannel to return same context handle
// if it doesn't then we better bail out
//
IF_DEBUG( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"AcceptSecurityContext() return context different from the one on input\n"
));
}
DBG_ASSERT( FALSE );
hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
goto ExitPoint;
}
if ( OutBuffers[0].pvBuffer != NULL &&
OutBuffers[0].cbBuffer != 0 )
{
//
// _fExpectRenegotiationFromClient flag will be used to eliminate
// client triggered renegotiation by enabling client data to cause
// renegotiation only if fExpectRenegotiationFromClient is already set
// That will prove that it is server initiated renegotiation
// If we don't prevent client triggered renegotiation there may
// be some wierd race conditions because client triggered renegotiation
// could interfere with the outgoing data stream (due to full duplex
// for incoming and outgoing data)
// Do interlocked because we check the value on the incoming data handling path
// in the DecryptMessage()
//
// Note: It is possible that after _fExpectRenegotiationFromClient is set
// client may try to send renegotiation request (client initiated renegotiation)
// Our flag is set already so that before we execute DoRawWrite
// there could be a thread executing DoHandshake
// Care must be used when modifying anything past the InterlockedExchange
// to eliminate any potential race with DoHandhshake function
//
InterlockedExchange( (LONG *)&_fExpectRenegotiationFromClient, (LONG) TRUE );
hr = QueryFiltChannelContext()->DoRawWrite(
UL_CONTEXT_FLAG_ASYNC |
UL_CONTEXT_FLAG_COMPLETION_CALLBACK,
OutBuffers[0].pvBuffer,
OutBuffers[0].cbBuffer,
NULL, // pcbWritten
OnHandshakeRawWriteCompletion,
OutBuffers[0].pvBuffer );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
else
{
// callback after DoRawWrite is responsible to cleanup pvOutBuffer
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].cbBuffer = 0;
}
}
hr = secStatus;
}
else
{
IF_DEBUG( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"AcceptSecurityContext() for renegotiation failed with secStatus=0x%x\n",
secStatus
));
}
if ( dwContextAttributes & ASC_RET_EXTENDED_ERROR )
{
if ( OutBuffers[0].pvBuffer != NULL &&
OutBuffers[0].cbBuffer != 0 )
{
hr = QueryFiltChannelContext()->DoRawWrite(
UL_CONTEXT_FLAG_ASYNC |
UL_CONTEXT_FLAG_COMPLETION_CALLBACK,
OutBuffers[0].pvBuffer,
OutBuffers[0].cbBuffer,
NULL, // pcbWritten
OnHandshakeRawWriteCompletion,
OutBuffers[0].pvBuffer );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
else
{
// callback after DoRawWrite is responsible to cleanup pvOutBuffer
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].cbBuffer = 0;
}
}
}
else
{
//
// Note: _OutBuffers[ 0 ].pvBuffer may be changed in AcceptSecurityContext
// even if error is returned.
// Per JBanes, FreeContextBuffer() should be called in the case of error
// only if ASC_RET_EXTENDED_ERROR flag is set in ContextAttributes.
// Otherwise value should be ignored. We will NULL it out to prevent problems
//
OutBuffers[0].pvBuffer = NULL;
OutBuffers[0].cbBuffer = 0;
}
hr = secStatus;
}
ExitPoint:
if ( OutBuffers[0].pvBuffer != NULL )
{
FreeContextBuffer( OutBuffers[0].pvBuffer );
OutBuffers[0].pvBuffer = NULL;
}
return hr;
}
HRESULT
SSL_STREAM_CONTEXT::DoDecrypt(
RAW_STREAM_INFO * pRawStreamInfo,
BOOL * pfReadMore,
BOOL * pfComplete,
BOOL * pfExtraData
)
/*++
Routine Description:
Decrypt some data
Arguments:
pRawStreamInfo - Raw data buffer
pfReadMore - Set to true if we should read more data
pfComplete - Set to true if we should disconnect
Return Value:
HRESULT
--*/
{
SECURITY_STATUS secStatus = SEC_E_OK;
INT iExtra;
BOOL fDecryptAgain = TRUE;
UCHAR FirstByte = 0; //used only for debug output
// Buffers used for SSL Handshake (incoming handshake blob)
// 4 is the schannel magic number
SecBufferDesc Message;
SecBuffer Buffers[ 4 ];
if ( pRawStreamInfo == NULL ||
pfReadMore == NULL ||
pfComplete == NULL ||
pfExtraData == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
*pfReadMore = FALSE;
*pfComplete = FALSE;
*pfExtraData = FALSE;
IF_DEBUG( SCHANNEL_CALLS )
{
DBGPRINTF(( DBG_CONTEXT,
"DoDecrypt(): _cbDecrypted = %d, _cbToBeProcessedOffset=%d\n",
_cbDecrypted,
_cbToBeProcessedOffset
));
}
//
// Setup an DecryptMessage call. The input buffer is the _buffRaw plus
// an offset. The offset is non-zero if we had to do another read to
// get more data for a previously incomplete message
//
if( pRawStreamInfo->cbData < _cbToBeProcessedOffset )
{
//
// Inconsisent state of the SSL_STREAM_CONTEXT
// This should never really happen, but because we
// execute in lsass we don't want to see wrong offset calculations
// cause much trouble in lsass
//
IF_DEBUG ( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"Error: HttpFilter detected unexpected offset in it's internal data (connection will be closed)\n"
));
}
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
}
//
// Prepare message buffers to be passed to the DecryptMessage
//
Message.ulVersion = SECBUFFER_VERSION;
Message.cBuffers = 4;
Message.pBuffers = Buffers;
Buffers[ 0 ].pvBuffer = pRawStreamInfo->pbBuffer + _cbToBeProcessedOffset;
Buffers[ 0 ].cbBuffer = pRawStreamInfo->cbData - _cbToBeProcessedOffset;
Buffers[ 0 ].BufferType = SECBUFFER_DATA;
Buffers[ 1 ].BufferType = SECBUFFER_EMPTY;
Buffers[ 2 ].BufferType = SECBUFFER_EMPTY;
Buffers[ 3 ].BufferType = SECBUFFER_EMPTY;
while ( fDecryptAgain )
{
fDecryptAgain = FALSE;
IF_DEBUG( SCHANNEL_CALLS )
{
//
// remember first byte because Decrypt will alter it
//
FirstByte = (unsigned char) *((char *)Buffers[ 0 ].pvBuffer);
}
secStatus = DecryptMessage( &_hContext,
&Message,
0,
NULL );
IF_DEBUG( SCHANNEL_CALLS )
{
DBGPRINTF(( DBG_CONTEXT,
"DecryptMessage( bytes:%d, first byte:0x%x ) secStatus=0x%x\n",
pRawStreamInfo->cbData - _cbToBeProcessedOffset,
FirstByte,
secStatus
));
}
if ( FAILED( secStatus ) )
{
if ( secStatus == SEC_E_INCOMPLETE_MESSAGE )
{
//
// Setup another read since the message is incomplete. Remember
// where the new data is going to since we only pass this data
// to the next DecryptMessage call
//
_cbToBeProcessedOffset = (DWORD) DIFF( (BYTE *)Buffers[ 0 ].pvBuffer -
pRawStreamInfo->pbBuffer );
QueryFiltChannelContext()->SetNextRawReadSize( Buffers[ 1 ].cbBuffer );
*pfReadMore = TRUE;
return S_OK;
}
IF_DEBUG( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"DecryptMessage() failed with secStatus=0x%x\n",
secStatus
));
}
return secStatus;
}
if ( secStatus == SEC_E_OK )
{
//
// Encrypted data contains header and trailer
// AppWrite expects continuous buffer
// so we have to move currently decrypted message block
// just after the already Decrypted data (if any - otherwise the
// begining of the buffer)
//
memmove( pRawStreamInfo->pbBuffer + _cbDecrypted,
Buffers[ 1 ].pvBuffer,
Buffers[ 1 ].cbBuffer );
_cbDecrypted += Buffers[ 1 ].cbBuffer;
}
//
// Locate extra data (may be available)
//
iExtra = 0;
for ( int i = 1; i < 4; i++ )
{
IF_DEBUG( SCHANNEL_CALLS )
{
if( Buffers[ i ].BufferType != 0 )
{
DBGPRINTF(( DBG_CONTEXT,
"DecryptMessage returned extra buffer"
" - %d bytes buffer type: %d\n",
Buffers[ i ].cbBuffer,
Buffers[ i ].BufferType
));
}
}
if ( Buffers[ i ].BufferType == SECBUFFER_EXTRA )
{
iExtra = i;
break;
}
}
if ( iExtra != 0 )
{
//
// Process extra buffer
//
_cbToBeProcessedOffset = (DWORD) DIFF( (PBYTE) Buffers[ iExtra ].pvBuffer -
pRawStreamInfo->pbBuffer );
if ( secStatus != SEC_I_RENEGOTIATE )
{
//
// There are more message blocks in the blob received from client
//
Buffers[ 0 ].pvBuffer = Buffers[ iExtra ].pvBuffer;
Buffers[ 0 ].cbBuffer = Buffers[ iExtra ].cbBuffer;
Buffers[ 0 ].BufferType = SECBUFFER_DATA;
Buffers[ 1 ].BufferType = SECBUFFER_EMPTY;
Buffers[ 2 ].BufferType = SECBUFFER_EMPTY;
Buffers[ 3 ].BufferType = SECBUFFER_EMPTY;
fDecryptAgain = TRUE;
continue;
}
else // secStatus == SEC_I_RENEGOTIATE
{
//
// we will accept renegotiation
// only if _fExpectRenegotiationFromClient is already set
// That means that renegotiation was started by server
// We reject client initiated renegotiations because
// that may cause problems with our state handling
// and could possibly make us prone to attacks
// Also reset _fExpectRenegotiationFromClient to FALSE so that
// client is not allowed to try more than once
if ( InterlockedExchange( (LONG *)&_fExpectRenegotiationFromClient, (LONG) FALSE ) )
{
//
// If a renegotiation is triggered, resume the handshake state
//
_sslState = SSL_STATE_HANDSHAKE_IN_PROGRESS;
//
// Caller has to detect that some data is
// still in the buffer not processed and
// That will signal to call DoHandshake()
// for that extra data
//
*pfExtraData = TRUE;
return S_OK;
}
else
{
secStatus = SEC_E_UNSUPPORTED_FUNCTION;
if ( ( DEBUG_FLAG_ERROR & GET_DEBUG_FLAGS() )
&& FAILED(secStatus) )
{
DBGPRINTF(( DBG_CONTEXT,
"Client initiated renegotiation is not supported by IIS. secStatus=0x%x\n",
secStatus
));
}
return secStatus;
}
}
}
}
//
// there would have been extra data with SEC_I_RENEGOTIATE
// so we must never get here when renegotiating
//
DBG_ASSERT( secStatus != SEC_I_RENEGOTIATE );
//
// Adjust cbData to include only decrypted data
//
pRawStreamInfo->cbData = _cbDecrypted;
//
// We have final decrypted buffer and no extra data left
// Cleanup _cbDecrypted and _cbToBeProcessedOffset to make sure that
// next ProcessRawReadData() will work fine.
//
_cbDecrypted = 0;
_cbToBeProcessedOffset = 0;
return S_OK;
}
HRESULT
SSL_STREAM_CONTEXT::DoEncrypt(
RAW_STREAM_INFO * pRawStreamInfo,
BOOL * pfComplete
)
/*++
Routine Description:
Encrypt data from the application
Arguments:
pRawStreamInfo - Raw data buffer
pfComplete - Set to true if we should disconnect
Return Value:
HRESULT
--*/
{
SECURITY_STATUS secStatus = SEC_E_OK;
// number of chunks the data to be encrypted will be split to
DWORD dwChunks = 0;
// current Data chunk size to be encrypted
DWORD cbDataChunk = 0;
// bytes already encrypted from the source
DWORD cbDataProcessed = 0;
// offset to *pbufRawWrite where new chunk should be placed
DWORD cbRawWriteOffset = 0;
// buffer acquired from FILTER_CHANNEL_CONTEXT to store data for RawWrite
// it is necessary to use buffer owned by FILTER_CHANNEL_CONTEXT to
// allow 2 outstanding RawWrites
PBYTE pbRawWrite = NULL;
DWORD cbRawWrite = 0;
HRESULT hr = E_FAIL;
// Buffer for encrypting clear text data from application
// to be sent back to client
// 4 is magic number coming from schannel
SecBufferDesc Message;
SecBuffer EncryptBuffers[ 4 ];
if ( pRawStreamInfo == NULL ||
pfComplete == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
*pfComplete = FALSE;
//
// Each protocol has limit on maximum size of message
// that can be encrypted with one EncryptMessage() call
//
DBG_ASSERT( _cbMaximumMessage != 0 );
//
// Calculate number of chunks based on _cbMaximumMessage
//
dwChunks = pRawStreamInfo->cbData / _cbMaximumMessage;
if ( pRawStreamInfo->cbData % _cbMaximumMessage != 0 )
{
dwChunks++;
}
//
// ask FILTER_CHANNEL_CONTEXT for one buffer for RawWrite
// (FILTER_CHANNEL_CONTEXT maintains 2 buffer to allow 2 outstanding RawWrites
// to handle the TCP's DELAYED_ACK issue - see Windows Bugs 394511)
// buffer doesn't have to be released in the error case because
// error will cause connection to get closed and FILTER_CHANNEL_CONTEXT will
// handle cleanup.
//
//
// Allocate a large enough buffer for encrypted data
// ( remember that each chunk needs header and trailer )
//
cbRawWrite = pRawStreamInfo->cbData +
dwChunks * _cbHeader +
dwChunks * _cbTrailer;
hr = QueryFiltChannelContext()->AcquireRawWriteBuffer(
&pbRawWrite,
cbRawWrite );
if ( FAILED ( hr ) )
{
return hr;
}
DBG_ASSERT( pbRawWrite != NULL );
//
// Loop to encrypt required data in chunks each not exceeding _cbMaximumMessage
//
for ( DWORD dwCurrentChunk = 0; dwCurrentChunk < dwChunks; dwCurrentChunk++ )
{
DBG_ASSERT( cbRawWrite > cbRawWriteOffset );
cbDataChunk = min( pRawStreamInfo->cbData - cbDataProcessed,
_cbMaximumMessage );
memcpy( pbRawWrite + _cbHeader + cbRawWriteOffset,
pRawStreamInfo->pbBuffer + cbDataProcessed,
cbDataChunk );
//
// Setup buffer for app data to be encrypted
//
Message.ulVersion = SECBUFFER_VERSION;
Message.cBuffers = 4;
Message.pBuffers = EncryptBuffers;
EncryptBuffers[ 0 ].pvBuffer = pbRawWrite +
cbRawWriteOffset;
EncryptBuffers[ 0 ].cbBuffer = _cbHeader;
EncryptBuffers[ 0 ].BufferType = SECBUFFER_STREAM_HEADER;
EncryptBuffers[ 1 ].pvBuffer = pbRawWrite +
_cbHeader +
cbRawWriteOffset;
EncryptBuffers[ 1 ].cbBuffer = cbDataChunk;
EncryptBuffers[ 1 ].BufferType = SECBUFFER_DATA;
EncryptBuffers[ 2 ].pvBuffer = pbRawWrite +
_cbHeader +
cbDataChunk +
cbRawWriteOffset;
EncryptBuffers[ 2 ].cbBuffer = _cbTrailer;
EncryptBuffers[ 2 ].BufferType = SECBUFFER_STREAM_TRAILER;
EncryptBuffers[ 3 ].BufferType = SECBUFFER_EMPTY;
secStatus = EncryptMessage( &_hContext,
0,
&Message,
0 );
IF_DEBUG( SCHANNEL_CALLS )
{
DBGPRINTF(( DBG_CONTEXT,
"EncryptMessage() secStatus=0x%x\n",
secStatus
));
}
if(SUCCEEDED(secStatus))
{
//
// next chunk was successfully encrypted
//
cbDataProcessed += cbDataChunk;
cbRawWriteOffset += EncryptBuffers[ 0 ].cbBuffer +
EncryptBuffers[ 1 ].cbBuffer +
EncryptBuffers[ 2 ].cbBuffer;
}
else
{
//
// Set cbData to 0 just for the case that caller ignored error
// and tried to send not encrypted data to client
//
pRawStreamInfo->cbData = 0;
IF_DEBUG( FLAG_ERROR )
{
DBGPRINTF(( DBG_CONTEXT,
"EncryptMessage() failed with secStatus=0x%x\n",
secStatus
));
}
return secStatus;
}
}
//
// Replace the raw stream buffer with the encrypted data
//
pRawStreamInfo->pbBuffer = pbRawWrite;
pRawStreamInfo->cbBuffer = cbRawWrite;
pRawStreamInfo->cbData = cbRawWriteOffset;
return S_OK;
}
HRESULT
SSL_STREAM_CONTEXT::BuildSslInfo(
VOID
)
/*++
Routine Description:
Build UL_SSL_INFO structure based on Schannel context handle
Arguments:
None
Return Value:
HRESULT
--*/
{
SECURITY_STATUS secStatus;
SecPkgContext_ConnectionInfo ConnectionInfo;
SERVER_CERT * pServerCert = NULL;
HRESULT hr = S_OK;
//
// Negotiated key size
//
//
// Negotiated key size
//
if ( _ulSslInfo.ConnectionKeySize == 0 )
{
secStatus = QueryContextAttributes( &_hContext,
SECPKG_ATTR_CONNECTION_INFO,
&ConnectionInfo );
if ( SUCCEEDED( secStatus ) )
{
_ulSslInfo.ConnectionKeySize = (USHORT) ConnectionInfo.dwCipherStrength;
}
}
//
// A bunch of parameters are based off the server certificate. Get that
// cert now
//
DBG_ASSERT( _pEndpointConfig != NULL );
pServerCert = _pEndpointConfig->QueryServerCert();
DBG_ASSERT( pServerCert != NULL );
//
// Server cert strength
//
if ( _ulSslInfo.ServerCertKeySize == 0 )
{
_ulSslInfo.ServerCertKeySize = pServerCert->QueryPublicKeySize();
}
//
// Server Cert Issuer
//
if ( _ulSslInfo.pServerCertIssuer == NULL )
{
DBG_ASSERT( _ulSslInfo.ServerCertIssuerSize == 0 );
_ulSslInfo.pServerCertIssuer = pServerCert->QueryIssuer()->QueryStr();
_ulSslInfo.ServerCertIssuerSize = pServerCert->QueryIssuer()->QueryCCH();
}
//
// Server Cert subject
//
if ( _ulSslInfo.pServerCertSubject == NULL )
{
DBG_ASSERT( _ulSslInfo.ServerCertSubjectSize == 0 );
_ulSslInfo.pServerCertSubject = pServerCert->QuerySubject()->QueryStr(),
_ulSslInfo.ServerCertSubjectSize = pServerCert->QuerySubject()->QueryCCH();
}
return hr;
}
HRESULT
SSL_STREAM_CONTEXT::RetrieveClientCertAndToken(
VOID
)
/*++
Routine Description:
Query client certificate and token from the SSL context
Arguments:
none
Return Value:
HRESULT
--*/
{
SECURITY_STATUS secStatus = SEC_E_OK;
//
// If client certificate has already been retrieved then simply return
// with success
//
if ( _pClientCert != NULL )
{
return SEC_E_OK;
}
secStatus = QueryContextAttributes( &_hContext,
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
&_pClientCert );
if ( SUCCEEDED( secStatus ) )
{
DBG_ASSERT( _pClientCert != NULL );
}
else
{
goto ExitPoint;
}
//
// If we got a client cert and mapping is enabled, then
// request Schannel mapping
//
if( _ulCertInfo.Token != NULL )
{
CloseHandle( _ulCertInfo.Token );
_ulCertInfo.Token = NULL;
}
//
// Only DS mapper is executed in streamfilt
// IIS mapper is executed in w3core as part of IISCertmap Auth Provider
//
DBG_ASSERT( _pEndpointConfig != NULL );
//
// If DSMapper is enabled, we have to query the token and pass it
// to HTTP service regardless if mapping was requested by worker process
// for this specific request
// the reason is that http.sys will cache client certificate and token
// for the connection and next time new request in worker process coming through
// the same connection may require DS certificate mappings and if http.sys doesn't
// have token cached for the connection only client certificate,
// the worker process will not be able to retrieve it any more
//
if ( _pEndpointConfig->QueryUseDSMapper() )
{
secStatus = QuerySecurityContextToken( &_hContext,
&_ulCertInfo.Token );
if ( SUCCEEDED( secStatus ) )
{
DBG_ASSERT( _ulCertInfo.Token != NULL );
}
}
if ( FAILED ( secStatus ) )
{
//
// if token from mapping is not available
// it is OK, no mapping was found or
// denied access mapping was used
//
// BUGBUG - some errors should probably be logged
//
secStatus = SEC_E_OK;
}
ExitPoint:
return secStatus;
}
HRESULT
SSL_STREAM_CONTEXT::BuildClientCertInfo(
VOID
)
/*++
Routine Description:
Get client certificate info
Arguments:
None
Return Value:
HRESULT
--*/
{
HCERTCHAINENGINE hCertEngine = NULL;
HRESULT hr = S_OK;
CERT_CHAIN_PARA chainPara;
BOOL fRet;
PCCERT_CHAIN_CONTEXT pChainContext = NULL;
DWORD dwRevocationFlags = 0;
DWORD dwCacheFlags = 0;
HTTPSPolicyCallbackData HTTPSPolicy;
CERT_CHAIN_POLICY_PARA PolicyPara;
CERT_CHAIN_POLICY_STATUS PolicyStatus;
DBG_ASSERT( _pClientCert != NULL );
//
// Do the easy stuff!
//
_ulCertInfo.CertEncodedSize = _pClientCert->cbCertEncoded;
_ulCertInfo.pCertEncoded = _pClientCert->pbCertEncoded;
_ulCertInfo.CertFlags = 0;
//
// Now for the hard stuff. We need to validate the server does indeed
// accept the client certificate. Accept means we trusted the
// transitive trust chain to the CA, that the cert isn't revoked, etc.
//
// We use CAPI chaining functionality to check the certificate.
//
DBG_ASSERT( _pEndpointConfig != NULL );
//
// Default chain engine
//
hCertEngine = HCCE_LOCAL_MACHINE;
if ( !( _pEndpointConfig->QueryCertCheckMode() &
MD_CERT_NO_REVOC_CHECK ) )
{
dwRevocationFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
}
if ( _pEndpointConfig->QueryCertCheckMode() &
MD_CERT_CACHE_RETRIEVAL_ONLY )
{
dwCacheFlags |= CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
}
if ( sm_fCertChainCacheOnlyUrlRetrieval )
{
dwCacheFlags |= CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL;
}
//
// Let'r rip
//
ZeroMemory( &chainPara, sizeof( chainPara ) );
chainPara.cbSize = sizeof( chainPara );
chainPara.dwUrlRetrievalTimeout =
_pEndpointConfig->QueryRevocationUrlRetrievalTimeout();
chainPara.dwRevocationFreshnessTime =
_pEndpointConfig->QueryRevocationFreshnessTime();
chainPara.fCheckRevocationFreshnessTime =
!!( _pEndpointConfig->QueryCertCheckMode() &
MD_CERT_CHECK_REVOCATION_FRESHNESS_TIME );
LPSTR UsageIdentifiers[1] = { szOID_PKIX_KP_CLIENT_AUTH };
if ( !(_pEndpointConfig->QueryCertCheckMode() &
MD_CERT_NO_USAGE_CHECK ) )
{
chainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
chainPara.RequestedUsage.Usage.cUsageIdentifier = 1;
chainPara.RequestedUsage.Usage.rgpszUsageIdentifier =
UsageIdentifiers;
}
//
// CertGetCertificateChain may eventually block
// such as in the case of the CRL retrieval.
// We got to bump up the soft thread limit
//
QueryFiltChannelContext()->AddWorkerThread();
fRet = CertGetCertificateChain( hCertEngine,
_pClientCert,
NULL,
NULL,
&chainPara,
dwRevocationFlags | dwCacheFlags,
NULL,
&pChainContext );
QueryFiltChannelContext()->RemoveWorkerThread();
if ( !fRet )
{
//
// Bad. Couldn't get the chain at all.
//
hr = HRESULT_FROM_WIN32( GetLastError() );
goto ExitPoint;
}
//
// Validate certificate chain using CertVerifyCertificateChainPolicy.
//
ZeroMemory( &HTTPSPolicy, sizeof(HTTPSPolicy));
HTTPSPolicy.cbStruct = sizeof(HTTPSPolicyCallbackData);
HTTPSPolicy.dwAuthType = AUTHTYPE_CLIENT;
HTTPSPolicy.fdwChecks = 0;
HTTPSPolicy.pwszServerName = NULL;
ZeroMemory( &PolicyPara, sizeof( PolicyPara ) );
PolicyPara.cbSize = sizeof( PolicyPara );
PolicyPara.pvExtraPolicyPara = &HTTPSPolicy;
ZeroMemory( &PolicyStatus, sizeof( PolicyStatus) );
PolicyStatus.cbSize = sizeof( PolicyStatus );
if ( !CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL,
pChainContext,
&PolicyPara,
&PolicyStatus ) )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
DBGPRINTF((DBG_CONTEXT,
"CertVerifyCertificateChainPolicy failed. hr = 0x%x\n",
hr ));
goto ExitPoint;
}
if ( PolicyStatus.dwError == CRYPT_E_NO_REVOCATION_CHECK )
{
{
//
// If a cert in the chain has no CDP we will NOT take it as error
// reset the dwError
// Note: This is based on the advice from Crypto team
//
PolicyStatus.dwError = ERROR_SUCCESS;
}
}
if ( PolicyStatus.dwError != ERROR_SUCCESS )
{
//
// Client certificate
//
_ulCertInfo.CertFlags = PolicyStatus.dwError;
}
else
{
//
// Now verify CTL - IIS uses unsigned CTLs
// and since CAPI doesn't support unsigned CTL verification
// it is necessary to verify manually
//
if ( _pEndpointConfig->IsCtlRequired() )
{
PCERT_SIMPLE_CHAIN pSimpleChain = pChainContext->rgpChain[pChainContext->cChain - 1];
PCERT_CHAIN_ELEMENT pChainElement =
pSimpleChain->rgpElement[pSimpleChain->cElement - 1];
PCCERT_CONTEXT pChainTop = pChainElement->pCertContext;
DBG_ASSERT( pChainTop != NULL );
IIS_CTL * pIisCtl = _pEndpointConfig->QueryIisCtl();
BOOL fCtlContainsCert = FALSE;
if ( pIisCtl != NULL )
{
hr = pIisCtl->VerifyContainsCert( pChainTop, &fCtlContainsCert );
if ( FAILED( hr ) || !fCtlContainsCert )
{
_ulCertInfo.CertFlags = (ULONG) CERT_E_UNTRUSTEDROOT;
}
}
else
{
//
// CTL not available but site requires it.
// Request must fail
//
_ulCertInfo.CertFlags = (ULONG) CERT_E_UNTRUSTEDROOT;
}
}
}
IF_DEBUG( CLIENT_CERT_INFO )
{
//
// Dump out some debug info about the certificate
//
DumpCertDebugInfo( PolicyStatus.dwError );
}
//
// the mapped user token was already assigned (in RetrieveClientCertAndToken)
//
hr = S_OK;
ExitPoint:
if ( pChainContext != NULL )
{
CertFreeCertificateChain( pChainContext );
}
return hr;
}
VOID
SSL_STREAM_CONTEXT::DumpCertDebugInfo(
DWORD dwPolicyStatus
)
/*++
Routine Description:
On checked builds, dumps certificate (and chain) information to
debugger
Arguments:
dwPolicyStatus - policy status (result of CertVerifyCertificateChainPolicy)
Return Value:
None
--*/
{
PCERT_PUBLIC_KEY_INFO pPublicKey;
DWORD cbKeyLength;
WCHAR achBuffer[ 512 ];
//
// Get certificate public key size
//
pPublicKey = &(_pClientCert->pCertInfo->SubjectPublicKeyInfo);
cbKeyLength = CertGetPublicKeyLength( X509_ASN_ENCODING,
pPublicKey );
DBGPRINTF(( DBG_CONTEXT,
"Client cert key length = %d bits\n",
cbKeyLength ));
//
// Get issuer string
//
if ( CertGetNameString( _pClientCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
achBuffer,
sizeof( achBuffer ) / sizeof( WCHAR ) ) )
{
DBGPRINTF(( DBG_CONTEXT,
"Client cert issuer = %ws\n",
achBuffer ));
}
else
{
DBGPRINTF(( DBG_CONTEXT,
"Error determining client cert issuer. Win32 = %d\n",
GetLastError() ));
}
//
// Get subject string
//
if ( CertGetNameString( _pClientCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
achBuffer,
sizeof( achBuffer ) / sizeof( WCHAR ) ) )
{
DBGPRINTF(( DBG_CONTEXT,
"Client cert subject = %ws\n",
achBuffer ));
}
else
{
DBGPRINTF(( DBG_CONTEXT,
"Error determining client cert subject. Win32 = %d\n",
GetLastError() ));
}
//
// Dump Policy Status
//
LPSTR pszName = NULL;
switch( dwPolicyStatus )
{
case CERT_E_EXPIRED:
pszName = "CERT_E_EXPIRED"; break;
case CERT_E_VALIDITYPERIODNESTING:
pszName = "CERT_E_VALIDITYPERIODNESTING"; break;
case CERT_E_ROLE:
pszName = "CERT_E_ROLE"; break;
case CERT_E_PATHLENCONST:
pszName = "CERT_E_PATHLENCONST"; break;
case CERT_E_CRITICAL:
pszName = "CERT_E_CRITICAL"; break;
case CERT_E_PURPOSE:
pszName = "CERT_E_PURPOSE"; break;
case CERT_E_ISSUERCHAINING:
pszName = "CERT_E_ISSUERCHAINING"; break;
case CERT_E_MALFORMED:
pszName = "CERT_E_MALFORMED"; break;
case CERT_E_UNTRUSTEDROOT:
pszName = "CERT_E_UNTRUSTEDROOT"; break;
case CERT_E_CHAINING:
pszName = "CERT_E_CHAINING"; break;
case TRUST_E_FAIL:
pszName = "TRUST_E_FAIL"; break;
case CERT_E_REVOKED:
pszName = "CERT_E_REVOKED"; break;
case CERT_E_UNTRUSTEDTESTROOT:
pszName = "CERT_E_UNTRUSTEDTESTROOT"; break;
case CERT_E_REVOCATION_FAILURE:
pszName = "CERT_E_REVOCATION_FAILURE"; break;
case CERT_E_CN_NO_MATCH:
pszName = "CERT_E_CN_NO_MATCH"; break;
case CERT_E_WRONG_USAGE:
pszName = "CERT_E_WRONG_USAGE"; break;
case CRYPT_E_NO_REVOCATION_CHECK:
pszName = "CRYPT_E_NO_REVOCATION_CHECK";break;
case CRYPT_E_REVOKED:
pszName = "CRYPT_E_REVOKED";break;
case CRYPT_E_REVOCATION_OFFLINE:
pszName = "CRYPT_E_REVOCATION_OFFLINE";break;
case ERROR_SUCCESS:
pszName = "SUCCESS";break;
default:
pszName = "(unknown)"; break;
}
DBGPRINTF((DBG_CONTEXT,
"Client cert verification result = 0x%x (%s)\n",
dwPolicyStatus, pszName));
}
CredHandle *
SSL_STREAM_CONTEXT::QueryCredentials(
VOID
)
/*++
Routine Description:
Get the applicable credentials (depending on whether we're mapping or not)
Arguments:
None
Return Value:
CredHandle *
--*/
{
DBG_ASSERT( _pEndpointConfig != NULL );
return _pEndpointConfig->QueryCredentials();
}