contentmgmt/cafstreamingsupport/source/ipsec/ipseckeystreamsink.cpp
changeset 56 c11c717470d0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contentmgmt/cafstreamingsupport/source/ipsec/ipseckeystreamsink.cpp	Fri Apr 16 16:52:34 2010 +0300
@@ -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 <caf/streaming/keyassociation.h>
+#include <networking/pfkeyv2.h>
+#include <s32mem.h>
+#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<const TInetAddr *>(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<const sadb_sa *>(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<const CIpSecKeyAssociation *>(&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<SADB_EXT_SA>(), bigEndianSpi, iAuthAlg, iEncAlg); 
+	const HBufC8 *encryptionKey(ipsecKeyAssociation->GetEncryptionKeyL());
+	if(!encryptionKey)
+		User::Leave(KErrArgument);
+	
+	sendMessage.Add( Int2Type<SADB_EXT_KEY_ENCRYPT>(), *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<SADB_EXT_KEY_AUTH>(), *authenticationKey, authenticationKey->Length() * 8);			
+		}
+	sendMessage.Add( Int2Type<SADB_EXT_ADDRESS_SRC>(), iSourceAddr);
+	sendMessage.Add( Int2Type<SADB_EXT_ADDRESS_DST>(), 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<SADB_EXT_SA>(), aSpi, iAuthAlg, iEncAlg); 
+	sendMessage.Add( Int2Type<SADB_EXT_ADDRESS_DST>(), 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<KInet6AddrMaxBufferSize> 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<KInet6AddrMaxBufferSize> 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<KInet6AddrMaxBufferSize> 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<KInet6AddrMaxBufferSize> 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;
+	}