diff -r 000000000000 -r af10295192d8 networksecurity/tlsprovider/Test/tlstest2/handshakestep.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/networksecurity/tlsprovider/Test/tlstest2/handshakestep.cpp Tue Jan 26 15:23:49 2010 +0200 @@ -0,0 +1,713 @@ +// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// + +/** + @file handshakestep.cpp + @internalTechnology +*/ +#include "handshakestep.h" + +#include + + +_LIT(KWebAddress,"WebAddress"); +_LIT(KWebPage,"WebPage"); +_LIT(KPortNum,"PortNum"); +_LIT(KExpectedFinalCipher, "ExpectedFinalCipherSuit"); +_LIT(KExpectedSetCipherError, "ExpectedSetCipherError"); +_LIT(KExpectedHandshakeError, "ExpectedHandshakeError"); +_LIT8(KSimpleGet, "GET /"); +_LIT8(KGetTail, " HTTP/1.0\r\n\r\n"); +_LIT8(KExpectedPageContent, "Hello world"); +_LIT8(KPSK_IDENTITY, "Client_identity"); +_LIT8(KPSK_KEY, "0123456789"); +_LIT(KNumServerNames, "NumServerNames"); +_LIT(KServerNameBase, "ServerName"); + + + +// CertRequest tester active. +CHandShakeTesterActive::CHandShakeTesterActive( CTestExecuteLogger& aLogger ) : + CActive( EPriorityStandard ), + iLogger( aLogger ) + { + CActiveScheduler::Add( this ); + } + +CHandShakeTesterActive::~CHandShakeTesterActive() + { + + } + +/** This is the core of the test, it handshakes a TLS test server, if succesfull it + requests a web page from the TLS server, if the page was received succesfully it + checks its content. Finally it checks the cipher suite used is what was expected. + @param None + @return TVeredic EPass if all test sequence was completed an succesful. + */ +TVerdict CHandShakeTesterActive::DoSecureConnectionTestL(CHandShakeStep* master) + { + TInt error_Returned; + + iStepPointer = master; + // Negociates secure connection. + error_Returned = HandShakeL(); + if (error_Returned != iStepPointer->iExpectedHandshakeError) + { + ERR_PRINTF3(_L("Handshake failed, Expected error: %D Received Error: %D"), iStepPointer->iExpectedHandshakeError, error_Returned); + return EFail; + } + else if( error_Returned != KErrNone) + { + // An error was received but it was expected, communication can not continue as handshake failed. + ERR_PRINTF2(_L("Test abbreviated as expected handshake error %D was received."), error_Returned); + return EPass; // Terminates test. + } + + // request a web page to server. + if (MakePageRequest() != KErrNone) + { + ERR_PRINTF1(_L("Page request failed")); + return EFail; + } + + // Invokes appropiate methods to receive web page. + GetPageReceivedL(); + + // Verify that the page received has the expected content. + if (VerifyPageReceived() != KErrNone) + { + ERR_PRINTF1(_L("Page received not what expected")); + return EFail; + } + + // debug, outputs page received to file. + // OutputPageToFileL(iStepPointer->iRcvPage); // debug + + // Check that the cipher suite negociated on handshake is the one + // expected by the tester. + if (VerifyFinalCipherUsed() != KErrNone) + { + ERR_PRINTF1(_L("The final cipher Suite used not what expected")); + return EFail; + } + return EPass; + } + +void CHandShakeStep::GetPskL(const HBufC8 */*aPskIdentityHint*/, HBufC8 *&aPskIdentity, HBufC8 *&aPskKey) + { + + aPskIdentity = KPSK_IDENTITY().AllocL(); + aPskKey = KPSK_KEY().AllocL(); + + return; + } + +/** Establishes secure connection to TLS test server by using + CSecureSocket::StartClientHandshake method. + @param None + @return TInt ,return error code returned by status of StartClientHandshake method. + */ +TInt CHandShakeTesterActive::HandShakeL() + { + _LIT(KTLS1,"TLS1.0"); + iStepPointer->iTlsSocket = CSecureSocket::NewL( iStepPointer->iSocket, KTLS1 ); + + // Clears any previous options + iStepPointer->iTlsSocket->FlushSessionCache(); + + // Sets Call back if needed. + iStepPointer->SetPskL(); + + // Sets Null cipher if needed. + iStepPointer->SetNullCipherL(); + + // No client authentication or dialogs for this test. + iStepPointer->iTlsSocket->SetClientCertMode(EClientCertModeIgnore); + iStepPointer->iTlsSocket->SetDialogMode(EDialogModeUnattended); + + // Sets Server Name + if(iStepPointer->iUseServerNames) + { + TPckgC serverNamePkg(iStepPointer->iServerNamesArray); + User::LeaveIfError(iStepPointer->iTlsSocket->SetOpt(KSoServerNameIndication, KSolInetSSL, serverNamePkg)); + } + + // Alternatively dialog mode attended could be used for manual tests. + // iStepPointer->iTlsSocket->SetClientCertMode(EClientCertModeOptional); // debug + // iStepPointer->iTlsSocket->SetDialogMode(EDialogModeAttended); // debug + + // Set cipher suits to be presented in client hello message for handshake. + TInt setCipherSuitesError; + setCipherSuitesError = iStepPointer->iTlsSocket->SetAvailableCipherSuites( *(iStepPointer->iBufCipherSuitesFromIni)); + + if ( setCipherSuitesError != iStepPointer->iExpectedSetSuitesError) + { + ERR_PRINTF3(_L("The SetAvailableCipherSuites method returned error: %d but error expected was: %d"), setCipherSuitesError , iStepPointer->iExpectedSetSuitesError); + User::Leave(KErrGeneral); + } + // start the handshake + iStepPointer->iTlsSocket->StartClientHandshake( iStatus); + iState = ESecureConnected; + SetActive(); + CActiveScheduler::Start(); + + return iStatus.Int(); + } + +/** Requests web page to TLS test server. + @param None + @return TInt ,return error code returned by status of CSecureSocket::Send method. + */ +TInt CHandShakeTesterActive::MakePageRequest() + { + // Create a GET request + iStepPointer->iSndBuffer += KSimpleGet; + iStepPointer->iSndBuffer += iStepPointer->iConnectSettings.iPage; + iStepPointer->iSndBuffer += KGetTail; + + // Send the request + iStepPointer->iTlsSocket->Send( iStepPointer->iSndBuffer, iStatus, iStepPointer->iBytesSent ); + iState = EGetRequestSent; + SetActive(); + CActiveScheduler::Start(); + return iStatus.Int(); + } + +/** Receives web page previously requested to TLS test server. + @param None + @return TInt ,return error code returned by status of CSecureSocket::Recv method. + */ +void CHandShakeTesterActive::GetPageReceivedL() + { + // check for unexpected error. + if(iStatus != KErrEof && iStatus != KErrNone) + { + ERR_PRINTF2(_L("Unexpected error received on get page requested: %d"), iStatus.Int()); + User::Leave(iStatus.Int()); + } + + // First receive invoked (status equals to KErrNone) + if(iStatus == KErrNone) + { + iStepPointer->iRcvBuffer.SetLength( 0 ); + iStepPointer->iTlsSocket->Recv( iStepPointer->iRcvBuffer, iStatus ); + iState = EGetPageReceived; + SetActive(); + CActiveScheduler::Start(); + INFO_PRINTF1(_L("New packet received (for page request)")); + // Append received packet to page received so far. + iStepPointer->iRcvPage.Append(iStepPointer->iRcvBuffer); + } + + // KErrEof or buffer with length zero received. + if(iStatus == KErrEof || iStepPointer->iRcvBuffer.Length() == 0) + { + // Tells the state machine to stop requesting for more packets. + iState = EIdle; + return; + } + + } + +/** Compares the received page content against expected content. + At present the expected content is hardcoded in constant KExpectedPageContent, + if more sofisticated web pages checkings are needed test code may need to be modified to + read expected content from binary file. + @param None + @return KErrNone if webpage is what is expected , otherwise returns KErrGeneral. + */ +TInt CHandShakeTesterActive::VerifyPageReceived() + { + if( iStepPointer->iRcvPage == KExpectedPageContent) + { + return KErrNone; + } + else + { + return KErrGeneral; + } + } + +/** Checks that the cipher suit agreed in the TLS handshake is what the tester + was expecting. + @param None + @return KErrNone if final cipher suite is what is expected , otherwise returns KErrGeneral. + */ +TInt CHandShakeTesterActive::VerifyFinalCipherUsed() + { + TBuf8<2> finalCipherBuffer; + TInt finalCipher=0; + iStepPointer->iTlsSocket->CurrentCipherSuite(finalCipherBuffer); + // Converts two bytes buffer in single integer. + finalCipher += finalCipherBuffer[1]; + finalCipher += (finalCipherBuffer[0] << 8); + + if( iStepPointer->iExpectedFinalCipher == finalCipher) + { + return KErrNone; + } + else + { + ERR_PRINTF3(_L("Used cipher: %d. Expected cipher: %d"), finalCipher, iStepPointer->iExpectedFinalCipher ); + return KErrGeneral; + } + } + +void CHandShakeTesterActive::OutputPageToFileL(const TDesC8& aPageReceived) + { + RFs fs; + User::LeaveIfError (fs.Connect()); + TDriveUnit sysDrive (RFs::GetSystemDrive()); + TBuf<128> rName (sysDrive.Name());; + rName.Append(_L("\\tlstest2\\myresults\\")); + + TInt err = fs.MkDir(rName); + if (err != KErrNone && err != KErrAlreadyExists) + { + // This line will panic if directory does not exist. + User::Leave(err); + } + + RFile file; + CleanupClosePushL(file); + + _LIT(KExtension, ".html"); + rName.Append(iStepPointer->ConfigSection()); + rName.Append(KExtension); + rName.LowerCase(); + User::LeaveIfError(file.Replace(fs, rName, EFileWrite | EFileStream)); + User::LeaveIfError(file.Write(aPageReceived)); + CleanupStack::PopAndDestroy(&file); + fs.Close (); + } + +TInt CHandShakeTesterActive::RunError(TInt aError) + { + iRunError = aError; + CActiveScheduler::Stop(); + return KErrNone; + } + + + +/** RunL method used for all asyncronous requests made by test code. + */ +void CHandShakeTesterActive::RunL() + { + iRunError =KErrNone; + + User::LeaveIfError(iStatus.Int()); + + switch(iState) + { + case EIdle: + CActiveScheduler::Stop(); + break; + + case ESecureConnected: + INFO_PRINTF1(_L(" secure connection made ")); + iState = EIdle; + CActiveScheduler::Stop(); + break; + case EGetRequestSent: + INFO_PRINTF1(_L(" Page requested ")); + iState = EIdle; + CActiveScheduler::Stop(); + break; + case EGetPageReceived: + // Keeps getting packages until KErrEof is received. + GetPageReceivedL(); + // This line will be reach only after all packages received. + CActiveScheduler::Stop(); + break; + default: + { + INFO_PRINTF1(_L("HandShakeTesterActive: State corrupted.")); + User::Leave(KErrCorrupt); + } + } + return; + } + +CHandShakeStep::CHandShakeStep() + { + SetTestStepName(KHandShakeTestStep); + } + +CHandShakeStep::~CHandShakeStep() + { + delete iBufCipherSuitesFromIni; + iSndBuffer.Close(); + iRcvBuffer.Close(); + iRcvPage.Close(); + if (iActiveObjTest) + { + delete iActiveObjTest; + } + } + +/** Test preamble reads all ini values needed for test, these are: + NumCipherSuites: (int) The number of cipher suit to be included in client hello message. + CipherSuiteX: (hex) individual cipher suite to be included in client hello message. + WebAddress: (string) address of the test server could be decimal doted or human readable. + WebPage: (string) Test page to be requested to test server. + PortNum: (int) listening port used by test server. + ExpectedFinalCipherSuit: (hex) The cipher suite that is expected to be adopted on hnadshake. + UsePsk: (Bool) Use to indicate if optional PSK cipher suites are to be included in list + of cipher suites send client hello message. + */ +TVerdict CHandShakeStep::doTestStepPreambleL() + { + // Reads cipher suites to be sent in client hello message. + iBufCipherSuitesFromIni = ReadCipherSuitesL(); + + // Reads web address, this value is mandatory. + TPtrC webAddress; + if(GetStringFromConfig(ConfigSection(), KWebAddress, webAddress)) + { + iConnectSettings.iAddress = webAddress; + } + else + { + ERR_PRINTF1(_L("Failed to read web address value from INI file.")); + User::Leave(KErrNotFound); + } + + // Reads page web to be requested, this value is mandatory + TPtrC page; + if(GetStringFromConfig(ConfigSection(), KWebPage, page)) + { + iConnectSettings.iPage.Copy(page) ; + } + else + { + ERR_PRINTF1(_L("Failed to read page value from INI file.")); + User::Leave(KErrNotFound); + } + + // Reads port, this value is mandatory + TInt portNum; + if(GetIntFromConfig(ConfigSection(), KPortNum, portNum)) + { + iConnectSettings.iPortNum = portNum; + } + else + { + ERR_PRINTF1(_L("Failed to read Port Number from INI file")); + User::Leave(KErrNotFound); + } + + // Reads expected final cipher suits, mandatory value + TInt cipher; + if(GetHexFromConfig(ConfigSection(), KExpectedFinalCipher, cipher)) + { + iExpectedFinalCipher = cipher; + } + else + { + ERR_PRINTF1(_L("Failed to read Expect final cipher value from INI file.")); + User::Leave(KErrNotFound); + } + + // Reads expected set cipher suites error, optional value + TInt expectedSetSuitesError; + if(GetIntFromConfig(ConfigSection(), KExpectedSetCipherError, expectedSetSuitesError)) + { + iExpectedSetSuitesError = expectedSetSuitesError; + } + else + { + iExpectedSetSuitesError = KErrNone; + } + + // Reads expected handshake error, optional value + TInt expectedHandshakeError; + if(GetIntFromConfig(ConfigSection(), KExpectedHandshakeError, expectedHandshakeError)) + { + iExpectedHandshakeError = expectedHandshakeError; + } + else + { + iExpectedHandshakeError = KErrNone; + } + + + // Initialises send and receive buffers + iSndBuffer.CreateL(KSendBufferSize); + iRcvBuffer.CreateL(KReceiveBufferSize); + iRcvPage.CreateL(KReceiveBufferSize * 8); // Assumes 8 packages will be enough + + // Reads PSK options (optional) + TBool option; + if(GetBoolFromConfig(ConfigSection(), KUsePsk, option)) + { + iUsePsk = option; + } + else + { + iUsePsk = EFalse; + } + + // Reads Null cipher options (optional) + TBool nullCipher; + if(GetBoolFromConfig(ConfigSection(), KUseNullCipher, nullCipher)) + { + iUseNullCipher = nullCipher; + } + else + { + iUseNullCipher = EFalse; + } + + // Read server names from INI file. + iServerNamesArray = ReadServerNamesL(); + + return EPass; + } + +TVerdict CHandShakeStep::doTestStepL() + { + doTestL(); + return TestStepResult(); + } + + +TVerdict CHandShakeStep::doTestL() + { + TVerdict testResult; + SetTestStepResult(EFail); + + // Instantiates scheduler + iSched=new(ELeave) CActiveScheduler; + CleanupStack::PushL(iSched); + CActiveScheduler::Install(iSched); + + // Initialises and connects the RSocket + ConnectL(); + + // Creates active tester object which will deal will CSecureSocket instance + iActiveObjTest = new (ELeave) CHandShakeTesterActive(Logger()); + CleanupStack::PushL(iActiveObjTest); + + // Creates secure socket and start client handshake. + testResult = iActiveObjTest->DoSecureConnectionTestL(this); + + // Deletes and closes all production code been tested. + CloseConnection(); + + CleanupStack::PopAndDestroy(iActiveObjTest); + iActiveObjTest = NULL; + CleanupStack::PopAndDestroy(iSched); + iSched=NULL; + + SetTestStepResult(testResult); + return TestStepResult(); + } + + +void CHandShakeStep::SetPskL() + { + + if(iUsePsk) + { + TPckgBuf pskConfigPkg; + pskConfigPkg() = this; + User::LeaveIfError(iTlsSocket->SetOpt(KSoPskConfig, KSolInetSSL, pskConfigPkg)); + } + + } + +void CHandShakeStep::SetNullCipherL() + { + + if(iUseNullCipher) + { + User::LeaveIfError(iTlsSocket->SetOpt(KSoEnableNullCiphers, KSolInetSSL, ETrue)); + } + + } + + +void CHandShakeStep::CloseConnection() + { + + // Clean up + iSocket.CancelAll(); + if(iTlsSocket) + { + iTlsSocket->CancelAll(); + iTlsSocket->Close(); + delete iTlsSocket; + iTlsSocket =0; + } + + iSocket.Close(); + + } + + +void CHandShakeStep::ConnectL() + { + + TRequestStatus rs; + RHostResolver hostResolver; + + // Connect the socket server + User::LeaveIfError( iSocketServ.Connect()); + +// User::LeaveIfError( iSocketServ.Connect()); // debug + + // Interpret server address + if (iInetAddr.Input(iConnectSettings.iAddress) != KErrNone) + // Success if already in dotted-decimal format + { + // Connect to a host resolver (for DNS resolution) - happens sychronously + TInt retVal = hostResolver.Open( iSocketServ, KAfInet, KProtocolInetTcp); + + if(retVal != KErrNone) + { + ERR_PRINTF2(_L("Failed to open host resolver, Error:%d"),retVal); + User::Leave(KErrGeneral); + } + + CleanupClosePushL(hostResolver); + + // Try to resolve symbolic name + TNameEntry nameEntry; + + retVal = hostResolver.GetByName( iConnectSettings.iAddress, nameEntry ); + if(retVal != KErrNone) + { + ERR_PRINTF2(_L("Failed to get name from host resolver, Error:%d"),retVal); + User::Leave(KErrGeneral); + } + + TSockAddr sockAddr = nameEntry().iAddr; + iInetAddr = iInetAddr.Cast( sockAddr ); + + CleanupStack::PopAndDestroy(&hostResolver); + } + // Store other connection parameters + iInetAddr.SetPort( iConnectSettings.iPortNum ); + + // Open a TCP socket + User::LeaveIfError( iSocket.Open( iSocketServ, KAfInet, KSockStream, KProtocolInetTcp ) ); + + // Connect to the server, asynchronously + iSocket.Connect( iInetAddr, rs ); + + User::WaitForRequest( rs ); + // Print status + if(rs.Int() != KErrNone) + { + INFO_PRINTF4(_L("Connection failed to %S port:%d Error: %d"), &iConnectSettings.iAddress, iConnectSettings.iPortNum, rs.Int() ); + User::Leave(KErrCouldNotConnect); + } + else + { + INFO_PRINTF3(_L("Connected to %S port:%d"), &iConnectSettings.iAddress, iConnectSettings.iPortNum ); + } + } + +/** Read list of cipher to be included in client hello message. +@param None +@return HBufC8 list of cipher suites +*/ + +HBufC8* CHandShakeStep::ReadCipherSuitesL() + { + + TInt numCipherSuites; + TName fCipherSuite; + TInt iniCipherSuite; + HBufC8* allSuites = NULL; + + if(GetIntFromConfig(ConfigSection(),KNumCipherSuites,numCipherSuites)) + { + // Each cipher suite uses 2 bytes + allSuites = HBufC8::NewMaxL( numCipherSuites * 2); + TPtr8 ptr = allSuites->Des(); + + for(TInt index = 1;index <= numCipherSuites ; ++index) + { + fCipherSuite.Format(_L("%S%d"), &KCipherSuiteBase, index); + if(GetHexFromConfig(ConfigSection(), fCipherSuite,iniCipherSuite)) + { + // Two bytes for each cipher suite + ptr[(index - 1) * 2] = ( ( iniCipherSuite & 0xFF00) >> 8); + ptr[( (index - 1) * 2 ) + 1] = iniCipherSuite & 0xFF; + } + } + } + + return allSuites; + } + +/** Read list of server to be included in client hello message form INI file. +@param None +@return CDesC8ArrayFlat list of server names +*/ + + +CDesC8ArrayFlat * CHandShakeStep::ReadServerNamesL() + { + + TInt numServerNames; + TName fServerName; + TPtrC serverName; + CDesC8ArrayFlat* allServerNames = NULL; + + if(GetIntFromConfig(ConfigSection(),KNumServerNames,numServerNames)) + { + + if( numServerNames >0) + { + iUseServerNames = ETrue; + + allServerNames = new(ELeave) CDesC8ArrayFlat(1); + CleanupStack::PushL(allServerNames); + + // Reads each server name and appends it to array. + for(TInt index = 1;index <= numServerNames ; ++index) + { + fServerName.Format(_L("%S%d"), &KServerNameBase, index); + if(GetStringFromConfig(ConfigSection(), fServerName, serverName)) + { + HBufC8 *serverName8bited = HBufC8::NewMaxLC(serverName.Length()); + serverName8bited->Des().Copy(serverName); + allServerNames->AppendL(serverName8bited->Des()); + CleanupStack::PopAndDestroy(serverName8bited); + } + else + { + INFO_PRINTF2(_L("Could not found ServerName %d from INI file"), index ); + User::Leave(KErrGeneral); + } + } + CleanupStack::Pop(allServerNames); + } + + } + else + { + iUseServerNames = EFalse; + } + + return allServerNames; + } + + +