windows-nt/Source/XPSP1/NT/ds/security/protocols/schannel/spbase/pct1srv.c

1426 lines
44 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1995.
//
// File: pct1srv.c
//
// Contents:
//
// Classes:
//
// Functions:
//
// History: 09-23-97 jbanes LSA integration stuff.
//
//----------------------------------------------------------------------------
#include <spbase.h>
#include <pct1msg.h>
#include <pct1prot.h>
#include <ssl2msg.h>
SP_STATUS
Pct1SrvHandleUniHello(
PSPContext pContext,
PSPBuffer pCommInput,
PSsl2_Client_Hello pHello,
PSPBuffer pCommOutput);
SP_STATUS WINAPI
Pct1ServerProtocolHandler(PSPContext pContext,
PSPBuffer pCommInput,
PSPBuffer pCommOutput)
{
SP_STATUS pctRet= 0;
DWORD dwStateTransition;
SP_BEGIN("Pct1ServerProtocolHandler");
if(pCommOutput) pCommOutput->cbData = 0;
/* Protocol handling steps should be listed in most common
* to least common in order to improve performance
*/
/* We are not connected, so we're doing
* protocol negotiation of some sort. All protocol
* negotiation messages are sent in the clear */
/* There are no branches in the connecting protocol
* state transition diagram, besides connection and error,
* which means that a simple case statement will do */
/* Do we have enough data to determine what kind of message we have */
/* Do we have enough data to determine what kind of message we have, or how much data we need*/
dwStateTransition = (pContext->State & 0xffff);
if(((pContext->State & 0xffff) != SP_STATE_CONNECTED) &&
((pContext->State & 0xffff) != PCT1_STATE_RENEGOTIATE) &&
((pContext->State & 0xffff) != SP_STATE_SHUTDOWN) &&
((pContext->State & 0xffff) != SP_STATE_SHUTDOWN_PENDING))
{
if(pCommInput->cbData < 3)
{
pctRet = PCT_INT_INCOMPLETE_MSG;
}
}
if(pCommInput->cbData >= 3)
{
dwStateTransition |= (((PUCHAR)pCommInput->pvBuffer)[2]<<16);
}
if(pctRet == PCT_ERR_OK)
{
switch(dwStateTransition)
{
case SP_STATE_SHUTDOWN_PENDING:
// There's no CloseNotify in PCT, 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 SP_STATE_CONNECTED:
{
//We're connected, and we got called, so we must be doing a REDO
SPBuffer In;
DWORD cbMessage;
// Transfer the write key over from the application process.
if(pContext->hWriteKey == 0)
{
pctRet = SPGetUserKeys(pContext, SCH_FLAG_WRITE_KEY);
if(pctRet != PCT_ERR_OK)
{
SP_RETURN(SP_LOG_RESULT(pctRet));
}
}
// Calculate size of buffer
pCommOutput->cbData = 0;
cbMessage = pContext->pHashInfo->cbCheckSum +
pContext->pCipherInfo->dwBlockSize +
sizeof(PCT1_MESSAGE_HEADER_EX);
/* are we allocating our own memory? */
if(pCommOutput->pvBuffer == NULL)
{
pCommOutput->pvBuffer = SPExternalAlloc(cbMessage);
if (NULL == pCommOutput->pvBuffer)
{
SP_RETURN(SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY));
}
pCommOutput->cbBuffer = cbMessage;
}
if(cbMessage > pCommOutput->cbBuffer)
{
pCommOutput->cbData = cbMessage;
SP_RETURN(PCT_INT_BUFF_TOO_SMALL);
}
In.pvBuffer = ((char *)pCommOutput->pvBuffer)+3;
In.cbBuffer = pCommOutput->cbBuffer-3;
In.cbData = 1;
((char *)In.pvBuffer)[0] = PCT1_ET_REDO_CONN;
// Build a Redo Request
pctRet = Pct1EncryptRaw(pContext, &In, pCommOutput, PCT1_ENCRYPT_ESCAPE);
break;
}
/* Server receives client hello */
case (SSL2_MT_CLIENT_HELLO << 16) | UNI_STATE_RECVD_UNIHELLO:
{
PSsl2_Client_Hello pSsl2Hello;
// Attempt to recognize and handle various versions of client
// hello, start by trying to unpickle the most recent version, and
// then next most recent, until one unpickles. Then run the handle
// code. We can also put unpickling and handling code in here for
// SSL messages.
pctRet = Ssl2UnpackClientHello(pCommInput, &pSsl2Hello);
if(PCT_ERR_OK == pctRet)
{
// We know we're doing a full handshake, so allocate a cache entry.
if(!SPCacheRetrieveNew(TRUE,
pContext->pszTarget,
&pContext->RipeZombie))
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
}
else
{
pContext->RipeZombie->fProtocol = pContext->dwProtocol;
pContext->RipeZombie->dwCF = pContext->dwRequestedCF;
pContext->RipeZombie->pServerCred = pContext->pCredGroup;
pctRet = Pct1SrvHandleUniHello(
pContext,
pCommInput,
pSsl2Hello,
pCommOutput);
if (PCT_ERR_OK == pctRet)
{
pContext->State = PCT1_STATE_SERVER_HELLO;
}
}
SPExternalFree(pSsl2Hello);
}
if (SP_FATAL(pctRet))
{
pContext->State = PCT1_STATE_ERROR;
}
break;
}
/* Server receives client hello */
case (PCT1_MSG_CLIENT_HELLO << 16) | PCT1_STATE_RENEGOTIATE:
{
PPct1_Client_Hello pPct1Hello;
UCHAR fRealSessId = 0;
int i;
// This is a renegotiate hello, so we do not restart
pctRet = Pct1UnpackClientHello(
pCommInput,
&pPct1Hello);
if(PCT_ERR_OK == pctRet)
{
// Mark context as "unmapped" so that the new keys will get
// passed to the application process once the handshake is
// completed.
pContext->Flags &= ~CONTEXT_FLAG_MAPPED;
// We need to do a full handshake, so lose the cache entry.
SPCacheDereference(pContext->RipeZombie);
pContext->RipeZombie = NULL;
// Get a new cache item, as restarts are not allowed in
// REDO
if(!SPCacheRetrieveNew(TRUE,
pContext->pszTarget,
&pContext->RipeZombie))
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
}
else
{
pContext->RipeZombie->fProtocol = SP_PROT_PCT1_SERVER;
pContext->RipeZombie->dwCF = pContext->dwRequestedCF;
pContext->RipeZombie->pServerCred = pContext->pCredGroup;
pctRet = Pct1SrvHandleClientHello(pContext,
pCommInput,
pPct1Hello,
pCommOutput);
if(PCT_ERR_OK == pctRet)
{
pContext->State = PCT1_STATE_SERVER_HELLO;
}
}
SPExternalFree(pPct1Hello);
}
else if(pctRet != PCT_INT_INCOMPLETE_MSG)
{
pctRet |= PCT_INT_DROP_CONNECTION;
}
if(SP_FATAL(pctRet))
{
pContext->State = PCT1_STATE_ERROR;
}
break;
}
case (PCT1_MSG_CLIENT_HELLO << 16) | SP_STATE_NONE:
{
PPct1_Client_Hello pPct1Hello;
UCHAR fRealSessId = 0;
int i;
/* Attempt to recognize and handle various versions
* of client hello, start by trying to unpickle the
* most recent version, and then next most recent, until
* one unpickles. Then run the handle code. We can also put
* unpickling and handling code in here for SSL messages */
pctRet = Pct1UnpackClientHello(
pCommInput,
&pPct1Hello);
if(PCT_ERR_OK == pctRet)
{
for(i=0;i<(int)pPct1Hello->cbSessionID;i++)
{
fRealSessId |= pPct1Hello->SessionID[i];
}
if (((pContext->Flags & CONTEXT_FLAG_NOCACHE) == 0) &&
(fRealSessId) &&
(SPCacheRetrieveBySession(pContext,
pPct1Hello->SessionID,
pPct1Hello->cbSessionID,
&pContext->RipeZombie)))
{
// We have a good zombie
DebugLog((DEB_TRACE, "Accept client's reconnect request.\n"));
pctRet = Pct1SrvRestart(pContext,
pPct1Hello,
pCommOutput);
if(PCT_ERR_OK == pctRet)
{
pContext->State = SP_STATE_CONNECTED;
pContext->DecryptHandler = Pct1DecryptHandler;
pContext->Encrypt = Pct1EncryptMessage;
pContext->Decrypt = Pct1DecryptMessage;
pContext->GetHeaderSize = Pct1GetHeaderSize;
}
}
else
{
// We're doing a full handshake, so allocate a cache entry.
if(!SPCacheRetrieveNew(TRUE,
pContext->pszTarget,
&pContext->RipeZombie))
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
}
else
{
pContext->RipeZombie->fProtocol = pContext->dwProtocol;
pContext->RipeZombie->dwCF = pContext->dwRequestedCF;
pContext->RipeZombie->pServerCred = pContext->pCredGroup;
pctRet = Pct1SrvHandleClientHello(pContext,
pCommInput,
pPct1Hello,
pCommOutput);
if (PCT_ERR_OK == pctRet)
{
pContext->State = PCT1_STATE_SERVER_HELLO;
}
}
}
SPExternalFree(pPct1Hello);
}
else if(pctRet != PCT_INT_INCOMPLETE_MSG)
{
pctRet |= PCT_INT_DROP_CONNECTION;
}
if(SP_FATAL(pctRet)) {
pContext->State = PCT1_STATE_ERROR;
}
break;
}
case (PCT1_MSG_CLIENT_MASTER_KEY << 16) | PCT1_STATE_SERVER_HELLO:
pctRet = Pct1SrvHandleCMKey(pContext,
pCommInput,
pCommOutput);
if(SP_FATAL(pctRet)) {
pContext->State = PCT1_STATE_ERROR;
} else {
if(PCT_ERR_OK == pctRet) {
pContext->State = SP_STATE_CONNECTED;
pContext->DecryptHandler = Pct1DecryptHandler;
pContext->Encrypt = Pct1EncryptMessage;
pContext->Decrypt = Pct1DecryptMessage;
pContext->GetHeaderSize = Pct1GetHeaderSize;
}
/* We received a non-fatal error, so the state doesn't
* change, giving the app time to deal with this */
}
break;
default:
pContext->State = PCT1_STATE_ERROR;
{
pctRet = PCT_INT_ILLEGAL_MSG;
if(((PUCHAR)pCommInput->pvBuffer)[2] == PCT1_MSG_ERROR)
{
/* we received an error message, process it */
pctRet = Pct1HandleError(pContext,
pCommInput,
pCommOutput);
} else {
/* we received an unknown error, generate a
* PCT_ERR_ILLEGAL_MESSAGE */
pctRet = Pct1GenerateError(pContext,
pCommOutput,
PCT_ERR_ILLEGAL_MESSAGE,
NULL);
}
}
}
}
if(pctRet & PCT_INT_DROP_CONNECTION)
{
pContext->State &= ~SP_STATE_CONNECTED;
}
SP_RETURN(pctRet);
}
SP_STATUS
Pct1SrvHandleUniHello(
PSPContext pContext,
PSPBuffer pCommInput,
PSsl2_Client_Hello pHello,
PSPBuffer pCommOutput)
{
SP_STATUS pctRet = PCT_ERR_ILLEGAL_MESSAGE;
Pct1_Client_Hello ClientHello;
DWORD iCipher;
DWORD dwSpec;
DWORD i;
CipherSpec aCipherSpecs[PCT1_MAX_CIPH_SPECS];
HashSpec aHashSpecs[PCT1_MAX_HASH_SPECS];
CertSpec aCertSpecs[PCT1_MAX_CERT_SPECS];
ExchSpec aExchSpecs[PCT1_MAX_EXCH_SPECS];
SP_BEGIN("Pct1SrvHandlUniHello");
if(NULL == pContext)
{
SP_RETURN(SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR));
}
ClientHello.pCipherSpecs =aCipherSpecs;
ClientHello.pHashSpecs =aHashSpecs;
ClientHello.pCertSpecs =aCertSpecs;
ClientHello.pExchSpecs =aExchSpecs;
ClientHello.cCipherSpecs =0;
ClientHello.cHashSpecs =0;
ClientHello.cCertSpecs =0;
ClientHello.cExchSpecs =0;
/* validate the buffer configuration */
for (iCipher = 0;
(iCipher < pHello->cCipherSpecs) && (iCipher < PCT1_MAX_CIPH_SPECS) ;
iCipher++ )
{
dwSpec = pHello->CipherSpecs[iCipher] & 0xffff;
switch(pHello->CipherSpecs[iCipher] >> 16)
{
case PCT_SSL_HASH_TYPE:
ClientHello.pHashSpecs[ClientHello.cHashSpecs++] = dwSpec;
break;
case PCT_SSL_EXCH_TYPE:
ClientHello.pExchSpecs[ClientHello.cExchSpecs++] = dwSpec;
break;
case PCT_SSL_CERT_TYPE:
ClientHello.pCertSpecs[ClientHello.cCertSpecs++] = dwSpec;
break;
case PCT_SSL_CIPHER_TYPE_1ST_HALF:
// Do we have enough room for a 2nd half.
if(iCipher+1 >= pHello->cCipherSpecs)
{
break;
}
if((pHello->CipherSpecs[iCipher+1] >> 16) != PCT_SSL_CIPHER_TYPE_2ND_HALF)
{
break;
}
dwSpec = (pHello->CipherSpecs[iCipher+1] & 0xffff) |
(dwSpec<< 16);
ClientHello.pCipherSpecs[ClientHello.cCipherSpecs++] = dwSpec;
break;
}
}
// Restarts are not allowed with Uni Hello's, so we don't need
// The session ID.
ClientHello.cbSessionID = 0;
/* Make the SSL2 challenge into a PCT1 challenge as per the
* compatability doc. */
CopyMemory( ClientHello.Challenge,
pHello->Challenge,
pHello->cbChallenge);
for(i=0; i < pHello->cbChallenge; i++)
{
ClientHello.Challenge[i + pHello->cbChallenge] = ~ClientHello.Challenge[i];
}
ClientHello.cbChallenge = 2*pHello->cbChallenge;
ClientHello.cbKeyArgSize = 0;
pctRet = Pct1SrvHandleClientHello(pContext, pCommInput, &ClientHello, pCommOutput);
SP_RETURN(pctRet);
}
/* Otherwise known as Handle Client Hello */
SP_STATUS
Pct1SrvHandleClientHello(
PSPContext pContext,
PSPBuffer pCommInput,
PPct1_Client_Hello pHello,
PSPBuffer pCommOutput
)
{
SP_STATUS pctRet = PCT_ERR_ILLEGAL_MESSAGE;
PSPCredentialGroup pCred;
Pct1_Server_Hello Reply;
DWORD i, j, k , fMismatch;
BYTE MisData[PCT_NUM_MISMATCHES];
SPBuffer ErrData;
PSessCacheItem pZombie;
BOOL fCert = FALSE;
DWORD aCertSpecs[PCT1_MAX_CERT_SPECS];
DWORD aSigSpecs[PCT1_MAX_SIG_SPECS];
DWORD cCertSpecs;
DWORD cSigSpecs;
BOOL fAllocatedOutput = FALSE;
CertTypeMap LocalCertEncodingPref[5] ;
DWORD cLocalCertEncodingPref = 0;
BOOL fFound;
#if DBG
DWORD di;
#endif
SP_BEGIN("Pct1SrvHandleClientHello");
pCommOutput->cbData = 0;
/* validate the buffer configuration */
ErrData.cbData = 0;
ErrData.pvBuffer = NULL;
ErrData.cbBuffer = 0;
pZombie = pContext->RipeZombie;
pCred = pZombie->pServerCred;
if (!pCred)
{
SP_RETURN(SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR));
}
do {
#if DBG
DebugLog((DEB_TRACE, "Client Hello at %x\n", pHello));
DebugLog((DEB_TRACE, " CipherSpecs %d\n", pHello->cCipherSpecs));
for (di = 0 ; di < pHello->cCipherSpecs ; di++ )
{
DebugLog((DEB_TRACE, " Cipher[%d] = %06x (%s)\n", di,
pHello->pCipherSpecs[di],
DbgGetNameOfCrypto(pHello->pCipherSpecs[di]) ));
}
#endif
/* store the challenge in the auth block */
CopyMemory( pContext->pChallenge,
pHello->Challenge,
pHello->cbChallenge );
pContext->cbChallenge = pHello->cbChallenge;
// The session id was computed when the cache entry
// was created. We do need to fill in the length, though.
pZombie->cbSessionID = PCT1_SESSION_ID_SIZE;
/* Begin to build the server hello */
FillMemory( &Reply, sizeof( Reply ), 0 );
/* no matter what, we need to make a new connection id */
GenerateRandomBits( Reply.ConnectionID,
PCT1_SESSION_ID_SIZE );
Reply.cbConnectionID = PCT1_SESSION_ID_SIZE;
CopyMemory( pContext->pConnectionID,
Reply.ConnectionID,
PCT1_SESSION_ID_SIZE );
pContext->cbConnectionID = PCT_SESSION_ID_SIZE;
/* no restart case */
/* fill in from properties here... */
Reply.RestartOk = FALSE;
Reply.ClientAuthReq = ((pContext->Flags & CONTEXT_FLAG_MUTUAL_AUTH) != 0);
fMismatch = 0;
pContext->pPendingCipherInfo = NULL;
/* Build a list of cert specs */
/* Hash order of preference:
* Server Preference
* Client Preference
*/
for(i=0; i < cPct1CertEncodingPref; i++)
{
for(j=0; j< pHello->cCertSpecs; j++)
{
// Does the client want this cipher type
if(aPct1CertEncodingPref[i].Spec == pHello->pCertSpecs[j])
{
LocalCertEncodingPref[cLocalCertEncodingPref].Spec = aPct1CertEncodingPref[i].Spec;
LocalCertEncodingPref[cLocalCertEncodingPref++].dwCertEncodingType = aPct1CertEncodingPref[i].dwCertEncodingType;
break;
}
}
}
/* Determine Key Exchange to use */
/* Key Exchange order of preference:
* Server Preference
* Client Preference
*/
// NOTE: Yes, the following line does do away with any error
// information if we had a previous mismatch. However, the
// setting of pctRet to mismatch in previous lines is for
// logging purposes only. The actual error report occurs later.
pctRet = PCT_ERR_OK;
for(i=0; i < cPct1LocalExchKeyPref; i++)
{
// Do we enable this cipher
if(NULL == KeyExchangeFromSpec(aPct1LocalExchKeyPref[i].Spec, SP_PROT_PCT1_SERVER))
{
continue;
}
for(j=0; j< pHello->cExchSpecs; j++)
{
// Does the client want this cipher type
if(aPct1LocalExchKeyPref[i].Spec != pHello->pExchSpecs[j])
{
continue;
}
// See if we have a cert for this type of
// key exchange.
pctRet = SPPickServerCertificate(pContext,
aPct1LocalExchKeyPref[i].Spec);
if(pctRet != PCT_ERR_OK)
{
continue;
}
// Store the exch id in the cache.
pZombie->SessExchSpec = aPct1LocalExchKeyPref[i].Spec;
pContext->pKeyExchInfo = GetKeyExchangeInfo(pZombie->SessExchSpec);
// load the exch info structure
if(!IsExchAllowed(pContext,
pContext->pKeyExchInfo,
pZombie->fProtocol))
{
pContext->pKeyExchInfo = NULL;
continue;
}
Reply.SrvExchSpec = aPct1LocalExchKeyPref[i].Spec;
break;
}
if(pContext->pKeyExchInfo)
{
break;
}
}
if(PCT_ERR_OK != pctRet)
{
fMismatch |= PCT_IMIS_CERT;
}
if (NULL == pContext->pKeyExchInfo)
{
pctRet = SP_LOG_RESULT(PCT_ERR_SPECS_MISMATCH);
fMismatch |= PCT_IMIS_EXCH;
}
if (fMismatch)
{
pctRet = PCT_ERR_SPECS_MISMATCH;
break;
}
/* Determine Cipher to use */
/* Cipher order of preference:
* Server Preference
* Client Preference
*/
fFound = FALSE;
for(i=0; i < Pct1NumCipher; i++)
{
for(j=0; j< pHello->cCipherSpecs; j++)
{
// Does the client want this cipher type
if(Pct1CipherRank[i].Spec == pHello->pCipherSpecs[j])
{
// Store this cipher identifier in the cache
pZombie->aiCipher = Pct1CipherRank[i].aiCipher;
pZombie->dwStrength = Pct1CipherRank[i].dwStrength;
// Load the pending cipher structure.
pContext->pPendingCipherInfo = GetCipherInfo(pZombie->aiCipher,
pZombie->dwStrength);
if(!IsCipherAllowed(pContext,
pContext->pPendingCipherInfo,
pZombie->fProtocol,
pZombie->dwCF))
{
pContext->pPendingCipherInfo = NULL;
continue;
}
// Is cipher supported by CSP?
for(k = 0; k < pZombie->pActiveServerCred->cCapiAlgs; k++)
{
PROV_ENUMALGS_EX *pAlgInfo = &pZombie->pActiveServerCred->pCapiAlgs[k];
if(pAlgInfo->aiAlgid != Pct1CipherRank[i].aiCipher)
{
continue;
}
if(Pct1CipherRank[i].dwStrength > pAlgInfo->dwMaxLen ||
Pct1CipherRank[i].dwStrength < pAlgInfo->dwMinLen)
{
continue;
}
if(!(pAlgInfo->dwProtocols & CRYPT_FLAG_PCT1))
{
continue;
}
fFound = TRUE;
break;
}
if(fFound)
{
break;
}
}
}
if(fFound)
{
break;
}
}
if(fFound)
{
Reply.SrvCipherSpec = Pct1CipherRank[i].Spec;
}
else
{
pctRet = SP_LOG_RESULT(PCT_ERR_SPECS_MISMATCH);
fMismatch |= PCT_IMIS_CIPHER;
}
/* Determine Hash to use */
/* Hash order of preference:
* Server Preference
* Client Preference
*/
for(i=0; i < Pct1NumHash; i++)
{
for(j=0; j< pHello->cHashSpecs; j++)
{
// Does the client want this cipher type
if(Pct1HashRank[i].Spec == pHello->pHashSpecs[j])
{
// Store this hash id in the cache
pZombie->aiHash = Pct1HashRank[i].aiHash;
pContext->pPendingHashInfo = GetHashInfo(pZombie->aiHash);
if(!IsHashAllowed(pContext,
pContext->pPendingHashInfo,
pZombie->fProtocol))
{
pContext->pPendingHashInfo = NULL;
continue;
}
Reply.SrvHashSpec = Pct1HashRank[i].Spec;
break;
}
}
if(pContext->pPendingHashInfo)
{
break;
}
}
if (pContext->pPendingHashInfo==NULL)
{
pctRet = SP_LOG_RESULT(PCT_ERR_SPECS_MISMATCH);
fMismatch |= PCT_IMIS_HASH;
}
if (fMismatch)
{
LogCipherMismatchEvent();
pctRet = PCT_ERR_SPECS_MISMATCH;
break;
}
// Pick a certificate to use based on
// the key exchange mechanism selected.
for(i=0; i < cLocalCertEncodingPref; i++)
{
if(LocalCertEncodingPref[i].dwCertEncodingType == pZombie->pActiveServerCred->pCert->dwCertEncodingType)
{
Reply.SrvCertSpec = LocalCertEncodingPref[i].Spec;
break;
}
}
if(Reply.SrvCertSpec == PCT1_CERT_X509_CHAIN)
{
pContext->fCertChainsAllowed = TRUE;
}
Reply.pCertificate = NULL;
Reply.CertificateLen = 0;
// NOTE: SPSerializeCertificate will allocate memory
// for the certificate, which we save in pZombie->pbServerCertificate.
// This must be freed when the zombie dies (can the undead die?)
pctRet = SPSerializeCertificate(SP_PROT_PCT1,
pContext->fCertChainsAllowed,
&pZombie->pbServerCertificate,
&pZombie->cbServerCertificate,
pZombie->pActiveServerCred->pCert,
CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL);
if(pctRet == PCT_ERR_OK)
{
Reply.pCertificate = pZombie->pbServerCertificate;
Reply.CertificateLen = pZombie->cbServerCertificate;
}
else
{
break;
}
pctRet = ContextInitCiphers(pContext, TRUE, TRUE);
if(PCT_ERR_OK != pctRet)
{
break;
}
/* sig and cert specs are pre-zeroed when Reply is initialized */
if(Reply.ClientAuthReq)
{
PCertSysInfo pCertInfo;
PSigInfo pSigInfo;
cCertSpecs=0;
cSigSpecs = 0;
for(i=0; i < cPct1LocalSigKeyPref; i++)
{
pSigInfo = GetSigInfo(aPct1LocalSigKeyPref[i].Spec);
if(pSigInfo != NULL)
{
if(pSigInfo->fProtocol & SP_PROT_PCT1_SERVER)
{
aSigSpecs[cSigSpecs++] = aPct1LocalSigKeyPref[i].Spec;
}
}
}
Reply.pClientSigSpecs = aSigSpecs;
Reply.cSigSpecs = cSigSpecs;
for(i=0; i < cPct1CertEncodingPref; i++)
{
pCertInfo = GetCertSysInfo(aPct1CertEncodingPref[i].dwCertEncodingType);
if(pCertInfo == NULL)
{
continue;
}
if(0 == (pCertInfo->fProtocol & SP_PROT_PCT1_SERVER))
{
continue;
}
aCertSpecs[cCertSpecs++] = aPct1CertEncodingPref[i].Spec;
}
Reply.pClientCertSpecs = aCertSpecs;
Reply.cCertSpecs = cCertSpecs;
}
#if DBG
DebugLog((DEB_TRACE, "Server picks cipher %06x (%s)\n",
Reply.SrvCipherSpec,
DbgGetNameOfCrypto(Reply.SrvCipherSpec) ));
#endif
Reply.ResponseLen = 0;
if(pCommOutput->pvBuffer == NULL)
{
fAllocatedOutput=TRUE;
}
pctRet = Pct1PackServerHello(&Reply, pCommOutput);
if(PCT_ERR_OK != pctRet)
{
break;
}
/* Regenerate the internal pVerifyPrelude, so we */
/* can match it against the client when we get the */
/* client master key */
pctRet = Pct1BeginVerifyPrelude(pContext,
pCommInput->pvBuffer,
pCommInput->cbData,
pCommOutput->pvBuffer,
pCommOutput->cbData);
if(PCT_ERR_OK != pctRet)
{
if(fAllocatedOutput)
{
SPExternalFree(pCommOutput->pvBuffer);
}
break;
}
SP_RETURN(PCT_ERR_OK);
} while (TRUE); /* end Polish Loop */
if(pctRet == PCT_ERR_SPECS_MISMATCH) {
for(i=0;i<PCT_NUM_MISMATCHES;i++)
{
MisData[i] = (BYTE)(fMismatch & 1);
fMismatch = fMismatch >> 1;
}
ErrData.cbData = ErrData.cbBuffer = PCT_NUM_MISMATCHES;
ErrData.pvBuffer = MisData;
}
pctRet = Pct1GenerateError(pContext,
pCommOutput,
pctRet,
&ErrData);
SP_RETURN(pctRet | PCT_INT_DROP_CONNECTION);
}
//+---------------------------------------------------------------------------
//
// Function: Pct1SrvHandleCMKey
//
// Synopsis: Process the ClientKeyExchange message group.
//
// Arguments: [pContext] -- Schannel context.
// [pCommInput] --
// [pCommOutput] --
//
// History: 10-10-97 jbanes Added CAPI integration.
//
// Notes: This routine is called by the server-side only.
//
//----------------------------------------------------------------------------
SP_STATUS
Pct1SrvHandleCMKey(
PSPContext pContext,
PSPBuffer pCommInput,
PSPBuffer pCommOutput)
{
SP_STATUS pctRet = PCT_ERR_ILLEGAL_MESSAGE;
PPct1_Client_Master_Key pMasterKey = NULL;
DWORD dwKeyLen;
DWORD EncryptedLen;
Pct1_Server_Verify Verify;
UCHAR VerifyPrelude[RESPONSE_SIZE];
DWORD cbVerifyPrelude;
SPBuffer ErrData;
DWORD k;
PSessCacheItem pZombie;
PSigInfo pSigInfo;
SP_BEGIN("Pct1SrvHandleCMKey");
pCommOutput->cbData = 0;
ErrData.cbData = 0;
ErrData.pvBuffer = NULL;
ErrData.cbBuffer = 0;
pZombie = pContext->RipeZombie;
do {
pctRet = Pct1UnpackClientMasterKey(pCommInput, &pMasterKey);
if (PCT_ERR_OK != pctRet)
{
// If it's an incomplete message or something, just return;
if(pctRet == PCT_INT_INCOMPLETE_MSG)
{
SP_RETURN(pctRet);
}
break;
}
/* Validate that the client properly authed */
/* The server requested client auth */
/* NOTE: this deviates from the first pct 1.0 spec,
* Now, we continue with the protocol if client
* auth fails. By the first spec, we should
* drop the connection */
if (pContext->Flags & CONTEXT_FLAG_MUTUAL_AUTH)
{
/* Client auth polish loop */
pctRet = PCT_ERR_OK;
do
{
/* check to see if the client sent no cert */
if(pMasterKey->ClientCertLen == 0)
{
/* No client auth */
break;
}
pctRet = SPLoadCertificate(SP_PROT_PCT1_SERVER,
X509_ASN_ENCODING,
pMasterKey->pClientCert,
pMasterKey->ClientCertLen,
&pZombie->pRemoteCert);
if(PCT_ERR_OK != pctRet)
{
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;
}
if(pZombie->pRemoteCert == NULL)
{
break;
}
/* verify that we got a sig type that meets PCT spec */
for(k=0; k < cPct1LocalSigKeyPref; k++)
{
if(aPct1LocalSigKeyPref[k].Spec == pMasterKey->ClientSigSpec)
{
break;
}
}
if(k == cPct1LocalSigKeyPref)
{
break;
}
// Get pointer to signature algorithm info and make sure
// we support it.
pSigInfo = GetSigInfo(pMasterKey->ClientSigSpec);
if(pSigInfo == NULL)
{
pctRet = SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE);
break;
}
if(!(pSigInfo->fProtocol & SP_PROT_PCT1_SERVER))
{
pctRet = SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE);
break;
}
// Verify client authentication signature.
DebugLog((DEB_TRACE, "Verify client response signature.\n"));
pctRet = SPVerifySignature(pZombie->hMasterProv,
pZombie->dwCapiFlags,
pZombie->pRemotePublic,
pSigInfo->aiHash,
pMasterKey->VerifyPrelude,
pMasterKey->VerifyPreludeLen,
pMasterKey->pbResponse,
pMasterKey->ResponseLen,
TRUE);
if(pctRet != PCT_ERR_OK)
{
// client auth signature failed to verify, so client auth
// does not happen.
SP_LOG_RESULT(pctRet);
break;
}
DebugLog((DEB_TRACE, "Client response verified successfully.\n"));
pctRet = SPContextDoMapping(pContext);
} while(FALSE); /* end polish loop */
if(PCT_ERR_OK != pctRet)
{
break;
}
}
/* Client auth was successful */
pctRet = PCT_ERR_ILLEGAL_MESSAGE;
/* Copy over the key args */
CopyMemory( pZombie->pKeyArgs,
pMasterKey->KeyArg,
pMasterKey->KeyArgLen );
pZombie->cbKeyArgs = pMasterKey->KeyArgLen;
// Decrypt the encrypted portion of the master key. Because
// we're CAPI integrated, the keys get derived as well.
pctRet = pContext->pKeyExchInfo->System->GenerateServerMasterKey(
pContext,
pMasterKey->ClearKey,
pMasterKey->ClearKeyLen,
pMasterKey->pbEncryptedKey,
pMasterKey->EncryptedKeyLen);
if(PCT_ERR_OK != pctRet)
{
break;
}
// Activate session keys.
Pct1ActivateSessionKeys(pContext);
if (pMasterKey->VerifyPreludeLen != pContext->pHashInfo->cbCheckSum)
{
pctRet = SP_LOG_RESULT(PCT_ERR_INTEGRITY_CHECK_FAILED);
break;
}
/* Check the verify prelude hashes */
/* Hash(CLIENT_MAC_KEY, Hash( "cvp", CLIENT_HELLO, SERVER_HELLO)) */
/* The internal hash should already be in the verify prelude buffer */
/* from the handle client master key. */
cbVerifyPrelude = sizeof(VerifyPrelude);
pctRet = Pct1EndVerifyPrelude(pContext, VerifyPrelude, &cbVerifyPrelude);
if(PCT_ERR_OK != pctRet)
{
break;
}
/* Did the verify prelude hash successfully? */
if(memcmp(VerifyPrelude, pMasterKey->VerifyPrelude, pContext->pHashInfo->cbCheckSum))
{
pctRet = SP_LOG_RESULT(PCT_ERR_INTEGRITY_CHECK_FAILED);
break;
}
/* don't need master key info anymore */
SPExternalFree(pMasterKey);
pMasterKey = NULL;
pContext->WriteCounter = 2;
pContext->ReadCounter = 2;
pZombie->cbSessionID = PCT1_SESSION_ID_SIZE;
CopyMemory( Verify.SessionIdData,
pZombie->SessionID,
pZombie->cbSessionID);
/* compute the response */
Verify.ResponseLen = sizeof(Verify.Response);
pctRet = Pct1ComputeResponse(pContext,
pContext->pChallenge,
pContext->cbChallenge,
pContext->pConnectionID,
pContext->cbConnectionID,
pZombie->SessionID,
pZombie->cbSessionID,
Verify.Response,
&Verify.ResponseLen);
if(pctRet != PCT_ERR_OK)
{
SP_RETURN(SP_LOG_RESULT(pctRet));
}
pctRet = Pct1PackServerVerify(&Verify, pCommOutput);
if(PCT_ERR_OK != pctRet)
{
break;
}
/* set up the session in cache */
SPCacheAdd(pContext);
SP_RETURN(PCT_ERR_OK);
} while(TRUE); /* End of polish loop */
if(pMasterKey) SPExternalFree(pMasterKey);
pctRet = Pct1GenerateError(pContext,
pCommOutput,
pctRet,
NULL);
SP_RETURN(pctRet | PCT_INT_DROP_CONNECTION);
}
SP_STATUS
Pct1SrvRestart(
PSPContext pContext,
PPct1_Client_Hello pHello,
PSPBuffer pCommOutput)
{
Pct1_Server_Hello Reply;
SPBuffer ErrData;
SP_STATUS pctRet = PCT_INT_ILLEGAL_MSG;
PSessCacheItem pZombie;
DWORD i;
SP_BEGIN("Pct1SrvRestart");
pCommOutput->cbData = 0;
/* validate the buffer configuration */
ErrData.cbData = 0;
ErrData.pvBuffer = NULL;
ErrData.cbBuffer = 0;
pZombie = pContext->RipeZombie;
do {
/* store the challenge in the auth block */
CopyMemory( pContext->pChallenge,
pHello->Challenge,
pHello->cbChallenge );
pContext->cbChallenge = pHello->cbChallenge;
/* Begin to build the server hello */
FillMemory( &Reply, sizeof( Reply ), 0 );
/* Generate new connection id */
GenerateRandomBits( Reply.ConnectionID,
PCT1_SESSION_ID_SIZE );
Reply.cbConnectionID = PCT1_SESSION_ID_SIZE;
CopyMemory( pContext->pConnectionID,
Reply.ConnectionID,
Reply.cbConnectionID );
pContext->cbConnectionID = Reply.cbConnectionID;
Reply.RestartOk = TRUE;
/* We don't pass a server cert back during a restart */
Reply.pCertificate = NULL;
Reply.CertificateLen = 0;
/* setup the context */
for(i=0; i < Pct1NumCipher; i++)
{
if((Pct1CipherRank[i].aiCipher == pZombie->aiCipher) &&
(Pct1CipherRank[i].dwStrength == pZombie->dwStrength))
{
Reply.SrvCipherSpec = Pct1CipherRank[i].Spec;
}
}
for(i=0; i < Pct1NumHash; i++)
{
if(Pct1HashRank[i].aiHash == pZombie->aiHash)
{
Reply.SrvHashSpec = Pct1HashRank[i].Spec;
}
}
Reply.SrvCertSpec = pZombie->pActiveServerCred->pCert->dwCertEncodingType;
Reply.SrvExchSpec = pZombie->SessExchSpec;
// We know what our ciphers are, so init the cipher system
pctRet = ContextInitCiphersFromCache(pContext);
if(PCT_ERR_OK != pctRet)
{
break;
}
// We know what our ciphers are, so init the cipher system
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.
Pct1ActivateSessionKeys(pContext);
/* compute the response */
Reply.ResponseLen = sizeof(Reply.Response);
pctRet = Pct1ComputeResponse(pContext,
pContext->pChallenge,
pContext->cbChallenge,
pContext->pConnectionID,
pContext->cbConnectionID,
pZombie->SessionID,
pZombie->cbSessionID,
Reply.Response,
&Reply.ResponseLen);
if(pctRet != PCT_ERR_OK)
{
break;
}
pctRet = Pct1PackServerHello(&Reply, pCommOutput);
if(PCT_ERR_OK != pctRet)
{
break;
}
pContext->ReadCounter = 1;
pContext->WriteCounter = 1;
SP_RETURN(PCT_ERR_OK);
} while (TRUE);
pctRet = Pct1GenerateError(pContext,
pCommOutput,
pctRet,
&ErrData);
SP_RETURN(pctRet);
}