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.
 
 
 
 
 
 

1684 lines
45 KiB

/*++
Copyright (c) 2000-2001 Microsoft Corporation
Module Name :
sslcontext.cxx
Abstract:
SSL stream context for the client SSL support.
Author:
Rajesh Sundaram (rajeshsu) 1-April-2001.
Environment:
Win32 - User Mode
Project:
Stream Filter Worker Process
--*/
#include "precomp.hxx"
// BUGBUG: close connection to server thro SSPI when cert validation fails
// BUGBUG: Explore - server cert alert messages
SECURITY_STATUS
CreateCredentialsHandle(
IN DWORD dwProtocolType,
IN DWORD dwFlags,
IN PCCERT_CONTEXT pCertContext,
OUT PCredHandle phClienCred
);
UC_SSL_STREAM_CONTEXT::UC_SSL_STREAM_CONTEXT(
FILTER_CHANNEL_CONTEXT *pUcContext
)
: STREAM_CONTEXT (pUcContext),
_pServerName (0),
_ServerNameLength (0),
_SslProtocolVersion (0),
_sslState (UC_SSL_STATE_HANDSHAKE_START),
_fRenegotiate (FALSE),
_fValidContext (FALSE),
_fValidClientCred (FALSE),
_cbReReadOffset (0),
_pServerCert (NULL),
_pSerializedCert (NULL),
_SerializedCertLength (0),
_pSerializedStore (NULL),
_SerializedStoreLength (0),
_pClientCert (NULL),
_cbDecrypted (0),
_ValidateServerCertFlag (0),
_fValidServerCertInfo (FALSE)
{
//
// Initialize security buffer structs
//
//
// Setup buffer to hold incoming raw data
//
_Message.ulVersion = SECBUFFER_VERSION;
_Message.cBuffers = 4;
_Message.pBuffers = _Buffers;
_Buffers[0].BufferType = SECBUFFER_EMPTY;
_Buffers[1].BufferType = SECBUFFER_EMPTY;
_Buffers[2].BufferType = SECBUFFER_EMPTY;
_Buffers[3].BufferType = SECBUFFER_EMPTY;
//
// Setup buffer for ISC to return raw data to be sent to client
//
_MessageOut.ulVersion = SECBUFFER_VERSION;
_MessageOut.cBuffers = 4;
_MessageOut.pBuffers = _OutBuffers;
_OutBuffers[0].BufferType = SECBUFFER_EMPTY;
_OutBuffers[1].BufferType = SECBUFFER_EMPTY;
_OutBuffers[2].BufferType = SECBUFFER_EMPTY;
_OutBuffers[3].BufferType = SECBUFFER_EMPTY;
//
// Setup buffer for app data to be encrypted
//
_EncryptMessage.ulVersion = SECBUFFER_VERSION;
_EncryptMessage.cBuffers = 4;
_EncryptMessage.pBuffers = _EncryptBuffers;
_EncryptBuffers[0].BufferType = SECBUFFER_EMPTY;
_EncryptBuffers[1].BufferType = SECBUFFER_EMPTY;
_EncryptBuffers[2].BufferType = SECBUFFER_EMPTY;
_EncryptBuffers[3].BufferType = SECBUFFER_EMPTY;
//
// Zero out data structures
//
ZeroMemory(&_hContext, sizeof(_hContext));
ZeroMemory(&_hClientCred, sizeof(_hClientCred));
ZeroMemory(&_ucServerCertInfo, sizeof(_ucServerCertInfo));
ZeroMemory(&_IssuerListInfo, sizeof(_IssuerListInfo));
}
UC_SSL_STREAM_CONTEXT::~UC_SSL_STREAM_CONTEXT()
{
if (_fValidContext)
{
DeleteSecurityContext(&_hContext);
_fValidContext = FALSE;
}
if (_fValidClientCred)
{
FreeCredentialsHandle(&_hClientCred);
_fValidClientCred = FALSE;
}
if (_pServerCert != NULL)
{
CertFreeCertificateContext(_pServerCert);
_pServerCert = NULL;
}
if (_pClientCert != NULL)
{
CertFreeCertificateContext(_pClientCert);
_pClientCert = NULL;
}
if (_pServerName != _ServerNameBuffer && _pServerName)
{
delete[] _pServerName;
_pServerName = NULL;
}
if (_IssuerListInfo.aIssuers)
{
DBG_ASSERT(_IssuerListInfo.cIssuers);
FreeContextBuffer(_IssuerListInfo.aIssuers);
_IssuerListInfo.aIssuers = NULL;
}
if (_pSerializedCert)
{
delete[] _pSerializedCert;
_pSerializedCert = NULL;
_SerializedCertLength = 0;
}
if (_pSerializedStore)
{
delete[] _pSerializedStore;
_pSerializedStore = NULL;
_SerializedStoreLength = 0;
}
}
/****************************************************************************++
Routine Description:
Handle a new raw connection
Arguments:
pConnectionInfo - The magic connection information
Return Value:
HRESULT
--****************************************************************************/
HRESULT
UC_SSL_STREAM_CONTEXT::ProcessNewConnection(
CONNECTION_INFO *pConnectionInfo,
ENDPOINT_CONFIG * /*pEndpointConfig*/
)
{
SECURITY_STATUS secStatus;
DBG_ASSERT(_sslState == UC_SSL_STATE_HANDSHAKE_START);
QueryFiltChannelContext()->SetIsSecure( TRUE );
DBG_ASSERT(pConnectionInfo->pClientSSLContext);
// Protocol version
_SslProtocolVersion = (DWORD)
pConnectionInfo->pClientSSLContext->SslProtocolVersion;
if (_SslProtocolVersion == 0)
{
// By default, all protocols are enabled.
_SslProtocolVersion = SP_PROT_CLIENTS;
}
// Client Cert
_pClientCert = (PCCERT_CONTEXT)
pConnectionInfo->pClientSSLContext->pClientCertContext;
if (_pClientCert)
{
// Bump up the reference count on the certificate context
_pClientCert = CertDuplicateCertificateContext(_pClientCert);
}
// Server cert validation
switch (pConnectionInfo->pClientSSLContext->ServerCertValidation)
{
case HttpSslServerCertValidationIgnore:
case HttpSslServerCertValidationManual:
case HttpSslServerCertValidationManualOnce:
_ValidateServerCertFlag = SCH_CRED_MANUAL_CRED_VALIDATION;
break;
case HttpSslServerCertValidationAutomatic:
_ValidateServerCertFlag = SCH_CRED_AUTO_CRED_VALIDATION;
break;
default:
// Catch this invalid case
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
break;
}
//
// Store the server name.
//
_ServerNameLength = pConnectionInfo->pClientSSLContext->ServerNameLength+1;
if (_ServerNameLength <= UC_SERVER_NAME_BUFFER_SIZE * sizeof(WCHAR))
{
_pServerName = _ServerNameBuffer;
}
else
{
_pServerName = new WCHAR [_ServerNameLength];
if (!_pServerName)
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
}
// Copy server name
memcpy(_pServerName,
pConnectionInfo->pClientSSLContext->ServerName,
pConnectionInfo->pClientSSLContext->ServerNameLength);
// Null termination
_pServerName[_ServerNameLength] = L'\0';
//
// Now create client credential handle
//
secStatus = CreateCredentialsHandle(_SslProtocolVersion,
_ValidateServerCertFlag,
_pClientCert,
&_hClientCred);
if (secStatus != SEC_E_OK)
{
DBGPRINTF((DBG_CONTEXT, "CreateCredentialsHandle failed 0x%x\n",
secStatus));
return secStatus;
}
_fValidClientCred = TRUE;
DBGPRINTF((DBG_CONTEXT,
"ProcessNewConnection: Got a new connection for server %ws \n",
_pServerName));
// Everything was Okay
return S_OK;
}
/****************************************************************************++
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
UC_SSL_STREAM_CONTEXT::ProcessRawReadData(
RAW_STREAM_INFO *pRawStreamInfo,
BOOL *pfReadMore,
BOOL *pfComplete
)
{
HRESULT hr = S_OK;
BOOL fExtraData = FALSE;
//
// 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 UC_SSL_STATE_HANDSHAKE_START:
case UC_SSL_STATE_HANDSHAKE_IN_PROGRESS:
DBGPRINTF((DBG_CONTEXT,
"ProcessRawReadData (Wire): _sslState %d, Handshake \n",
_sslState));
hr = DoHandshake(pRawStreamInfo,
pfReadMore,
pfComplete,
&fExtraData);
break;
case UC_SSL_STATE_HANDSHAKE_COMPLETE:
DBGPRINTF((DBG_CONTEXT,
"ProcessRawReadData (Wire): _sslState %d, Decrypt \n",
_sslState));
hr = DoDecrypt(pRawStreamInfo,
pfReadMore,
pfComplete,
&fExtraData);
break;
default:
DBG_ASSERT(FALSE);
}
if (FAILED(hr))
{
DBGPRINTF((DBG_CONTEXT,
"ProcessRawReadData (Wire): _sslState %d, failed %x\n",
_sslState, hr));
break;
}
//
// Is there still some extra data to be processed?
//
} while(fExtraData);
return hr;
}
/****************************************************************************++
Routine Description:
Called on read completion from app
Arguments:
pRawStreamInfo - Points to input stream and size
pfComplete - Set to TRUE if we should disconnect
Return Value:
HRESULT
--****************************************************************************/
HRESULT
UC_SSL_STREAM_CONTEXT::ProcessRawWriteData(
RAW_STREAM_INFO *pRawStreamInfo,
BOOL *pfComplete
)
{
HRESULT hr;
DBG_ASSERT(QueryFiltChannelContext()->QueryFilterBufferType() ==
HttpFilterBufferHttpStream);
DBGPRINTF((DBG_CONTEXT,
"ProcessRawWriteData (App): _sslState %d, Encrypt \n", _sslState));
hr = DoEncrypt(pRawStreamInfo,
pfComplete);
if (FAILED(hr))
{
DBGPRINTF((DBG_CONTEXT,
"ProcessRawWriteData(App): _sslState %d, failed %x\n",
_sslState,hr));
}
return hr;
}
HRESULT
UC_SSL_STREAM_CONTEXT::DoHandshakeCompleted()
{
HRESULT hr = S_OK;
SECURITY_STATUS secStatus = SEC_E_OK;
SecPkgContext_StreamSizes StreamSizes;
HTTP_FILTER_BUFFER ulFilterBuffer;
_sslState = UC_SSL_STATE_HANDSHAKE_COMPLETE;
DBGPRINTF((DBG_CONTEXT, "DoHandShakeCompleted Enter\n"));
//
// 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))
{
DBGPRINTF((DBG_CONTEXT, "QueryContextAttributes failed! - 0x%x\n",
secStatus ));
return secStatus;
}
_cbHeader = StreamSizes.cbHeader;
_cbTrailer = StreamSizes.cbTrailer;
_cbBlockSize = StreamSizes.cbBlockSize;
_cbMaximumMessage = StreamSizes.cbMaximumMessage;
}
if (!_fValidServerCertInfo)
{
//
// Build up a message for the application indicating stuff
// about the negotiated connection
//
hr = BuildServerCertInfo(SEC_E_OK, TRUE, FALSE);
if ( FAILED( hr ) )
{
DBGPRINTF((DBG_CONTEXT,
"BuildServerCertInfo failed! - 0x%x\n",
hr));
return hr;
}
}
DBG_ASSERT(_fValidServerCertInfo);
ulFilterBuffer.BufferType = HttpFilterBufferSslServerCert;
ulFilterBuffer.pBuffer = (PBYTE) &_ucServerCertInfo;
ulFilterBuffer.BufferSize = sizeof( _ucServerCertInfo );
//
// Write the message to the application
//
hr = QueryFiltChannelContext()->DoAppWrite(UL_CONTEXT_FLAG_SYNC,
&ulFilterBuffer,
NULL);
// No longer have a valid server cert info.
_fValidServerCertInfo = FALSE;
return hr;
}
/****************************************************************************++
Routine Description:
Do the SSL handshake for the client.
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
--****************************************************************************/
HRESULT
UC_SSL_STREAM_CONTEXT::DoHandshake(
RAW_STREAM_INFO *pRawStreamInfo,
BOOL *pfReadMore,
BOOL *pfComplete,
BOOL *pfExtraData
)
{
DWORD dwSSPIFlags;
DWORD dwSSPIOutFlags;
SECURITY_STATUS scRet = SEC_E_OK;
TimeStamp tsExpiry;
HRESULT hr = S_OK;
HTTP_FILTER_BUFFER ulFilterBuffer;
*pfReadMore = FALSE;
*pfComplete = FALSE;
*pfExtraData = FALSE;
//
// Setup a call to InitializeSecurityContext
//
dwSSPIFlags = UC_SSL_ISC_FLAGS |
((_ValidateServerCertFlag == SCH_CRED_MANUAL_CRED_VALIDATION)?
ISC_REQ_MANUAL_CRED_VALIDATION : 0);
//
// First, set up the InBuffers.
//
_Buffers[0].pvBuffer = pRawStreamInfo->pbBuffer + _cbReReadOffset;
_Buffers[0].BufferType = SECBUFFER_TOKEN;
_Buffers[0].cbBuffer = pRawStreamInfo->cbData - _cbReReadOffset;
_Buffers[1].BufferType = SECBUFFER_EMPTY;
_Buffers[2].BufferType = SECBUFFER_EMPTY;
_Buffers[3].BufferType = SECBUFFER_EMPTY;
//
// Then, the out buffers.
//
_OutBuffers[0].pvBuffer = NULL;
_OutBuffers[0].cbBuffer = 0;
_OutBuffers[0].BufferType = SECBUFFER_EMPTY;
_OutBuffers[1].BufferType = SECBUFFER_EMPTY;
_OutBuffers[2].BufferType = SECBUFFER_EMPTY;
_OutBuffers[3].BufferType = SECBUFFER_EMPTY;
if (_sslState == UC_SSL_STATE_HANDSHAKE_START)
{
scRet = InitializeSecurityContext(
&_hClientCred,
NULL,
_pServerName,
dwSSPIFlags,
0,
SECURITY_NATIVE_DREP,
NULL,
0,
&_hContext,
&_MessageOut,
&dwSSPIOutFlags,
&tsExpiry
);
DBGPRINTF((DBG_CONTEXT,
"[DoHandshake]:1st InitializeSecurityContext : Return 0x%x \n",
scRet));
if (SUCCEEDED(scRet))
{
_cbHeader = 0;
_cbTrailer = 0;
_cbBlockSize = 0;
_cbMaximumMessage = 0;
_fValidContext = TRUE;
_sslState = UC_SSL_STATE_HANDSHAKE_IN_PROGRESS;
}
}
else
{
DBG_ASSERT(_sslState == UC_SSL_STATE_HANDSHAKE_IN_PROGRESS);
//
// We have already called InitializeSecurityContext once.
//
scRet = InitializeSecurityContext(
&_hClientCred,
&_hContext,
_pServerName,
dwSSPIFlags,
0,
SECURITY_NATIVE_DREP,
&_Message,
0,
&_hContext,
&_MessageOut,
&dwSSPIOutFlags,
&tsExpiry
);
DBGPRINTF((DBG_CONTEXT,
"[DoHandshake]:2nd InitializeSecurityContext 0x%x\n",
scRet));
}
if (SUCCEEDED(scRet))
{
//
// Send response to the server if there is one.
//
if (_OutBuffers[0].pvBuffer && _OutBuffers[0].cbBuffer != 0)
{
hr = QueryFiltChannelContext()->DoRawWrite(UL_CONTEXT_FLAG_SYNC,
_OutBuffers[0].pvBuffer,
_OutBuffers[0].cbBuffer,
NULL);
if (FAILED(hr))
{
goto ExitPoint;
}
}
if (scRet == SEC_E_OK)
{
//
// Done with handshake.
//
hr = DoHandshakeCompleted();
if (FAILED(hr))
{
goto ExitPoint;
}
}
else if (scRet == SEC_I_INCOMPLETE_CREDENTIALS)
{
DBGPRINTF((DBG_CONTEXT, "[DoHandshake]:Client cert needed!\n"));
if (_fRenegotiate)
{
//
// Get issuer list from schannel
//
hr = BuildServerCertInfo(SEC_E_OK, TRUE, TRUE);
if (FAILED(hr))
{
goto ErrorPoint;
}
}
//
// Caller will resume handshake
//
*pfExtraData = TRUE;
//
// caller has to detect that some data is
// still in the buffer not processed and
//
hr = S_OK;
goto ExitPoint;
}
//
// If the input buffer has more info to be SChannelized, then do it
// now. If we haven't completed the handshake, call DoHandshake again,
// else, call DoEncrypt
//
if (_Buffers[1].BufferType == SECBUFFER_EXTRA)
{
//
// We better have valid extra data
// only cbBuffer is used, pvBuffer is not used with SECBUFFER_EXTRA
//
DBG_ASSERT( _Buffers[ 1 ].cbBuffer != 0 );
//
// Move extra data right after decrypted data (if any)
//
memmove( pRawStreamInfo->pbBuffer + _cbDecrypted,
pRawStreamInfo->pbBuffer + pRawStreamInfo->cbData
- _Buffers[ 1 ].cbBuffer,
_Buffers[ 1 ].cbBuffer);
//
// Now we have to adjust pRawStreamInfo->cbData and _cbReReadOffset
//
pRawStreamInfo->cbData = ( _cbDecrypted + _Buffers[ 1 ].cbBuffer );
_cbReReadOffset = _cbDecrypted;
*pfExtraData = TRUE;
//
// caller has to detect that some data is
// still in the buffer not processed and
//
hr = S_OK;
goto ExitPoint;
}
else
{
//
// 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 _cbDecrypted 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;
_cbReReadOffset = _cbDecrypted;
if ( _sslState != UC_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 _cbReReadOffset to make
// sure that next ProcessRawReadData() will work fine
//
_cbReReadOffset = 0;
_cbDecrypted = 0;
hr = S_OK;
goto ExitPoint;
}
else
{
//
// Does Schannel requires more data?
//
if ( scRet == SEC_E_INCOMPLETE_MESSAGE )
{
*pfReadMore = TRUE;
hr = S_OK;
goto ExitPoint;
}
if (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR)
{
if (_OutBuffers[ 0 ].pvBuffer!= NULL &&
_OutBuffers[ 0 ].cbBuffer != 0 )
{
hr = QueryFiltChannelContext()->DoRawWrite(
UL_CONTEXT_FLAG_SYNC,
_OutBuffers[ 0 ].pvBuffer,
_OutBuffers[ 0 ].cbBuffer,
NULL);
}
}
//
// InitializeSecurityContext failed!
//
goto ErrorPoint;
}
ErrorPoint:
{
//
// InitializeSecurityContext failed!
//
ZeroMemory(&_ucServerCertInfo, sizeof(_ucServerCertInfo));
_ucServerCertInfo.Status = scRet;
ulFilterBuffer.BufferType = HttpFilterBufferSslServerCert;
ulFilterBuffer.pBuffer = (PBYTE) &_ucServerCertInfo;
ulFilterBuffer.BufferSize = sizeof( _ucServerCertInfo );
//
// Write the message to the application
//
QueryFiltChannelContext()->DoAppWrite( UL_CONTEXT_FLAG_SYNC,
&ulFilterBuffer,
NULL );
hr = scRet;
}
ExitPoint:
if ( _OutBuffers[ 0 ].pvBuffer != NULL )
{
FreeContextBuffer( _OutBuffers[ 0 ].pvBuffer );
_OutBuffers[ 0 ].pvBuffer = NULL;
}
return hr;
}
/****************************************************************************++
Routine Description:
Encrypt data from the application
Arguments:
pRawStreamInfo - Raw data buffer
pfComplete - Set to true if we should disconnect
Return Value:
HRESULT
--****************************************************************************/
HRESULT
UC_SSL_STREAM_CONTEXT::DoEncrypt(
RAW_STREAM_INFO *pRawStreamInfo,
BOOL *pfComplete
)
{
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 _buffRawWrite where new chunk should be placed
DWORD cbRawWriteOffset = 0;
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++;
}
//
// Allocate a large enough buffer for encrypted data
// ( remember that each chunk needs header and trailer )
//
if ( !_buffRawWrite.Resize( pRawStreamInfo->cbData +
dwChunks * _cbHeader +
dwChunks * _cbTrailer ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
//
// Loop to encrypt required data in chunks each not exceeding
// _cbMaximumMessage
//
for( UINT dwCurrentChunk = 0; dwCurrentChunk < dwChunks; dwCurrentChunk++ )
{
DBG_ASSERT( _buffRawWrite.QuerySize() > cbRawWriteOffset );
cbDataChunk = min( pRawStreamInfo->cbData - cbDataProcessed,
_cbMaximumMessage );
memcpy( (PBYTE) _buffRawWrite.QueryPtr() + _cbHeader + cbRawWriteOffset,
pRawStreamInfo->pbBuffer + cbDataProcessed,
cbDataChunk );
_EncryptBuffers[ 0 ].pvBuffer = (PBYTE) _buffRawWrite.QueryPtr() +
cbRawWriteOffset;
_EncryptBuffers[ 0 ].cbBuffer = _cbHeader;
_EncryptBuffers[ 0 ].BufferType = SECBUFFER_STREAM_HEADER;
_EncryptBuffers[ 1 ].pvBuffer = (PBYTE) _buffRawWrite.QueryPtr() +
_cbHeader +
cbRawWriteOffset;
_EncryptBuffers[ 1 ].cbBuffer = cbDataChunk;
_EncryptBuffers[ 1 ].BufferType = SECBUFFER_DATA;
_EncryptBuffers[ 2 ].pvBuffer = (PBYTE) _buffRawWrite.QueryPtr() +
_cbHeader +
cbDataChunk +
cbRawWriteOffset;
_EncryptBuffers[ 2 ].cbBuffer = _cbTrailer;
_EncryptBuffers[ 2 ].BufferType = SECBUFFER_STREAM_TRAILER;
_EncryptBuffers[ 3 ].BufferType = SECBUFFER_EMPTY;
secStatus = EncryptMessage( &_hContext,
0,
&_EncryptMessage,
0 );
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;
return secStatus;
}
}
//
// Replace the raw stream buffer with the encrypted data
//
pRawStreamInfo->pbBuffer = (PBYTE) _buffRawWrite.QueryPtr();
pRawStreamInfo->cbBuffer = _buffRawWrite.QuerySize();
pRawStreamInfo->cbData = cbRawWriteOffset;
return S_OK;
}
/****************************************************************************++
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
--****************************************************************************/
HRESULT
UC_SSL_STREAM_CONTEXT::DoDecrypt(
RAW_STREAM_INFO *pRawStreamInfo,
BOOL *pfReadMore,
BOOL *pfComplete,
BOOL *pfExtraData
)
{
SECURITY_STATUS secStatus = SEC_E_OK;
INT iExtra;
if ( pRawStreamInfo == NULL ||
pfReadMore == NULL ||
pfComplete == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
*pfReadMore = FALSE;
*pfComplete = FALSE;
*pfExtraData = FALSE;
DBGPRINTF((DBG_CONTEXT,
"DoDecrypt(): _cbDecrypted = %d, _cbReReadOffset=%d\n",
_cbDecrypted, _cbReReadOffset));
//
// 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
//
DBG_ASSERT( pRawStreamInfo->cbData > _cbReReadOffset );
_Buffers[ 0 ].pvBuffer = pRawStreamInfo->pbBuffer + _cbReReadOffset;
_Buffers[ 0 ].cbBuffer = pRawStreamInfo->cbData - _cbReReadOffset;
_Buffers[ 0 ].BufferType = SECBUFFER_DATA;
_Buffers[ 1 ].BufferType = SECBUFFER_EMPTY;
_Buffers[ 2 ].BufferType = SECBUFFER_EMPTY;
_Buffers[ 3 ].BufferType = SECBUFFER_EMPTY;
DecryptAgain:
secStatus = DecryptMessage( &_hContext,
&_Message,
0,
NULL );
DBGPRINTF((DBG_CONTEXT,
"DecryptMessage( bytes:%d) secStatus=0x%x\n",
pRawStreamInfo->cbData - _cbReReadOffset,
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
//
_cbReReadOffset = (DWORD) DIFF( (BYTE *)_Buffers[ 0 ].pvBuffer -
pRawStreamInfo->pbBuffer );
QueryFiltChannelContext()->SetNextRawReadSize( _Buffers[ 1 ].cbBuffer );
*pfReadMore = TRUE;
return S_OK;
}
return secStatus;
}
if (secStatus != SEC_E_OK &&
secStatus != SEC_I_RENEGOTIATE)
{
return secStatus;
}
if ( secStatus == SEC_E_OK )
{
DBG_ASSERT( _Buffers[ 1 ].BufferType == SECBUFFER_DATA );
//
// Take decrypted data and fit it into read 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 ( _Buffers[ i ].BufferType == SECBUFFER_EXTRA )
{
iExtra = i;
break;
}
}
if ( iExtra != 0 )
{
//
// process extra buffer
//
_cbReReadOffset = (DWORD) DIFF( (PBYTE) _Buffers[ iExtra ].pvBuffer -
pRawStreamInfo->pbBuffer );
if ( secStatus != SEC_I_RENEGOTIATE )
{
_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;
goto DecryptAgain;
}
}
if ( secStatus == SEC_I_RENEGOTIATE )
{
//
// If a renegotiation is triggered, resume the handshake state
//
_fRenegotiate = TRUE;
_sslState = UC_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;
}
//
// 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 _cbReReadOffset to make sure that
// next ProcessRawReadData() will work fine.
//
_cbDecrypted = 0;
_cbReReadOffset = 0;
return S_OK;
}
/****************************************************************************++
Routine Description:
Initialize SSL
Arguments:
None
Return Value:
HRESULT
--****************************************************************************/
//static
HRESULT
UC_SSL_STREAM_CONTEXT::Initialize(
VOID
)
{
return S_OK;
}
/****************************************************************************++
Routine Description:
Terminate SSL
Arguments:
None
Return Value:
None
--****************************************************************************/
//static
VOID
UC_SSL_STREAM_CONTEXT::Terminate(
VOID
)
{
}
/*++
Routine Description:
Build UL_SSL_INFO structure given Schannel context handle
Arguments:
None
Return Value:
HRESULT
--*/
HRESULT
UC_SSL_STREAM_CONTEXT::BuildServerCertInfo(
SECURITY_STATUS InfoStatus,
BOOL fServerCert,
BOOL fIssuerList
)
{
SECURITY_STATUS secStatus;
DBG_ASSERT(_fValidServerCertInfo == FALSE);
if (fServerCert)
{
secStatus = GetServerCert();
if (FAILED(secStatus))
{
DBGPRINTF((DBG_CONTEXT, "Failed GetServerCert 0x%x\n", secStatus));
return secStatus;
}
}
// Is issuer list required?
if (fIssuerList)
{
secStatus = GetIssuerList();
if (FAILED(secStatus))
{
DBGPRINTF((DBG_CONTEXT, "Failed GetIssuerList 0x%x\n", secStatus));
return secStatus;
}
}
//
// Build Server cert Info
//
ZeroMemory(&_ucServerCertInfo, sizeof(_ucServerCertInfo));
// Issuer list required?
if (fIssuerList)
{
_ucServerCertInfo.IssuerInfo.IssuerCount = _IssuerListInfo.cIssuers;
_ucServerCertInfo.IssuerInfo.pIssuerList = _IssuerListInfo.aIssuers;
_ucServerCertInfo.IssuerInfo.IssuerListLength =
_IssuerListInfo.aIssuers[_IssuerListInfo.cIssuers-1].pbData -
(PBYTE)_IssuerListInfo.aIssuers +
_IssuerListInfo.aIssuers[_IssuerListInfo.cIssuers-1].cbData;
}
else
{
_ucServerCertInfo.IssuerInfo.IssuerListLength = 0;
_ucServerCertInfo.IssuerInfo.IssuerCount = 0;
_ucServerCertInfo.IssuerInfo.pIssuerList = 0;
}
if (fServerCert)
{
BuildServerCert();
}
// Ssl handshake was completed
_ucServerCertInfo.Status = InfoStatus;
// We now have a valid server cert info
_fValidServerCertInfo = TRUE;
return S_OK;
}
HRESULT
UC_SSL_STREAM_CONTEXT::GetServerCert(
VOID
)
{
SECURITY_STATUS secStatus;
PUCHAR pbCertContext;
DWORD cbCertContext;
CRYPT_DATA_BLOB CertStoreData;
// Free server cert before getting a new server cert
if (_pServerCert != NULL)
{
CertFreeCertificateContext(_pServerCert);
_pServerCert = NULL;
}
// Free serialized cert
if (_pSerializedCert)
{
delete[] _pSerializedCert;
_pSerializedCert = NULL;
_SerializedCertLength = 0;
}
// Free serialized store
if (_pSerializedStore)
{
delete[] _pSerializedStore;
_pSerializedStore = NULL;
_SerializedStoreLength = 0;
}
// Retrieve the server certificate
secStatus = QueryContextAttributes(&_hContext,
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
(PVOID)&_pServerCert);
if (!SUCCEEDED(secStatus))
{
DBGPRINTF((DBG_CONTEXT,
"Error 0x%x querying server certificate\n",
secStatus));
return secStatus;
}
//
// Serialize Server Certificate
//
pbCertContext = NULL;
cbCertContext = 0;
// First find the length of serialized cert
if (!CertSerializeCertificateStoreElement(_pServerCert,
0,
NULL,
&cbCertContext))
{
secStatus = GetLastError();
DBGPRINTF((DBG_CONTEXT,
"Error 0x%x serialize cert store element\n",
secStatus));
return secStatus;
}
// Allocate memory for serialized cert
pbCertContext = new UCHAR[cbCertContext];
if (!pbCertContext)
{
DBGPRINTF((DBG_CONTEXT, "Error allocating memory for cert context\n"));
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
// Now get the serialized cert
if (!CertSerializeCertificateStoreElement(_pServerCert,
0,
pbCertContext,
&cbCertContext))
{
secStatus = GetLastError();
DBGPRINTF((DBG_CONTEXT,
"Error 0x%x serialize cert store element\n",
secStatus));
return secStatus;
}
// Store away the serialized cert
_pSerializedCert = pbCertContext;
_SerializedCertLength = cbCertContext;
//
// Serialize Server Certificate's store
//
CertStoreData.pbData = NULL;
CertStoreData.cbData = 0;
if (_pServerCert->hCertStore)
{
// Find the length of serialized store
if (!CertSaveStore(_pServerCert->hCertStore,
X509_ASN_ENCODING,
CERT_STORE_SAVE_AS_STORE,
CERT_STORE_SAVE_TO_MEMORY,
(PVOID)&CertStoreData,
0))
{
secStatus = GetLastError();
DBGPRINTF((DBG_CONTEXT, "CertSaveStore failed 0x%x\n", secStatus));
return secStatus;
}
// Allocate memory for serialized store
CertStoreData.pbData = new UCHAR[CertStoreData.cbData];
if (!CertStoreData.pbData)
{
DBGPRINTF((DBG_CONTEXT, "Error allocating memory cert store\n"));
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
// Now get the serialized store
if (!CertSaveStore(_pServerCert->hCertStore,
X509_ASN_ENCODING,
CERT_STORE_SAVE_AS_STORE,
CERT_STORE_SAVE_TO_MEMORY,
(PVOID)&CertStoreData,
0))
{
secStatus = GetLastError();
DBGPRINTF((DBG_CONTEXT, "CertSaveStore failed 0x%x\n", secStatus));
return secStatus;
}
_pSerializedStore = CertStoreData.pbData;
_SerializedStoreLength = CertStoreData.cbData;
}
return S_OK;
}
HRESULT
UC_SSL_STREAM_CONTEXT::GetIssuerList(
VOID
)
{
SECURITY_STATUS secStatus;
// If there is a previous issuer list, free it now
if (_IssuerListInfo.aIssuers)
{
DBG_ASSERT(_IssuerListInfo.cIssuers);
FreeContextBuffer(_IssuerListInfo.aIssuers);
ZeroMemory(&_IssuerListInfo, sizeof(_IssuerListInfo));
}
// Get Issuer list from schannel
secStatus = QueryContextAttributes(&_hContext,
SECPKG_ATTR_ISSUER_LIST_EX,
(PVOID)&_IssuerListInfo);
if (secStatus != SEC_E_OK)
{
DBGPRINTF((DBG_CONTEXT,
"Error 0x%x querying issuer list info\n", secStatus));
}
return secStatus;
}
HRESULT
UC_SSL_STREAM_CONTEXT::BuildServerCert(
VOID
)
{
SECURITY_STATUS secStatus = SEC_E_OK;
DWORD CertHashLength;
DBG_ASSERT(_pServerCert);
//
// Server certificate
//
//_ucServerCertInfo.Cert.pCertContext = (PVOID)_pServerCert;
//
// Get server certificate hash
//
CertHashLength = sizeof(_ucServerCertInfo.Cert.CertHash);
// Certificate hash
if (!CertGetCertificateContextProperty(_pServerCert,
CERT_SHA1_HASH_PROP_ID,
_ucServerCertInfo.Cert.CertHash,
&CertHashLength))
{
secStatus = GetLastError();
DBGPRINTF((DBG_CONTEXT,
"Error 0x%x getting server certificate hash\n",
secStatus));
return secStatus;
}
DBG_ASSERT(CertHashLength <= sizeof(_ucServerCertInfo.Cert.CertHash));
// Server certificate hash length
_ucServerCertInfo.Cert.CertHashLength = CertHashLength;
// Serialized server cert
_ucServerCertInfo.Cert.pSerializedCert = _pSerializedCert;
_ucServerCertInfo.Cert.SerializedCertLength = _SerializedCertLength;
DBGPRINTF((DBG_CONTEXT, "Cert 0x%x, Length 0x%x\n",
_pSerializedCert,
_SerializedCertLength));
// Serialized cert store
_ucServerCertInfo.Cert.pSerializedCertStore = _pSerializedStore;
_ucServerCertInfo.Cert.SerializedCertStoreLength = _SerializedStoreLength;
DBGPRINTF((DBG_CONTEXT, "Store 0x%x, Length 0x%x\n",
_pSerializedStore,
_SerializedStoreLength));
return S_OK;
}
/****************************************************************************++
Routine Description:
Calls AcquireCredentialsHandle & returns the handle to the caller.
Arguments:
dwProtocolType - Supported protocol types by SSPI.
(SP_PROT_SSL2_CLIENT, SP_PROT_SSL3_CLIENT,
SP_PROT_TLS1_CLIENT, etc)
dwFlags - Additional flags that are passed to
AcquireCredentialsHandle
pCertContext - Client certificate, if any
phClientCred - client credentials handle (returned)
Return Value:
SECURITY_STATUS
--****************************************************************************/
SECURITY_STATUS
CreateCredentialsHandle(
IN DWORD dwProtocolType,
IN DWORD dwFlags,
IN PCCERT_CONTEXT pCertContext,
OUT PCredHandle phClientCred
)
{
SCHANNEL_CRED SChannelCred;
SECURITY_STATUS Status;
TimeStamp tsExpiry;
ZeroMemory(&SChannelCred, sizeof(SCHANNEL_CRED));
SChannelCred.dwVersion = SCHANNEL_CRED_VERSION;
if (pCertContext)
{
SChannelCred.paCred = &pCertContext;
SChannelCred.cCreds = 1;
}
SChannelCred.grbitEnabledProtocols = dwProtocolType;
#if 0
//
// This is used to pick RSA over DH, I don't think we need this for
// HTTP client (IE does not use this)
//
if (aiKeyExch)
{
rgbSupportedAlgs[cSupportedAlgs++] = aiKeyExch;
}
if (cSupportedAlgs)
{
SChannelCred.cSupportedAlgs = cSupportedAlgs;
SChannelCred.palgSupportedAlgs = rgbSupportedAlgs;
}
#endif
SChannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
// dwFlags can be either
// SCH_CRED_MANUAL_CRED_VALIDATION or
// SCH_CRED_AUTO_CRED_VALIDATION
//
SChannelCred.dwFlags |= dwFlags;
//
// Create an SSPI handle.
//
Status = AcquireCredentialsHandle(
NULL, // name of principal
UNISP_NAME, // Name of package
SECPKG_CRED_OUTBOUND, // Flags indicating use
NULL, // LogonID
&SChannelCred, // Package specific data
NULL, // GetKey() function
NULL, // GetKey context
phClientCred, // (out) Cred handle
&tsExpiry // (out) lifetime
);
if (Status != SEC_E_OK)
{
DBGPRINTF((DBG_CONTEXT,
"AcquireCredentialsHandle failed with 0x%x \n",
Status));
}
return Status;
}