diff -r 000000000000 -r bde4ae8d615e os/security/contentmgmt/cafstreamingsupport/source/ipsec/ipseckeystreamsink.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/os/security/contentmgmt/cafstreamingsupport/source/ipsec/ipseckeystreamsink.cpp Fri Jun 15 03:10:57 2012 +0200 @@ -0,0 +1,366 @@ +// Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of the License "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: +// + +#include "ipseckeystreamsink.h" +#include +#include +#include +#include "scaflog.h" + +using namespace StreamAccess; + +// The last two rules: (inbound = {}, outbound = {}) are catch-all rules - without them, all packets +// which do not match the policy will get rejected +_LIT8( KPolicyFormat, +"SECURITY_FILE_VERSION: 3\r\n[INFO]\r\n\ +Test CAF IpSec Integration Policy\r\n\ +[POLICY]\r\n\ +sa caf_sas = {\r\n\ +esp\r\n\ +encrypt_alg %d\r\n\ +auth_alg %d\r\n\ +src_specific\r\n\ +local_port_specific\r\n\ +remote_port_specific\r\n\ +}\r\n\ +inbound local %S 255.255.255.255 local_port %d remote_port %d = { caf_sas() }\r\n\ +inbound = {}\r\n\ +outbound = {}\r\n" ); + +// The number is taken as the maximal recommendation from OMA DRM BCAST standard (section 9.1, IPSec management) +const TUint KDefaultMaxSpiNumber = 3; + +CIpSecKeyStreamSink* CIpSecKeyStreamSink::NewLC(const TInetAddr& aSrcAddr, const TInetAddr& aDstAddr) + { + CIpSecKeyStreamSink* self = new (ELeave) CIpSecKeyStreamSink(aSrcAddr, aDstAddr); + CleanupStack::PushL(self); + self->ConstructL(); + return self; + } + +CIpSecKeyStreamSink::CIpSecKeyStreamSink(const TInetAddr& aSrcAddr, const TInetAddr& aDstAddr) : + iSourceAddr(aSrcAddr), + iDestinationAddr(aDstAddr), + iPolicySet(EFalse), + iMaxSpiNumber(KDefaultMaxSpiNumber) + { + } + +CIpSecKeyStreamSink::~CIpSecKeyStreamSink() + { + // Remove all SA-s + TInt submittedSaCount = iSubmittedSpiList.Count(); + for (TInt i = 0; i < submittedSaCount; ++i) + { + TRAP_IGNORE(RemoveSaL(iSubmittedSpiList[i])); + } + + if (iPolicySet) + { + TRequestStatus status; + iPolicyServer.UnloadPolicy( iPolicyHandle(), status ); + User::WaitForRequest(status); + // Impossible to handle the error well in destructor - ignore the status code + } + + iSADB.Close(); + iSocketServ.Close(); + + iPolicyServer.Close(); + iSubmittedSpiList.Close(); + } + +void CIpSecKeyStreamSink::ConstructL() + { + DEBUG_PRINTF(_L("Constructing an IPSec key stream sink.")); + User::LeaveIfError(iSocketServ.Connect()); + User::LeaveIfError(iSADB.Open(iSocketServ)); + User::LeaveIfError(iPolicyServer.Connect()); + DEBUG_PRINTF(_L("Constructed an IPSec key stream sink.")); + } + +CKeyStreamSink* CIpSecKeyStreamSink::CloneLC() const + { + CIpSecKeyStreamSink *ret = CIpSecKeyStreamSink::NewLC(iSourceAddr, iDestinationAddr); + ret->iEncAlg = this->iEncAlg; + ret->iAuthAlg = this->iAuthAlg; + return ret; + } + +static TUint32 ConvertToNetworkOrder(TUint32 aNum) + { + const TInt KMaxTUint32CStringLen = 11; + TUint8 temp[ KMaxTUint32CStringLen ]; + LittleEndian::Put32( temp, aNum ); + return BigEndian::Get32( temp ); + } + +TBool CIpSecKeyStreamSink::CompareReceivedMessageExtensionsL(TPfkeyRecvMsg &aReceivedReply, TUint32 aSpi) const + { + TBool spiVerified(EFalse), destAddrVerified(EFalse); + + TPfkeyAnyExt ext; + + // We verify that both the SPI matches and the destination address matches - this should exclude + // replies to unrelated messages + while ( !spiVerified || !destAddrVerified ) + { + // Both extensions should be found in the reply + User::LeaveIfError(aReceivedReply.NextExtension(ext)); + TInt extType = ext.ExtType(); + if ( extType == SADB_EXT_ADDRESS_DST ) + { + const TInetAddr* addr = reinterpret_cast(ext.Ptr() + sizeof(struct sadb_address)); + if (*addr == iDestinationAddr) + destAddrVerified = ETrue; + else + return EFalse; + } + else if (extType == SADB_EXT_SA) + { + const sadb_sa* saExt = reinterpret_cast(ext.Ptr()); + if (saExt->sadb_sa_spi == aSpi) + spiVerified = ETrue; + else + return EFalse; + } + } + return ETrue; + } + +void CIpSecKeyStreamSink::SynchronousSendAndVerifyMessageL(TPfkeySendMsg& aMessage, TInt aMessageType, TUint32 aSpi) + { + // Wait for the message to be sent + TRequestStatus status; + iSADB.FinalizeAndSend(aMessage, status); + User::WaitForRequest(status); + User::LeaveIfError(status.Int()); + + // Receive a reply - since SADB sends replies to _all_ sockets, we need to filter out replies + // which do not correspond to our own request + for(;;) + { + TPfkeyRecvMsg receivedReply; + iSADB.ReadRequest(receivedReply, status); + User::WaitForRequest(status); + User::LeaveIfError(status.Int()); + + // Verify that the message is a reply to the one sent by us + sadb_msg &msgHeader = receivedReply.MsgHdr(); + if (msgHeader.sadb_msg_pid != RProcess().Id()) + continue; + if (msgHeader.sadb_msg_seq != iSequenceNumber) + continue; + // Do additional validation by checking whether the destination address and the SPI are the same as ours + TBool isResponseToRequest(ETrue); + switch (aMessageType) + { + case SADB_ADD: + case SADB_DELETE: + isResponseToRequest = CompareReceivedMessageExtensionsL(receivedReply, aSpi); + break; + default: + ASSERT(0); // Unexpected state + } + if (!isResponseToRequest) + continue; + // If the message types does not match, then the problem is internal in IPSec - it should not answer with a different message type + if (msgHeader.sadb_msg_type != aMessageType) + User::Leave(KErrArgument); + if (msgHeader.sadb_msg_errno != 0) + { + // Mimic the logic in IPSec error handling (see the Update function in key_msg.cpp) + TUint16 reservedField = (TUint16)msgHeader.sadb_msg_reserved << 8; + TUint16 errnoField = msgHeader.sadb_msg_errno; + User::Leave(-(reservedField + errnoField)); + } + break; + } + } + +void CIpSecKeyStreamSink::AddAssociationL(TPfkeySendMsg& aMessage, TUint32 aSpi) + { + SynchronousSendAndVerifyMessageL(aMessage, SADB_ADD, aSpi); + + // Take care to delete old SA-s, if needed + iSubmittedSpiList.AppendL(aSpi); + if (iSubmittedSpiList.Count() > iMaxSpiNumber) + { + RemoveSaL(iSubmittedSpiList[0]); + iSubmittedSpiList.Remove(0); + } + } + +void CIpSecKeyStreamSink::ProcessNewKeyAssociationL(const CKeyAssociation& aKeyAssociation) + { + if (!iPolicySet) + { + SetPolicyL(); + } + // No official RTTI support, using static_cast - no validation that it is indeed an IPSec association + const CIpSecKeyAssociation* ipsecKeyAssociation = static_cast(&aKeyAssociation); + + DEBUG_PRINTF2(_L("IPSec key stream sink - processing new association with SPI %d."), ipsecKeyAssociation->GetSpiL()); + + // We use ESP, since there is very low probability that AH will be used - it does not provide confidentiality + TPfkeySendMsg sendMessage(SADB_ADD, SADB_SATYPE_ESP, ++iSequenceNumber, RProcess().Id()); + TUint32 bigEndianSpi(ConvertToNetworkOrder(ipsecKeyAssociation->GetSpiL())); + sendMessage.Add( Int2Type(), bigEndianSpi, iAuthAlg, iEncAlg); + const HBufC8 *encryptionKey(ipsecKeyAssociation->GetEncryptionKeyL()); + if(!encryptionKey) + User::Leave(KErrArgument); + + sendMessage.Add( Int2Type(), *encryptionKey, encryptionKey->Length() * 8); + if (iAuthAlg) // Authentication is optional - use it only if algorithm has been set + { + const HBufC8 *authenticationKey(ipsecKeyAssociation->GetAuthenticationKeyL()); + if (!authenticationKey) + User::Leave(KErrArgument); + sendMessage.Add( Int2Type(), *authenticationKey, authenticationKey->Length() * 8); + } + sendMessage.Add( Int2Type(), iSourceAddr); + sendMessage.Add( Int2Type(), iDestinationAddr); + + TRAPD(err, AddAssociationL(sendMessage, bigEndianSpi)); + // If something went wrong, try to remove the SA, to keep the SADB in consistent state. + // We only do our best effort, since the error might be global to the device, or it may have occured before we've added the SA + if (err != KErrNone) + { + TRAP_IGNORE(RemoveSaL(bigEndianSpi)); + TInt submittedSpiCount = iSubmittedSpiList.Count(); + if (submittedSpiCount > 0 && iSubmittedSpiList[submittedSpiCount - 1] == bigEndianSpi) + iSubmittedSpiList.Remove(submittedSpiCount - 1); + + User::Leave(err); + } + DEBUG_PRINTF2(_L("IPSec key stream sink - processed new association with SPI %d."), ipsecKeyAssociation->GetSpiL()); + } + +void CIpSecKeyStreamSink::RemoveSaL(TUint32 aSpi) + { + TPfkeySendMsg sendMessage(SADB_DELETE, SADB_SATYPE_ESP, ++iSequenceNumber, RProcess().Id()); + sendMessage.Add( Int2Type(), aSpi, iAuthAlg, iEncAlg); + sendMessage.Add( Int2Type(), iDestinationAddr); + SynchronousSendAndVerifyMessageL(sendMessage, SADB_DELETE, aSpi); + } + +void CIpSecKeyStreamSink::VerifyAssociationsNotSentL() const + { + if (iSubmittedSpiList.Count()) + { + User::Leave(KErrNotSupported); // It is forbidden to change the algorithm once associations have been processed + } + } + +const TUint32 SADB_AALG_AESCBC = 12; // temporarily here until IPSec supports AES constants + +void CIpSecKeyStreamSink::SetEncryptionAlgorithmL(const TEncryptionAlgorithm& aEncryptionAlgorithm) + { + VerifyAssociationsNotSentL(); + switch (aEncryptionAlgorithm) + { + case EAES_128_CBC: iEncAlg = SADB_AALG_AESCBC; break; + case ENoEncryption: + case EAES_128_CTR: + default: User::Leave(KErrNotSupported); + }; + } + +void CIpSecKeyStreamSink::SetAuthenticationAlgorithmL(const TAuthenticationAlgorithm& aAuthenticationAlgorithm) + { + VerifyAssociationsNotSentL(); + switch (aAuthenticationAlgorithm) + { + case EHMAC_SHA1: iAuthAlg = SADB_AALG_SHA1HMAC; break; + case ENoAuthentication: iAuthAlg = 0; break; + default: User::Leave(KErrNotSupported); + }; + } + +const TUint KInet6AddrMaxBufferSize = 40; + +void CIpSecKeyStreamSink::SetPolicyL() + { + DEBUG_PRINTF(_L("IPSec key stream sink - setting policy.")); + ASSERT(!iPolicySet); + + TBuf destAddrStr; + iDestinationAddr.Output(destAddrStr); + + // Convert 16-bit to 8-bit. For some strange reason, the string for IP address is returned in 16-bit. + TBuf8 destAddrStr8bit; + destAddrStr8bit.Copy(destAddrStr); + + HBufC8 *policyData = HBufC8::NewLC( KPolicyFormat().Length() + 256); // Allow size for port and IP spec. + TPtr8 policyDataPtr(policyData->Des()); + policyDataPtr.AppendFormat(KPolicyFormat, iEncAlg, iAuthAlg, &destAddrStr8bit, iDestinationAddr.Port(), + iSourceAddr.Port()); + + // Load the server-side policy to protect all packets to a specific port + TRequestStatus status; + iPolicyServer.LoadPolicy( *policyData, iPolicyHandle, status ); + User::WaitForRequest(status); + User::LeaveIfError(status.Int()); + + iPolicyServer.ActivatePolicy( iPolicyHandle(), status ); + User::WaitForRequest(status); + User::LeaveIfError(status.Int()); + + CleanupStack::PopAndDestroy(policyData); + iPolicySet = ETrue; + DEBUG_PRINTF(_L("IPSec key stream sink - policy set.")); + } + +static void ReadIpAddrFromStreamL(RReadStream& aStream, TInetAddr& addr) + { + TBuf addrStr; + TUint8 addrStrLen = aStream.ReadUint8L(); + aStream.ReadL(addrStr, addrStrLen); + User::LeaveIfError(addr.Input(addrStr)); + TInt port = aStream.ReadInt32L(); + addr.SetPort(port); + } + +static void WriteIpAddrToStreamL(RWriteStream& aStream, const TInetAddr& addr) + { + TBuf addrStr; + addr.Output(addrStr); + aStream.WriteUint8L(addrStr.Length()); + aStream.WriteL(addrStr); + aStream.WriteInt32L(addr.Port()); + } + +void CIpSecKeyStreamSink::DoExternalizeL(RWriteStream& aStream) const + { + aStream.WriteUint16L(CKeyStreamSink::EIpSecSinkId); + WriteIpAddrToStreamL(aStream, iDestinationAddr); + WriteIpAddrToStreamL(aStream, iSourceAddr); + aStream.WriteUint16L(iEncAlg); + aStream.WriteUint16L(iAuthAlg); + } + +CIpSecKeyStreamSink* CIpSecKeyStreamSink::NewLC(RReadStream& aReadStream) + { + TInetAddr destAddr; + ReadIpAddrFromStreamL(aReadStream, destAddr); + TInetAddr srcAddr; + ReadIpAddrFromStreamL(aReadStream, srcAddr); + CIpSecKeyStreamSink* self = new (ELeave) CIpSecKeyStreamSink(srcAddr, destAddr); + CleanupStack::PushL(self); + self->ConstructL(); + self->iEncAlg = aReadStream.ReadUint16L(); + self->iAuthAlg = aReadStream.ReadUint16L(); + return self; + }