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