windows-nt/Source/XPSP1/NT/ds/security/protocols/schannel/spbase/ssl2cli.c
2020-09-26 16:20:57 +08:00

808 lines
23 KiB
C

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1995.
//
// File: ssl2cli.c
//
// Contents:
//
// Classes:
//
// Functions:
//
// History: 8-08-95 RichardW Created
//
//----------------------------------------------------------------------------
#include <spbase.h>
#include <ssl2msg.h>
#include <ssl3msg.h>
#include <ssl2prot.h>
SP_STATUS WINAPI
Ssl2ClientProtocolHandler(
PSPContext pContext,
PSPBuffer pCommInput,
PSPBuffer pCommOutput)
{
SP_STATUS pctRet = 0;
DWORD cMessageType;
DWORD dwStateTransition;
BOOL fRaw = TRUE;
SPBuffer MsgInput;
DWORD cbMsg;
PUCHAR pb;
UCHAR bCT;
if (NULL != pCommOutput)
{
pCommOutput->cbData = 0;
}
MsgInput.pvBuffer = pCommInput->pvBuffer;
MsgInput.cbBuffer = pCommInput->cbBuffer;
MsgInput.cbData = pCommInput->cbData;
// In the following states, we should decrypt the message:
switch(pContext->State)
{
case SSL2_STATE_CLIENT_MASTER_KEY:
case SSL2_STATE_CLIENT_FINISH:
case SSL2_STATE_CLIENT_RESTART:
{
DWORD cbHeader;
DWORD cbPadding;
if (MsgInput.cbData < 3)
{
return PCT_INT_INCOMPLETE_MSG;
}
if(((PCHAR)pCommInput->pvBuffer)[0] & 0x80)
{
cbHeader = 2 + pContext->pHashInfo->cbCheckSum;
cbPadding = 0;
}
else
{
cbHeader = 3 + pContext->pHashInfo->cbCheckSum;
cbPadding = ((PCHAR)pCommInput->pvBuffer)[2];
}
(PUCHAR)MsgInput.pvBuffer += cbHeader;
MsgInput.cbBuffer -= cbHeader;
MsgInput.cbData -= (cbHeader+cbPadding);
pctRet = Ssl2DecryptMessage(pContext, pCommInput, &MsgInput);
if (pctRet != PCT_ERR_OK)
{
// to handle incomplete message errors
return(pctRet);
}
cMessageType = ((PUCHAR) MsgInput.pvBuffer)[0];
fRaw = FALSE;
break;
}
case SP_STATE_SHUTDOWN:
case SP_STATE_SHUTDOWN_PENDING:
cMessageType = 0;
break;
default:
if(pCommInput->cbData < 3)
{
return PCT_INT_INCOMPLETE_MSG;
}
cMessageType = ((PUCHAR) MsgInput.pvBuffer)[2];
break;
}
dwStateTransition = pContext->State | (cMessageType<<16);
switch(dwStateTransition)
{
case SP_STATE_SHUTDOWN_PENDING:
// There's no CloseNotify in SSL2, so just transition to
// the shutdown state and leave the output buffer empty.
pContext->State = SP_STATE_SHUTDOWN;
break;
case SP_STATE_SHUTDOWN:
return PCT_INT_EXPIRED;
case (SSL2_MT_SERVER_HELLO << 16) | UNI_STATE_CLIENT_HELLO:
case (SSL2_MT_SERVER_HELLO << 16) | SSL2_STATE_CLIENT_HELLO:
{
PSsl2_Server_Hello pHello;
// Attempt to recognize and handle various versions of Server
// hello, start by trying to unpickle the oldest, and the next
// version, until one unpickles. Then run the handle code.
// We can also put unpickling and handling code in here for SSL
// messages.
pctRet = Ssl2UnpackServerHello(pCommInput, &pHello);
if (PCT_ERR_OK == pctRet)
{
if (pHello->SessionIdHit)
{
pctRet = Ssl2CliHandleServerRestart(
pContext,
pCommInput,
pHello,
pCommOutput);
if (PCT_ERR_OK == pctRet)
{
pContext->State = SSL2_STATE_CLIENT_RESTART;
}
}
else
{
if(pContext->RipeZombie->hMasterKey != 0)
{
// We've attempted to do a reconnect and the server has
// blown us off. In this case we must use a new and different
// cache entry.
pContext->RipeZombie->ZombieJuju = FALSE;
if(!SPCacheClone(&pContext->RipeZombie))
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
}
}
if (pctRet == PCT_ERR_OK)
{
pctRet = Ssl2CliHandleServerHello(
pContext,
pCommInput,
pHello,
pCommOutput);
}
if (PCT_ERR_OK == pctRet)
{
pContext->State = SSL2_STATE_CLIENT_MASTER_KEY;
}
}
SPExternalFree(pHello);
}
else if(pctRet != PCT_INT_INCOMPLETE_MSG)
{
pctRet |= PCT_INT_DROP_CONNECTION;
}
if (SP_FATAL(pctRet))
{
pContext->State = PCT1_STATE_ERROR;
}
break;
}
case (SSL2_MT_SERVER_VERIFY << 16) | SSL2_STATE_CLIENT_MASTER_KEY:
pctRet = Ssl2CliHandleServerVerify(
pContext,
&MsgInput,
pCommOutput);
if (PCT_ERR_OK == pctRet)
{
pContext->State =SSL2_STATE_CLIENT_FINISH;
}
if (SP_FATAL(pctRet))
{
pContext->State = PCT1_STATE_ERROR;
}
break;
case (SSL2_MT_SERVER_VERIFY << 16) | SSL2_STATE_CLIENT_RESTART:
pctRet = Ssl2CliFinishRestart(pContext, &MsgInput, pCommOutput);
if (PCT_ERR_OK == pctRet)
{
pContext->State =SSL2_STATE_CLIENT_FINISH;
}
if (SP_FATAL(pctRet))
{
pContext->State = PCT1_STATE_ERROR;
}
// Note, we will transmit no data, but we expect a server finished message.
// If the SSPI EXTRA DATA message is not processed by wininet
// then we may be in trouble.
break;
case (SSL2_MT_SERVER_FINISHED_V2 << 16) | SSL2_STATE_CLIENT_FINISH:
pctRet = Ssl2CliHandleServerFinish(
pContext,
&MsgInput,
pCommOutput);
if (SP_FATAL(pctRet))
{
pContext->State = PCT1_STATE_ERROR;
}
else
{
if (PCT_ERR_OK == pctRet)
{
pContext->State = SP_STATE_CONNECTED;
pContext->DecryptHandler = Ssl2DecryptHandler;
pContext->Encrypt = Ssl2EncryptMessage;
pContext->Decrypt = Ssl2DecryptMessage;
pContext->GetHeaderSize = Ssl2GetHeaderSize;
}
// We received a non-fatal error, so the state doesn't
// change, giving the app time to deal with this.
}
break;
default:
DebugLog((DEB_WARN, "Error in protocol, dwStateTransition is %lx\n", dwStateTransition));
pContext->State = PCT1_STATE_ERROR;
pctRet = SP_LOG_RESULT(PCT_INT_ILLEGAL_MSG);
break;
}
if (pctRet & PCT_INT_DROP_CONNECTION)
{
pContext->State &= ~SP_STATE_CONNECTED;
}
// To handle incomplete message errors:
return(pctRet);
}
SP_STATUS
Ssl2CliHandleServerHello(
PSPContext pContext,
PSPBuffer pCommInput,
PSsl2_Server_Hello pHello,
PSPBuffer pCommOutput)
{
/* error to return to peer */
SP_STATUS pctRet=PCT_ERR_ILLEGAL_MESSAGE;
Ssl2_Client_Master_Key Key;
DWORD i,j;
PUCHAR pPortionToEncrypt;
PSessCacheItem pZombie;
pCommOutput->cbData = 0;
SP_BEGIN("Ssl2CliHandleServerHello");
pZombie = pContext->RipeZombie;
do {
pContext->ReadCounter++;
#if DBG
DebugLog((DEB_TRACE, "Hello = %x\n", pHello));
DebugLog((DEB_TRACE, " Session ID hit \t%s\n", pHello->SessionIdHit ? "Yes" : "No"));
DebugLog((DEB_TRACE, " Certificate Type\t%d\n", pHello->CertificateType));
DebugLog((DEB_TRACE, " Certificate Len\t%d\n", pHello->cbCertificate));
DebugLog((DEB_TRACE, " cCipherSpecs \t%d\n", pHello->cCipherSpecs));
DebugLog((DEB_TRACE, " ConnectionId \t%d\n", pHello->cbConnectionID));
for (i = 0 ; i < pHello->cCipherSpecs ; i++ )
{
DebugLog((DEB_TRACE, " Cipher[%i] = %06x (%s)\n", i, pHello->pCipherSpecs[i],
DbgGetNameOfCrypto(pHello->pCipherSpecs[i]) ));
}
#endif
/* Cycle throug the array of cipher tuples to spec mappings
* to find one that we support */
pContext->pCipherInfo = NULL;
pContext->pHashInfo = NULL;
pContext->pKeyExchInfo = NULL;
for(j=0;j<pHello->cCipherSpecs;j++)
{
for(i = 0; i < UniNumCiphers; i++)
{
if(UniAvailableCiphers[i].CipherKind == pHello->pCipherSpecs[j])
{
break;
}
}
if(i >= UniNumCiphers)
{
continue;
}
if(UniAvailableCiphers[i].CipherKind != pHello->pCipherSpecs[j])
{
continue;
}
// Some servers send SSL3 cipher suites in the ServerHello
// message. Skip over these.
if((UniAvailableCiphers[i].fProt & SP_PROT_SSL2_CLIENT) == 0)
{
continue;
}
// Copy all of the spec's to the cache
pZombie->fProtocol = SP_PROT_SSL2_CLIENT;
pZombie->aiCipher = UniAvailableCiphers[i].aiCipher;
pZombie->dwStrength = UniAvailableCiphers[i].dwStrength;
pZombie->aiHash = UniAvailableCiphers[i].aiHash;
pZombie->SessExchSpec = UniAvailableCiphers[i].KeyExch;
pctRet = ContextInitCiphersFromCache(pContext);
if(pctRet != PCT_ERR_OK)
{
continue;
}
Key.CipherKind = pHello->pCipherSpecs[j];
break;
}
// Go ahead and initialize the ciphers
pctRet = ContextInitCiphers(pContext, TRUE, TRUE);
if(pctRet != PCT_ERR_OK)
{
break;
}
pctRet = SPLoadCertificate(pZombie->fProtocol,
pHello->CertificateType,
pHello->pCertificate,
pHello->cbCertificate,
&pZombie->pRemoteCert);
if(pctRet != PCT_ERR_OK)
{
break;
}
if(pContext->RipeZombie->pRemotePublic != NULL)
{
SPExternalFree(pContext->RipeZombie->pRemotePublic);
pContext->RipeZombie->pRemotePublic = NULL;
}
pctRet = SPPublicKeyFromCert(pZombie->pRemoteCert,
&pZombie->pRemotePublic,
NULL);
if(PCT_ERR_OK != pctRet)
{
break;
}
// Automatically validate server certificate if appropriate
// context flag is set.
pctRet = AutoVerifyServerCertificate(pContext);
if(pctRet != PCT_ERR_OK)
{
SP_LOG_RESULT(pctRet);
break;
}
// Generate Key Args
if(pContext->pCipherInfo->dwBlockSize > 1)
{
GenerateRandomBits(pZombie->pKeyArgs, pContext->pCipherInfo->dwBlockSize);
pZombie->cbKeyArgs = Key.KeyArgLen = pContext->pCipherInfo->dwBlockSize;
/* Copy over the key args */
CopyMemory(Key.KeyArg,
pZombie->pKeyArgs,
pZombie->cbKeyArgs );
}
else
{
Key.KeyArgLen = 0;
}
CopyMemory(pContext->pConnectionID, pHello->ConnectionID, pHello->cbConnectionID);
pContext->cbConnectionID = pHello->cbConnectionID;
pctRet = pContext->pKeyExchInfo->System->GenerateClientExchangeValue(
pContext,
NULL,
0,
Key.ClearKey,
&Key.ClearKeyLen,
NULL,
&Key.EncryptedKeyLen);
if(PCT_ERR_OK != pctRet)
{
break;
}
Key.pbEncryptedKey = SPExternalAlloc(Key.EncryptedKeyLen);
if(Key.pbEncryptedKey == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
break;
}
pctRet = pContext->pKeyExchInfo->System->GenerateClientExchangeValue(
pContext,
NULL,
0,
Key.ClearKey,
&Key.ClearKeyLen,
Key.pbEncryptedKey,
&Key.EncryptedKeyLen);
if(PCT_ERR_OK != pctRet)
{
SPExternalFree(Key.pbEncryptedKey);
break;
}
// Activate session keys.
pContext->hReadKey = pContext->hPendingReadKey;
pContext->hWriteKey = pContext->hPendingWriteKey;
pContext->hPendingReadKey = 0;
pContext->hPendingWriteKey = 0;
pctRet = Ssl2PackClientMasterKey(&Key, pCommOutput);
SPExternalFree(Key.pbEncryptedKey);
if(PCT_ERR_OK != pctRet)
{
break;
}
pContext->WriteCounter++;
SP_RETURN(PCT_ERR_OK);
} while(TRUE);
if((pContext->Flags & CONTEXT_FLAG_EXT_ERR) &&
(pctRet == PCT_ERR_SPECS_MISMATCH))
{
// Our SSL2 implementation does not do client auth,
// so there is only one error message, cipher error.
pCommOutput->cbData = 3; // MSG-ERROR + ERROR-CODE-MSB + ERROR-CODE-LSB
if(pCommOutput->pvBuffer == NULL)
{
pCommOutput->pvBuffer = SPExternalAlloc(pCommOutput->cbData);
if (NULL == pCommOutput->pvBuffer)
{
SP_RETURN(SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY));
}
pCommOutput->cbBuffer = pCommOutput->cbData;
}
if(pCommOutput->cbData > pCommOutput->cbBuffer)
{
// Required buffer size returned in pCommOutput->cbData.
SP_RETURN(SP_LOG_RESULT(PCT_INT_BUFF_TOO_SMALL));
}
((PUCHAR)pCommOutput->pvBuffer)[0] = SSL2_MT_ERROR;
((PUCHAR)pCommOutput->pvBuffer)[1] = MSBOF(SSL_PE_NO_CIPHER);
((PUCHAR)pCommOutput->pvBuffer)[2] = LSBOF(SSL_PE_NO_CIPHER);
}
SP_RETURN(pctRet | PCT_INT_DROP_CONNECTION);
}
SP_STATUS
Ssl2CliHandleServerRestart(
PSPContext pContext,
PSPBuffer pCommInput,
PSsl2_Server_Hello pHello,
PSPBuffer pCommOutput)
{
/* error to return to peer */
SP_STATUS pctRet=PCT_ERR_ILLEGAL_MESSAGE;
PSessCacheItem pZombie;
pCommOutput->cbData = 0;
SP_BEGIN("Ssl2CliHandleServerRestart");
pZombie = pContext->RipeZombie;
do {
pContext->ReadCounter++;
/* if there's no zombie, the message is wrong. We can't restart. */
if(pZombie == NULL)
{
pctRet = SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE);
break;
}
if(!pZombie->hMasterKey)
{
pctRet = SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE);
break;
}
if(!pZombie->ZombieJuju)
{
DebugLog((DEB_WARN, "Session expired on client machine, but not on server.\n"));
}
CopyMemory(pContext->pConnectionID,
pHello->ConnectionID,
pHello->cbConnectionID);
pContext->cbConnectionID = pHello->cbConnectionID;
/* Cert length, Cipher Specs, and Cert Type should be zero */
// We know what our ciphers are, so init the cipher system
pctRet = ContextInitCiphersFromCache(pContext);
if(PCT_ERR_OK != pctRet)
{
break;
}
pctRet = ContextInitCiphers(pContext, TRUE, TRUE);
if(PCT_ERR_OK != pctRet)
{
break;
}
// Make a new set of session keys.
pctRet = MakeSessionKeys(pContext,
pContext->RipeZombie->hMasterProv,
pContext->RipeZombie->hMasterKey);
if(PCT_ERR_OK != pctRet)
{
break;
}
// Activate session keys.
pContext->hReadKey = pContext->hPendingReadKey;
pContext->hWriteKey = pContext->hPendingWriteKey;
pContext->hPendingReadKey = 0;
pContext->hPendingWriteKey = 0;
/* okay, now send the client finish */
pctRet = Ssl2GenCliFinished(pContext, pCommOutput);
SP_RETURN(pctRet);
} while(TRUE);
SP_RETURN(pctRet | PCT_INT_DROP_CONNECTION);
}
SP_STATUS
Ssl2GenCliFinished(
PSPContext pContext,
PSPBuffer pCommOutput)
{
SP_STATUS pctRet = PCT_ERR_ILLEGAL_MESSAGE;
PSSL2_SERVER_VERIFY pVerify = NULL;
PSSL2_CLIENT_FINISHED pFinish;
DWORD HeaderSize;
SPBuffer MsgOutput;
DWORD cPadding;
BOOL fAlloced=FALSE;
SP_BEGIN("Ssl2GenCliFinished");
pCommOutput->cbData = 0;
MsgOutput.cbData = sizeof(UCHAR) + pContext->cbConnectionID;
cPadding = ((MsgOutput.cbData+pContext->pHashInfo->cbCheckSum) % pContext->pCipherInfo->dwBlockSize);
if(cPadding)
{
cPadding = pContext->pCipherInfo->dwBlockSize - cPadding;
}
HeaderSize = (cPadding?3:2);
pCommOutput->cbData = MsgOutput.cbData +
pContext->pHashInfo->cbCheckSum +
cPadding +
HeaderSize;
/* are we allocating our own memory? */
if(pCommOutput->pvBuffer == NULL)
{
pCommOutput->pvBuffer = SPExternalAlloc(pCommOutput->cbData);
if (NULL == pCommOutput->pvBuffer)
{
SP_RETURN(SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY));
}
fAlloced = TRUE;
pCommOutput->cbBuffer = pCommOutput->cbData;
}
if(pCommOutput->cbData > pCommOutput->cbBuffer)
{
// Required buffer size returned in pCommOutput->cbData.
SP_RETURN(SP_LOG_RESULT(PCT_INT_BUFF_TOO_SMALL));
}
MsgOutput.pvBuffer= (PUCHAR)pCommOutput->pvBuffer +
HeaderSize +
pContext->pHashInfo->cbCheckSum;
MsgOutput.cbBuffer = pCommOutput->cbBuffer -
(pContext->pHashInfo->cbCheckSum + HeaderSize);
pFinish = (PSSL2_CLIENT_FINISHED) MsgOutput.pvBuffer;
pFinish->MessageId = SSL2_MT_CLIENT_FINISHED_V2;
CopyMemory( pFinish->ConnectionID,
pContext->pConnectionID,
pContext->cbConnectionID );
pctRet = Ssl2EncryptMessage( pContext, &MsgOutput, pCommOutput);
if(PCT_ERR_OK != pctRet)
{
SPExternalFree(pCommOutput->pvBuffer);
pCommOutput->pvBuffer = NULL;
pctRet |= PCT_INT_DROP_CONNECTION;
pCommOutput->cbBuffer = 0;
}
SP_RETURN(pctRet);
}
SP_STATUS
Ssl2CliHandleServerVerify(
PSPContext pContext,
PSPBuffer pCommInput,
PSPBuffer pCommOutput)
{
SP_STATUS pctRet = PCT_ERR_ILLEGAL_MESSAGE;
PSSL2_SERVER_VERIFY pVerify = NULL;
/* Read and Write Counters are incremented by the encrypt and decrypt */
SP_BEGIN("Ssl2CliHandleServerVerify");
pCommOutput->cbData = 0;
/* Note, there is no header in this message, as it has been pre-decrypted */
if(pCommInput->cbData != sizeof(UCHAR) + pContext->cbChallenge)
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
pVerify = pCommInput->pvBuffer;
if (pVerify->MessageId != SSL2_MT_SERVER_VERIFY)
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
if (memcmp( pVerify->ChallengeData,
pContext->pChallenge,
pContext->cbChallenge) )
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
pctRet = Ssl2GenCliFinished( pContext, pCommOutput);
SP_RETURN(pctRet);
}
SP_STATUS
Ssl2CliFinishRestart(
PSPContext pContext,
PSPBuffer pCommInput,
PSPBuffer pCommOutput)
{
SP_STATUS pctRet = PCT_ERR_ILLEGAL_MESSAGE;
PSSL2_SERVER_VERIFY pVerify = NULL;
/* Read and Write Counters are incremented by the encrypt and decrypt */
SP_BEGIN("Ssl2CliFinishRestart");
pCommOutput->cbData = 0;
/* Note, there is no header in this message, as it has been pre-decrypted */
if(pCommInput->cbData != sizeof(UCHAR) + pContext->cbChallenge)
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
pVerify = pCommInput->pvBuffer;
if (pVerify->MessageId != SSL2_MT_SERVER_VERIFY)
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
if (memcmp( pVerify->ChallengeData,
pContext->pChallenge,
pContext->cbChallenge) )
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
SP_RETURN(PCT_ERR_OK);
}
SP_STATUS
Ssl2CliHandleServerFinish(
PSPContext pContext,
PSPBuffer pCommInput,
PSPBuffer pCommOutput)
{
SP_STATUS pctRet = PCT_ERR_ILLEGAL_MESSAGE;
PSSL2_SERVER_FINISHED pFinished = NULL;
SP_BEGIN("Ssl2CliHandleServerFinish");
pCommOutput->cbData = 0;
/* Note, there is no header in this message, as it has been pre-decrypted */
if(pCommInput->cbData < sizeof(UCHAR))
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
pFinished = pCommInput->pvBuffer;
if (pFinished->MessageId != SSL2_MT_SERVER_FINISHED_V2)
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
if((pCommInput->cbData-1) != SSL2_SESSION_ID_LEN)
{
SP_RETURN(SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE));
}
CopyMemory( pContext->RipeZombie->SessionID,
pFinished->SessionID,
pCommInput->cbData - 1);
pContext->RipeZombie->cbSessionID = pCommInput->cbData - 1;
SPCacheAdd(pContext);
SP_RETURN(PCT_ERR_OK);
}