//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1995. // // File: ssl2cli.c // // Contents: // // Classes: // // Functions: // // History: 8-08-95 RichardW Created // //---------------------------------------------------------------------------- #include #include #include #include SP_STATUS WINAPI Ssl2ClientProtocolHandler( PSPContext pContext, PSPBuffer pCommInput, PSPBuffer pCommOutput) { SP_STATUS pctRet = 0; DWORD cMessageType; DWORD dwStateTransition; BOOL fRaw = TRUE; SPBuffer MsgInput; 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]; } MsgInput.pvBuffer = (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; PSessCacheItem pZombie; UNREFERENCED_PARAMETER(pCommInput); pCommOutput->cbData = 0; SP_BEGIN("Ssl2CliHandleServerHello"); pZombie = pContext->RipeZombie; 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;jcCipherSpecs;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) { goto error; } pctRet = SPLoadCertificate(pZombie->fProtocol, pHello->CertificateType, pHello->pCertificate, pHello->cbCertificate, &pZombie->pRemoteCert); if(pctRet != PCT_ERR_OK) { goto error; } 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) { goto error; } // Automatically validate server certificate if appropriate // context flag is set. pctRet = AutoVerifyServerCertificate(pContext); if(pctRet != PCT_ERR_OK) { SP_LOG_RESULT(pctRet); goto error; } // Generate Key Args if(pContext->pCipherInfo->dwBlockSize > 1) { pctRet = GenerateRandomBits(pZombie->pKeyArgs, pContext->pCipherInfo->dwBlockSize); if(!NT_SUCCESS(pctRet)) { goto error; } 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) { goto error; } Key.pbEncryptedKey = SPExternalAlloc(Key.EncryptedKeyLen); if(Key.pbEncryptedKey == NULL) { pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY); goto error; } pctRet = pContext->pKeyExchInfo->System->GenerateClientExchangeValue( pContext, NULL, 0, Key.ClearKey, &Key.ClearKeyLen, Key.pbEncryptedKey, &Key.EncryptedKeyLen); if(PCT_ERR_OK != pctRet) { SPExternalFree(Key.pbEncryptedKey); goto error; } // 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) { goto error; } pContext->WriteCounter++; SP_RETURN(PCT_ERR_OK); error: 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; UNREFERENCED_PARAMETER(pCommInput); pCommOutput->cbData = 0; SP_BEGIN("Ssl2CliHandleServerRestart"); pZombie = pContext->RipeZombie; 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); goto error; } if(!pZombie->hMasterKey) { pctRet = SP_LOG_RESULT(PCT_ERR_ILLEGAL_MESSAGE); goto error; } 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) { goto error; } pctRet = ContextInitCiphers(pContext, TRUE, TRUE); if(PCT_ERR_OK != pctRet) { goto error; } // Make a new set of session keys. pctRet = MakeSessionKeys(pContext, pContext->RipeZombie->hMasterProv, pContext->RipeZombie->hMasterKey); if(PCT_ERR_OK != pctRet) { goto error; } // 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); error: SP_RETURN(pctRet | PCT_INT_DROP_CONNECTION); } SP_STATUS Ssl2GenCliFinished( PSPContext pContext, PSPBuffer pCommOutput) { SP_STATUS pctRet = PCT_ERR_ILLEGAL_MESSAGE; 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) { 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) { 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); }