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