/** @file
The Common operations used by IKE Exchange Process.
(C) Copyright 2015 Hewlett-Packard Development Company, L.P.
Copyright (c) 2010 - 2014, Intel Corporation. All rights reserved.
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php.
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include "Utility.h"
#include "IpSecDebug.h"
#include "IkeService.h"
#include "IpSecConfigImpl.h"
UINT16 mIkev2EncryptAlgorithmList[IKEV2_SUPPORT_ENCRYPT_ALGORITHM_NUM] = {
IKEV2_TRANSFORM_ID_ENCR_3DES,
IKEV2_TRANSFORM_ID_ENCR_AES_CBC,
};
UINT16 mIkev2PrfAlgorithmList[IKEV2_SUPPORT_PRF_ALGORITHM_NUM] = {
IKEV2_TRANSFORM_ID_PRF_HMAC_SHA1,
};
UINT16 mIkev2DhGroupAlgorithmList[IKEV2_SUPPORT_DH_ALGORITHM_NUM] = {
IKEV2_TRANSFORM_ID_DH_1024MODP,
IKEV2_TRANSFORM_ID_DH_2048MODP,
};
UINT16 mIkev2AuthAlgorithmList[IKEV2_SUPPORT_AUTH_ALGORITHM_NUM] = {
IKEV2_TRANSFORM_ID_AUTH_HMAC_SHA1_96,
};
/**
Allocate buffer for IKEV2_SA_SESSION and initialize it.
@param[in] Private Pointer to IPSEC_PRIVATE_DATA.
@param[in] UdpService Pointer to IKE_UDP_SERVICE related to this IKE SA Session.
@return Pointer to IKEV2_SA_SESSION or NULL.
**/
IKEV2_SA_SESSION *
Ikev2SaSessionAlloc (
IN IPSEC_PRIVATE_DATA *Private,
IN IKE_UDP_SERVICE *UdpService
)
{
EFI_STATUS Status;
IKEV2_SESSION_COMMON *SessionCommon;
IKEV2_SA_SESSION *IkeSaSession;
IkeSaSession = AllocateZeroPool (sizeof (IKEV2_SA_SESSION));
ASSERT (IkeSaSession != NULL);
//
// Initialize the fields of IkeSaSession and its SessionCommon.
//
IkeSaSession->NCookie = NULL;
IkeSaSession->Signature = IKEV2_SA_SESSION_SIGNATURE;
IkeSaSession->InitiatorCookie = IkeGenerateCookie ();
IkeSaSession->ResponderCookie = 0;
//
// BUGBUG: Message ID starts from 2 is to match the OpenSwan requirement, but it
// might not match the IPv6 Logo. In its test specification, it mentions that
// the Message ID should start from zero after the IKE_SA_INIT exchange.
//
IkeSaSession->MessageId = 2;
SessionCommon = &IkeSaSession->SessionCommon;
SessionCommon->UdpService = UdpService;
SessionCommon->Private = Private;
SessionCommon->IkeSessionType = IkeSessionTypeIkeSa;
SessionCommon->IkeVer = 2;
SessionCommon->AfterEncodePayload = NULL;
SessionCommon->BeforeDecodePayload = NULL;
//
// Create a resend notfiy event for retry.
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
Ikev2ResendNotify,
SessionCommon,
&SessionCommon->TimeoutEvent
);
if (EFI_ERROR (Status)) {
FreePool (IkeSaSession);
return NULL;
}
//
// Initialize the lists in IkeSaSession.
//
InitializeListHead (&IkeSaSession->ChildSaSessionList);
InitializeListHead (&IkeSaSession->ChildSaEstablishSessionList);
InitializeListHead (&IkeSaSession->InfoMIDList);
InitializeListHead (&IkeSaSession->DeleteSaList);
return IkeSaSession;
}
/**
Register the established IKEv2 SA into Private->Ikev2EstablishedList. If there is
IKEV2_SA_SESSION with same remote peer IP, remove the old one then register the
new one.
@param[in] IkeSaSession Pointer to IKEV2_SA_SESSION to be registered.
@param[in] Private Pointer to IPSEC_PRAVATE_DATA.
**/
VOID
Ikev2SaSessionReg (
IN IKEV2_SA_SESSION *IkeSaSession,
IN IPSEC_PRIVATE_DATA *Private
)
{
IKEV2_SESSION_COMMON *SessionCommon;
IKEV2_SA_SESSION *OldIkeSaSession;
EFI_STATUS Status;
UINT64 Lifetime;
//
// Keep IKE SA exclusive to remote ip address.
//
SessionCommon = &IkeSaSession->SessionCommon;
OldIkeSaSession = Ikev2SaSessionRemove (&Private->Ikev2EstablishedList, &SessionCommon->RemotePeerIp);
if (OldIkeSaSession != NULL) {
//
// TODO: It should delete all child SAs if rekey the IKE SA.
//
Ikev2SaSessionFree (OldIkeSaSession);
}
//
// Cleanup the fields of SessionCommon for processing.
//
Ikev2SessionCommonRefresh (SessionCommon);
//
// Insert the ready IKE SA session into established list.
//
Ikev2SaSessionInsert (&Private->Ikev2EstablishedList, IkeSaSession, &SessionCommon->RemotePeerIp);
//
// Create a notfiy event for the IKE SA life time counting.
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
Ikev2LifetimeNotify,
SessionCommon,
&SessionCommon->TimeoutEvent
);
if (EFI_ERROR(Status)){
//
// If TimerEvent creation failed, the SA will be alive untill user disable it or
// receiving a Delete Payload from peer.
//
return;
}
//
// Start to count the lifetime of the IKE SA.
//
if (IkeSaSession->Spd->Data->ProcessingPolicy->SaLifetime.HardLifetime == 0) {
Lifetime = IKE_SA_DEFAULT_LIFETIME;
} else {
Lifetime = IkeSaSession->Spd->Data->ProcessingPolicy->SaLifetime.HardLifetime;
}
Status = gBS->SetTimer (
SessionCommon->TimeoutEvent,
TimerRelative,
MultU64x32(Lifetime, 10000000) // ms->100ns
);
if (EFI_ERROR(Status)){
//
// If SetTimer failed, the SA will be alive untill user disable it or
// receiving a Delete Payload from peer.
//
return ;
}
DEBUG ((
DEBUG_INFO,
"\n------IkeSa established and start to count down %d seconds lifetime\n",
Lifetime
));
return ;
}
/**
Find a IKEV2_SA_SESSION by the remote peer IP.
@param[in] SaSessionList SaSession List to be searched.
@param[in] RemotePeerIp Pointer to specified IP address.
@return Pointer to IKEV2_SA_SESSION if find one or NULL.
**/
IKEV2_SA_SESSION *
Ikev2SaSessionLookup (
IN LIST_ENTRY *SaSessionList,
IN EFI_IP_ADDRESS *RemotePeerIp
)
{
LIST_ENTRY *Entry;
IKEV2_SA_SESSION *IkeSaSession;
NET_LIST_FOR_EACH (Entry, SaSessionList) {
IkeSaSession = IKEV2_SA_SESSION_BY_SESSION (Entry);
if (CompareMem (
&IkeSaSession->SessionCommon.RemotePeerIp,
RemotePeerIp,
sizeof (EFI_IP_ADDRESS)
) == 0) {
return IkeSaSession;
}
}
return NULL;
}
/**
Insert a IKE_SA_SESSION into IkeSaSession list. The IkeSaSession list is either
Private->Ikev2SaSession list or Private->Ikev2EstablishedList list.
@param[in] SaSessionList Pointer to list to be inserted into.
@param[in] IkeSaSession Pointer to IKEV2_SA_SESSION to be inserted.
@param[in] RemotePeerIp Pointer to EFI_IP_ADDRESSS to indicate the
unique IKEV2_SA_SESSION.
**/
VOID
Ikev2SaSessionInsert (
IN LIST_ENTRY *SaSessionList,
IN IKEV2_SA_SESSION *IkeSaSession,
IN EFI_IP_ADDRESS *RemotePeerIp
)
{
Ikev2SaSessionRemove (SaSessionList, RemotePeerIp);
InsertTailList (SaSessionList, &IkeSaSession->BySessionTable);
}
/**
Remove the SA Session by Remote Peer IP.
@param[in] SaSessionList Pointer to list to be searched.
@param[in] RemotePeerIp Pointer to EFI_IP_ADDRESS to use for SA Session search.
@retval Pointer to IKEV2_SA_SESSION with the specified remote IP address or NULL.
**/
IKEV2_SA_SESSION *
Ikev2SaSessionRemove (
IN LIST_ENTRY *SaSessionList,
IN EFI_IP_ADDRESS *RemotePeerIp
)
{
LIST_ENTRY *Entry;
IKEV2_SA_SESSION *IkeSaSession;
NET_LIST_FOR_EACH (Entry, SaSessionList) {
IkeSaSession = IKEV2_SA_SESSION_BY_SESSION (Entry);
if (CompareMem (
&IkeSaSession->SessionCommon.RemotePeerIp,
RemotePeerIp,
sizeof (EFI_IP_ADDRESS)
) == 0) {
RemoveEntryList (Entry);
return IkeSaSession;
}
}
return NULL;
}
/**
Marking a SA session as on deleting.
@param[in] IkeSaSession Pointer to IKEV2_SA_SESSION.
@retval EFI_SUCCESS Find the related SA session and marked it.
**/
EFI_STATUS
Ikev2SaSessionOnDeleting (
IN IKEV2_SA_SESSION *IkeSaSession
)
{
return EFI_SUCCESS;
}
/**
Free specified Seession Common. The session common would belong to a IKE SA or
a Child SA.
@param[in] SessionCommon Pointer to a Session Common.
**/
VOID
Ikev2SaSessionCommonFree (
IN IKEV2_SESSION_COMMON *SessionCommon
)
{
ASSERT (SessionCommon != NULL);
if (SessionCommon->LastSentPacket != NULL) {
IkePacketFree (SessionCommon->LastSentPacket);
}
if (SessionCommon->SaParams != NULL) {
FreePool (SessionCommon->SaParams);
}
if (SessionCommon->TimeoutEvent != NULL) {
gBS->CloseEvent (SessionCommon->TimeoutEvent);
}
}
/**
After IKE/Child SA is estiblished, close the time event and free sent packet.
@param[in] SessionCommon Pointer to a Session Common.
**/
VOID
Ikev2SessionCommonRefresh (
IN IKEV2_SESSION_COMMON *SessionCommon
)
{
ASSERT (SessionCommon != NULL);
gBS->CloseEvent (SessionCommon->TimeoutEvent);
SessionCommon->TimeoutEvent = NULL;
SessionCommon->TimeoutInterval = 0;
SessionCommon->RetryCount = 0;
if (SessionCommon->LastSentPacket != NULL) {
IkePacketFree (SessionCommon->LastSentPacket);
SessionCommon->LastSentPacket = NULL;
}
return ;
}
/**
Free specified IKEV2 SA Session.
@param[in] IkeSaSession Pointer to IKEV2_SA_SESSION to be freed.
**/
VOID
Ikev2SaSessionFree (
IN IKEV2_SA_SESSION *IkeSaSession
)
{
IKEV2_SESSION_KEYS *IkeKeys;
LIST_ENTRY *Entry;
IKEV2_CHILD_SA_SESSION *ChildSa;
IKEV2_DH_BUFFER *DhBuffer;
ASSERT (IkeSaSession != NULL);
//
// Delete Common Session
//
Ikev2SaSessionCommonFree (&IkeSaSession->SessionCommon);
//
// Delete ChildSaEstablish List and SAD
//
for (Entry = IkeSaSession->ChildSaEstablishSessionList.ForwardLink;
Entry != &IkeSaSession->ChildSaEstablishSessionList;
) {
ChildSa = IKEV2_CHILD_SA_SESSION_BY_IKE_SA (Entry);
Entry = Entry->ForwardLink;
Ikev2ChildSaSilentDelete (ChildSa->IkeSaSession, ChildSa->LocalPeerSpi);
}
//
// Delete ChildSaSessionList
//
for ( Entry = IkeSaSession->ChildSaSessionList.ForwardLink;
Entry != &IkeSaSession->ChildSaSessionList;
){
ChildSa = IKEV2_CHILD_SA_SESSION_BY_IKE_SA (Entry);
Entry = Entry->ForwardLink;
RemoveEntryList (Entry->BackLink);
Ikev2ChildSaSessionFree (ChildSa);
}
//
// Delete DhBuffer and Keys
//
if (IkeSaSession->IkeKeys != NULL) {
IkeKeys = IkeSaSession->IkeKeys;
DhBuffer = IkeKeys->DhBuffer;
//
// Delete DhBuffer
//
Ikev2DhBufferFree (DhBuffer);
//
// Delete Keys
//
if (IkeKeys->SkAiKey != NULL) {
FreePool (IkeKeys->SkAiKey);
}
if (IkeKeys->SkArKey != NULL) {
FreePool (IkeKeys->SkArKey);
}
if (IkeKeys->SkdKey != NULL) {
FreePool (IkeKeys->SkdKey);
}
if (IkeKeys->SkEiKey != NULL) {
FreePool (IkeKeys->SkEiKey);
}
if (IkeKeys->SkErKey != NULL) {
FreePool (IkeKeys->SkErKey);
}
if (IkeKeys->SkPiKey != NULL) {
FreePool (IkeKeys->SkPiKey);
}
if (IkeKeys->SkPrKey != NULL) {
FreePool (IkeKeys->SkPrKey);
}
FreePool (IkeKeys);
}
if (IkeSaSession->SaData != NULL) {
FreePool (IkeSaSession->SaData);
}
if (IkeSaSession->NiBlock != NULL) {
FreePool (IkeSaSession->NiBlock);
}
if (IkeSaSession->NrBlock != NULL) {
FreePool (IkeSaSession->NrBlock);
}
if (IkeSaSession->NCookie != NULL) {
FreePool (IkeSaSession->NCookie);
}
if (IkeSaSession->InitPacket != NULL) {
FreePool (IkeSaSession->InitPacket);
}
if (IkeSaSession->RespPacket != NULL) {
FreePool (IkeSaSession->RespPacket);
}
FreePool (IkeSaSession);
return ;
}
/**
Increase the MessageID in IkeSaSession.
@param[in] IkeSaSession Pointer to a specified IKEV2_SA_SESSION.
**/
VOID
Ikev2SaSessionIncreaseMessageId (
IN IKEV2_SA_SESSION *IkeSaSession
)
{
if (IkeSaSession->MessageId < 0xffffffff) {
IkeSaSession->MessageId ++;
} else {
//
// TODO: Trigger Rekey process.
//
}
}
/**
Allocate memory for IKEV2 Child SA Session.
@param[in] UdpService Pointer to IKE_UDP_SERVICE.
@param[in] IkeSaSession Pointer to IKEV2_SA_SESSION related to this Child SA
Session.
@retval Pointer of a new created IKEV2 Child SA Session or NULL.
**/
IKEV2_CHILD_SA_SESSION *
Ikev2ChildSaSessionAlloc (
IN IKE_UDP_SERVICE *UdpService,
IN IKEV2_SA_SESSION *IkeSaSession
)
{
EFI_STATUS Status;
IKEV2_CHILD_SA_SESSION *ChildSaSession;
IKEV2_SESSION_COMMON *ChildSaCommon;
IKEV2_SESSION_COMMON *SaCommon;
ChildSaSession = AllocateZeroPool (sizeof (IKEV2_CHILD_SA_SESSION));
if (ChildSaSession == NULL) {
return NULL;
}
//
// Initialize the fields of ChildSaSession and its SessionCommon.
//
ChildSaSession->Signature = IKEV2_CHILD_SA_SESSION_SIGNATURE;
ChildSaSession->IkeSaSession = IkeSaSession;
ChildSaSession->MessageId = IkeSaSession->MessageId;
ChildSaSession->LocalPeerSpi = IkeGenerateSpi ();
ChildSaCommon = &ChildSaSession->SessionCommon;
ChildSaCommon->UdpService = UdpService;
ChildSaCommon->Private = IkeSaSession->SessionCommon.Private;
ChildSaCommon->IkeSessionType = IkeSessionTypeChildSa;
ChildSaCommon->IkeVer = 2;
ChildSaCommon->AfterEncodePayload = Ikev2ChildSaAfterEncodePayload;
ChildSaCommon->BeforeDecodePayload = Ikev2ChildSaBeforeDecodePayload;
SaCommon = &ChildSaSession->IkeSaSession->SessionCommon;
//
// Create a resend notfiy event for retry.
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
Ikev2ResendNotify,
ChildSaCommon,
&ChildSaCommon->TimeoutEvent
);
if (EFI_ERROR (Status)) {
FreePool (ChildSaSession);
return NULL;
}
CopyMem (&ChildSaCommon->LocalPeerIp, &SaCommon->LocalPeerIp, sizeof (EFI_IP_ADDRESS));
CopyMem (&ChildSaCommon->RemotePeerIp, &SaCommon->RemotePeerIp, sizeof (EFI_IP_ADDRESS));
return ChildSaSession;
}
/**
Register a established IKEv2 Child SA into IkeSaSession->ChildSaEstablishSessionList.
If the there is IKEV2_CHILD_SA_SESSION with same remote peer IP, remove the old one
then register the new one.
@param[in] ChildSaSession Pointer to IKEV2_CHILD_SA_SESSION to be registered.
@param[in] Private Pointer to IPSEC_PRAVATE_DATA.
**/
VOID
Ikev2ChildSaSessionReg (
IN IKEV2_CHILD_SA_SESSION *ChildSaSession,
IN IPSEC_PRIVATE_DATA *Private
)
{
IKEV2_SESSION_COMMON *SessionCommon;
IKEV2_CHILD_SA_SESSION *OldChildSaSession;
IKEV2_SA_SESSION *IkeSaSession;
EFI_STATUS Status;
UINT64 Lifetime;
//
// Keep the IKE SA exclusive.
//
SessionCommon = &ChildSaSession->SessionCommon;
IkeSaSession = ChildSaSession->IkeSaSession;
OldChildSaSession = Ikev2ChildSaSessionRemove (
&IkeSaSession->ChildSaEstablishSessionList,
ChildSaSession->LocalPeerSpi,
IKEV2_ESTABLISHED_CHILDSA_LIST
);
if (OldChildSaSession != NULL) {
//
// Free the old one.
//
Ikev2ChildSaSessionFree (OldChildSaSession);
}
//
// Store the ready child SA into SAD.
//
Ikev2StoreSaData (ChildSaSession);
//
// Cleanup the fields of SessionCommon for processing.
//
Ikev2SessionCommonRefresh (SessionCommon);
//
// Insert the ready child SA session into established list.
//
Ikev2ChildSaSessionInsert (&IkeSaSession->ChildSaEstablishSessionList, ChildSaSession);
//
// Create a Notify event for the IKE SA life time counting.
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
Ikev2LifetimeNotify,
SessionCommon,
&SessionCommon->TimeoutEvent
);
if (EFI_ERROR(Status)){
return ;
}
//
// Start to count the lifetime of the IKE SA.
//
if (ChildSaSession->Spd->Data->ProcessingPolicy->SaLifetime.HardLifetime != 0){
Lifetime = ChildSaSession->Spd->Data->ProcessingPolicy->SaLifetime.HardLifetime;
} else {
Lifetime = CHILD_SA_DEFAULT_LIFETIME;
}
Status = gBS->SetTimer (
SessionCommon->TimeoutEvent,
TimerRelative,
MultU64x32(Lifetime, 10000000) // ms->100ns
);
if (EFI_ERROR(Status)){
return ;
}
DEBUG ((
DEBUG_INFO,
"\n------ChildSa established and start to count down %d seconds lifetime\n",
Lifetime
));
return ;
}
/**
Find the ChildSaSession by it's MessagId.
@param[in] SaSessionList Pointer to a ChildSaSession List.
@param[in] Mid The messageId used to search ChildSaSession.
@return Pointer to IKEV2_CHILD_SA_SESSION or NULL.
**/
IKEV2_CHILD_SA_SESSION *
Ikev2ChildSaSessionLookupByMid (
IN LIST_ENTRY *SaSessionList,
IN UINT32 Mid
)
{
LIST_ENTRY *Entry;
IKEV2_CHILD_SA_SESSION *ChildSaSession;
NET_LIST_FOR_EACH (Entry, SaSessionList) {
ChildSaSession = IKEV2_CHILD_SA_SESSION_BY_IKE_SA (Entry);
if (ChildSaSession->MessageId == Mid) {
return ChildSaSession;
}
}
return NULL;
}
/**
This function find the Child SA by the specified SPI.
This functin find a ChildSA session by searching the ChildSaSessionlist of
the input IKEV2_SA_SESSION by specified MessageID.
@param[in] SaSessionList Pointer to List to be searched.
@param[in] Spi Specified SPI.
@return Pointer to IKEV2_CHILD_SA_SESSION or NULL.
**/
IKEV2_CHILD_SA_SESSION *
Ikev2ChildSaSessionLookupBySpi (
IN LIST_ENTRY *SaSessionList,
IN UINT32 Spi
)
{
LIST_ENTRY *Entry;
IKEV2_CHILD_SA_SESSION *ChildSaSession;
NET_LIST_FOR_EACH (Entry, SaSessionList) {
ChildSaSession = IKEV2_CHILD_SA_SESSION_BY_IKE_SA (Entry);
if (ChildSaSession->RemotePeerSpi == Spi || ChildSaSession->LocalPeerSpi == Spi) {
return ChildSaSession;
}
}
return NULL;
}
/**
Insert a Child SA Session into the specified ChildSa list.
@param[in] SaSessionList Pointer to list to be inserted in.
@param[in] ChildSaSession Pointer to IKEV2_CHILD_SA_SESSION to be inserted.
**/
VOID
Ikev2ChildSaSessionInsert (
IN LIST_ENTRY *SaSessionList,
IN IKEV2_CHILD_SA_SESSION *ChildSaSession
)
{
InsertTailList (SaSessionList, &ChildSaSession->ByIkeSa);
}
/**
Remove the IKEV2_CHILD_SA_SESSION from IkeSaSessionList.
@param[in] SaSessionList The SA Session List to be iterated.
@param[in] Spi Spi used to identified the IKEV2_CHILD_SA_SESSION.
@param[in] ListType The type of the List to indicate whether it is a
Established.
@return The point to IKEV2_CHILD_SA_SESSION or NULL.
**/
IKEV2_CHILD_SA_SESSION *
Ikev2ChildSaSessionRemove (
IN LIST_ENTRY *SaSessionList,
IN UINT32 Spi,
IN UINT8 ListType
)
{
LIST_ENTRY *Entry;
LIST_ENTRY *NextEntry;
IKEV2_CHILD_SA_SESSION *ChildSaSession;
NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, SaSessionList) {
if (ListType == IKEV2_ESTABLISHED_CHILDSA_LIST || ListType == IKEV2_ESTABLISHING_CHILDSA_LIST) {
ChildSaSession = IKEV2_CHILD_SA_SESSION_BY_IKE_SA (Entry);
} else if (ListType == IKEV2_DELET_CHILDSA_LIST) {
ChildSaSession = IKEV2_CHILD_SA_SESSION_BY_DEL_SA (Entry);
} else {
return NULL;
}
if (ChildSaSession->RemotePeerSpi == Spi || ChildSaSession->LocalPeerSpi == Spi) {
RemoveEntryList (Entry);
return ChildSaSession;
}
}
return NULL;
}
/**
Mark a specified Child SA Session as on deleting.
@param[in] ChildSaSession Pointer to IKEV2_CHILD_SA_SESSION.
@retval EFI_SUCCESS Operation is successful.
**/
EFI_STATUS
Ikev2ChildSaSessionOnDeleting (
IN IKEV2_CHILD_SA_SESSION *ChildSaSession
)
{
return EFI_SUCCESS;
}
/**
Free the memory located for the specified IKEV2_CHILD_SA_SESSION.
@param[in] ChildSaSession Pointer to IKEV2_CHILD_SA_SESSION.
**/
VOID
Ikev2ChildSaSessionFree (
IN IKEV2_CHILD_SA_SESSION *ChildSaSession
)
{
IKEV2_SESSION_COMMON *SessionCommon;
SessionCommon = &ChildSaSession->SessionCommon;
if (ChildSaSession->SaData != NULL) {
FreePool (ChildSaSession->SaData);
}
if (ChildSaSession->NiBlock != NULL) {
FreePool (ChildSaSession->NiBlock);
}
if (ChildSaSession->NrBlock != NULL) {
FreePool (ChildSaSession->NrBlock);
}
if (ChildSaSession->ChildKeymats.LocalPeerInfo.EspAlgoInfo.AuthKey != NULL) {
FreePool (ChildSaSession->ChildKeymats.LocalPeerInfo.EspAlgoInfo.AuthKey);
}
if (ChildSaSession->ChildKeymats.LocalPeerInfo.EspAlgoInfo.EncKey != NULL) {
FreePool (ChildSaSession->ChildKeymats.LocalPeerInfo.EspAlgoInfo.EncKey);
}
if (ChildSaSession->ChildKeymats.RemotePeerInfo.EspAlgoInfo.AuthKey != NULL) {
FreePool (ChildSaSession->ChildKeymats.RemotePeerInfo.EspAlgoInfo.AuthKey);
}
if (ChildSaSession->ChildKeymats.RemotePeerInfo.EspAlgoInfo.EncKey != NULL) {
FreePool (ChildSaSession->ChildKeymats.RemotePeerInfo.EspAlgoInfo.EncKey);
}
//
// Delete DhBuffer
//
Ikev2DhBufferFree (ChildSaSession->DhBuffer);
//
// Delete SpdSelector
//
if (ChildSaSession->SpdSelector != NULL) {
if (ChildSaSession->SpdSelector->LocalAddress != NULL) {
FreePool (ChildSaSession->SpdSelector->LocalAddress);
}
if (ChildSaSession->SpdSelector->RemoteAddress != NULL) {
FreePool (ChildSaSession->SpdSelector->RemoteAddress);
}
FreePool (ChildSaSession->SpdSelector);
}
Ikev2SaSessionCommonFree (SessionCommon);
FreePool (ChildSaSession);
return ;
}
/**
Delete the specified established Child SA.
This function delete the Child SA directly and don't send the Information Packet to
remote peer.
@param[in] IkeSaSession Pointer to a IKE SA Session used to be searched for.
@param[in] Spi SPI used to find the Child SA.
@retval EFI_NOT_FOUND Pointer of IKE SA Session is NULL.
@retval EFI_NOT_FOUND There is no specified Child SA related with the input
SPI under this IKE SA Session.
@retval EFI_SUCCESS Delete the Child SA successfully.
**/
EFI_STATUS
Ikev2ChildSaSilentDelete (
IN IKEV2_SA_SESSION *IkeSaSession,
IN UINT32 Spi
)
{
EFI_STATUS Status;
EFI_IPSEC_CONFIG_SELECTOR *Selector;
UINTN SelectorSize;
BOOLEAN IsLocalFound;
BOOLEAN IsRemoteFound;
UINT32 LocalSpi;
UINT32 RemoteSpi;
IKEV2_CHILD_SA_SESSION *ChildSession;
EFI_IPSEC_CONFIG_SELECTOR *LocalSelector;
EFI_IPSEC_CONFIG_SELECTOR *RemoteSelector;
IKE_UDP_SERVICE *UdpService;
IPSEC_PRIVATE_DATA *Private;
if (IkeSaSession == NULL) {
return EFI_NOT_FOUND;
}
IsLocalFound = FALSE;
IsRemoteFound = FALSE;
ChildSession = NULL;
LocalSelector = NULL;
RemoteSelector = NULL;
UdpService = IkeSaSession->SessionCommon.UdpService;
Private = (UdpService->IpVersion == IP_VERSION_4) ?
IPSEC_PRIVATE_DATA_FROM_UDP4LIST(UdpService->ListHead) :
IPSEC_PRIVATE_DATA_FROM_UDP6LIST(UdpService->ListHead);
//
// Remove the Established SA from ChildSaEstablishlist.
//
ChildSession = Ikev2ChildSaSessionRemove(
&(IkeSaSession->ChildSaEstablishSessionList),
Spi,
IKEV2_ESTABLISHED_CHILDSA_LIST
);
if (ChildSession == NULL) {
return EFI_NOT_FOUND;
}
LocalSpi = ChildSession->LocalPeerSpi;
RemoteSpi = ChildSession->RemotePeerSpi;
SelectorSize = sizeof (EFI_IPSEC_CONFIG_SELECTOR);
Selector = AllocateZeroPool (SelectorSize);
ASSERT (Selector != NULL);
while (1) {
Status = EfiIpSecConfigGetNextSelector (
&Private->IpSecConfig,
IPsecConfigDataTypeSad,
&SelectorSize,
Selector
);
if (Status == EFI_BUFFER_TOO_SMALL) {
FreePool (Selector);
Selector = AllocateZeroPool (SelectorSize);
ASSERT (Selector != NULL);
Status = EfiIpSecConfigGetNextSelector (
&Private->IpSecConfig,
IPsecConfigDataTypeSad,
&SelectorSize,
Selector
);
}
if (EFI_ERROR (Status)) {
break;
}
if (Selector->SaId.Spi == RemoteSpi) {
//
// SPI is unique. There is only one SAD whose SPI is
// same with RemoteSpi.
//
IsRemoteFound = TRUE;
RemoteSelector = AllocateZeroPool (SelectorSize);
ASSERT (RemoteSelector != NULL);
CopyMem (RemoteSelector, Selector, SelectorSize);
}
if (Selector->SaId.Spi == LocalSpi) {
//
// SPI is unique. There is only one SAD whose SPI is
// same with LocalSpi.
//
IsLocalFound = TRUE;
LocalSelector = AllocateZeroPool (SelectorSize);
ASSERT (LocalSelector != NULL);
CopyMem (LocalSelector, Selector, SelectorSize);
}
}
//
// Delete SA from the Variable.
//
if (IsLocalFound) {
Status = EfiIpSecConfigSetData (
&Private->IpSecConfig,
IPsecConfigDataTypeSad,
LocalSelector,
NULL,
NULL
);
}
if (IsRemoteFound) {
Status = EfiIpSecConfigSetData (
&Private->IpSecConfig,
IPsecConfigDataTypeSad,
RemoteSelector,
NULL,
NULL
);
}
DEBUG (
(DEBUG_INFO,
"\n------IKEV2 deleted ChildSa(local spi, remote spi):(0x%x, 0x%x)------\n",
LocalSpi,
RemoteSpi)
);
Ikev2ChildSaSessionFree (ChildSession);
if (RemoteSelector != NULL) {
FreePool (RemoteSelector);
}
if (LocalSelector != NULL) {
FreePool (LocalSelector);
}
if (Selector != NULL) {
FreePool (Selector);
}
return Status;
}
/**
Free the specified DhBuffer.
@param[in] DhBuffer Pointer to IKEV2_DH_BUFFER to be freed.
**/
VOID
Ikev2DhBufferFree (
IKEV2_DH_BUFFER *DhBuffer
)
{
if (DhBuffer != NULL) {
if (DhBuffer->GxBuffer != NULL) {
FreePool (DhBuffer->GxBuffer);
}
if (DhBuffer->GyBuffer != NULL) {
FreePool (DhBuffer->GyBuffer);
}
if (DhBuffer->GxyBuffer != NULL) {
FreePool (DhBuffer->GxyBuffer);
}
if (DhBuffer->DhContext != NULL) {
IpSecCryptoIoFreeDh (&DhBuffer->DhContext);
}
FreePool (DhBuffer);
}
}
/**
This function is to parse a request IKE packet and return its request type.
The request type is one of IKE CHILD SA creation, IKE SA rekeying and
IKE CHILD SA rekeying.
@param[in] IkePacket IKE packet to be prased.
return the type of the IKE packet.
**/
IKEV2_CREATE_CHILD_REQUEST_TYPE
Ikev2ChildExchangeRequestType(
IN IKE_PACKET *IkePacket
)
{
BOOLEAN Flag;
LIST_ENTRY *Entry;
IKE_PAYLOAD *IkePayload;
Flag = FALSE;
NET_LIST_FOR_EACH (Entry, &(IkePacket)->PayloadList) {
IkePayload = IKE_PAYLOAD_BY_PACKET (Entry);
if (IkePayload->PayloadType == IKEV2_PAYLOAD_TYPE_TS_INIT) {
//
// Packet with Ts Payload means it is for either CHILD_SA_CREATE or CHILD_SA_REKEY.
//
Flag = TRUE;
}
if (IkePayload->PayloadType == IKEV2_PAYLOAD_TYPE_NOTIFY) {
if (((IKEV2_NOTIFY*)IkePayload)->MessageType == IKEV2_NOTIFICATION_REKEY_SA) {
//
// If notify payload with REKEY_SA message type, the IkePacket is for
// rekeying Child SA.
//
return IkeRequestTypeRekeyChildSa;
}
}
};
if (!Flag){
//
// The Create Child Exchange is for IKE SA rekeying.
//
return IkeRequestTypeRekeyIkeSa;
} else {
//
// If the Notify payloaad with transport mode message type, the IkePacket is
// for create Child SA.
//
return IkeRequestTypeCreateChildSa;
}
}
/**
Associate a SPD selector to the Child SA Session.
This function is called when the Child SA is not the first child SA of its
IKE SA. It associate a SPD to this Child SA.
@param[in, out] ChildSaSession Pointer to the Child SA Session to be associated to
a SPD selector.
@retval EFI_SUCCESS Associate one SPD selector to this Child SA Session successfully.
@retval EFI_NOT_FOUND Can't find the related SPD selector.
**/
EFI_STATUS
Ikev2ChildSaAssociateSpdEntry (
IN OUT IKEV2_CHILD_SA_SESSION *ChildSaSession
)
{
IpSecVisitConfigData (IPsecConfigDataTypeSpd, Ikev2MatchSpdEntry, ChildSaSession);
if (ChildSaSession->Spd != NULL) {
return EFI_SUCCESS;
} else {
return EFI_NOT_FOUND;
}
}
/**
This function finds the SPI from Create Child SA Exchange Packet.
@param[in] IkePacket Pointer to IKE_PACKET to be searched.
@retval SPI number or 0 if it is not supported.
**/
UINT32
Ikev2ChildExchangeRekeySpi (
IN IKE_PACKET *IkePacket
)
{
//
// Not support yet.
//
return 0;
}
/**
Validate the IKE header of received IKE packet.
@param[in] IkeSaSession Pointer to IKEV2_SA_SESSION related to this IKE packet.
@param[in] IkeHdr Pointer to IKE header of received IKE packet.
@retval TRUE If the IKE header is valid.
@retval FALSE If the IKE header is invalid.
**/
BOOLEAN
Ikev2ValidateHeader (
IN IKEV2_SA_SESSION *IkeSaSession,
IN IKE_HEADER *IkeHdr
)
{
IKEV2_SESSION_STATE State;
State = IkeSaSession->SessionCommon.State;
if (State == IkeStateInit) {
//
// For the IKE Initial Exchange, the MessagId should be zero.
//
if (IkeHdr->MessageId != 0) {
return FALSE;
}
} else {
if (State == IkeStateAuth) {
if (IkeHdr->MessageId != 1) {
return FALSE;
}
}
if (IkeHdr->InitiatorCookie != IkeSaSession->InitiatorCookie ||
IkeHdr->ResponderCookie != IkeSaSession->ResponderCookie
) {
//
// TODO: send notification INVALID-COOKIE
//
return FALSE;
}
}
//
// Information Exchagne and Create Child Exchange can be started from each part.
//
if (IkeHdr->ExchangeType != IKEV2_EXCHANGE_TYPE_INFO &&
IkeHdr->ExchangeType != IKEV2_EXCHANGE_TYPE_CREATE_CHILD
) {
if (IkeSaSession->SessionCommon.IsInitiator) {
if (IkeHdr->InitiatorCookie != IkeSaSession->InitiatorCookie) {
//
// TODO: send notification INVALID-COOKIE
//
return FALSE;
}
if (IkeHdr->Flags != IKE_HEADER_FLAGS_RESPOND) {
return FALSE;
}
} else {
if (IkeHdr->Flags != IKE_HEADER_FLAGS_INIT) {
return FALSE;
}
}
}
return TRUE;
}
/**
Create and intialize IKEV2_SA_DATA for speicifed IKEV2_SESSION_COMMON.
This function will be only called by the initiator. The responder's IKEV2_SA_DATA
will be generated during parsed the initiator packet.
@param[in] SessionCommon Pointer to IKEV2_SESSION_COMMON related to.
@retval a Pointer to a new IKEV2_SA_DATA or NULL.
**/
IKEV2_SA_DATA *
Ikev2InitializeSaData (
IN IKEV2_SESSION_COMMON *SessionCommon
)
{
IKEV2_CHILD_SA_SESSION *ChildSaSession;
IKEV2_SA_DATA *SaData;
IKEV2_PROPOSAL_DATA *ProposalData;
IKEV2_TRANSFORM_DATA *TransformData;
IKE_SA_ATTRIBUTE *Attribute;
ASSERT (SessionCommon != NULL);
//
// TODO: Remove the hard code of the support Alogrithm. Those data should be
// get from the SPD/PAD data.
//
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
SaData = AllocateZeroPool (
sizeof (IKEV2_SA_DATA) +
sizeof (IKEV2_PROPOSAL_DATA) * 2 +
sizeof (IKEV2_TRANSFORM_DATA) * 4 * 2
);
} else {
SaData = AllocateZeroPool (
sizeof (IKEV2_SA_DATA) +
sizeof (IKEV2_PROPOSAL_DATA) * 2 +
sizeof (IKEV2_TRANSFORM_DATA) * 3 * 2
);
}
if (SaData == NULL) {
return NULL;
}
//
// First proposal payload: 3DES + SHA1 + DH
//
SaData->NumProposals = 2;
ProposalData = (IKEV2_PROPOSAL_DATA *) (SaData + 1);
ProposalData->ProposalIndex = 1;
//
// If SA data for IKE_SA_INIT exchage, contains 4 transforms. If SA data for
// IKE_AUTH exchange contains 3 transforms.
//
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
ProposalData->NumTransforms = 4;
} else {
ProposalData->NumTransforms = 3;
}
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
ProposalData->ProtocolId = IPSEC_PROTO_ISAKMP;
} else {
ChildSaSession = IKEV2_CHILD_SA_SESSION_FROM_COMMON (SessionCommon);
ProposalData->ProtocolId = IPSEC_PROTO_IPSEC_ESP;
ProposalData->Spi = AllocateZeroPool (sizeof (ChildSaSession->LocalPeerSpi));
ASSERT (ProposalData->Spi != NULL);
CopyMem (
ProposalData->Spi,
&ChildSaSession->LocalPeerSpi,
sizeof(ChildSaSession->LocalPeerSpi)
);
}
//
// Set transform attribute for Encryption Algorithm - 3DES
//
TransformData = (IKEV2_TRANSFORM_DATA *) (ProposalData + 1);
TransformData->TransformIndex = 0;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_ENCR;
TransformData->TransformId = IKEV2_TRANSFORM_ID_ENCR_3DES;
//
// Set transform attribute for Integrity Algorithm - SHA1_96
//
TransformData = (IKEV2_TRANSFORM_DATA *) (TransformData + 1);
TransformData->TransformIndex = 1;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_INTEG;
TransformData->TransformId = IKEV2_TRANSFORM_ID_AUTH_HMAC_SHA1_96;
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
//
// Set transform attribute for Pseduo-Random Function - HAMC_SHA1
//
TransformData = (IKEV2_TRANSFORM_DATA *) (TransformData + 1);
TransformData->TransformIndex = 2;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_PRF;
TransformData->TransformId = IKEV2_TRANSFORM_ID_PRF_HMAC_SHA1;
}
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
//
// Set transform attribute for DH Group - DH 1024
//
TransformData = (IKEV2_TRANSFORM_DATA *) (TransformData + 1);
TransformData->TransformIndex = 3;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_DH;
TransformData->TransformId = IKEV2_TRANSFORM_ID_DH_1024MODP;
} else {
//
// Transform type for Extended Sequence Numbers. Currently not support Extended
// Sequence Number.
//
TransformData = (IKEV2_TRANSFORM_DATA *) (TransformData + 1);
TransformData->TransformIndex = 2;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_ESN;
TransformData->TransformId = 0;
}
//
// Second proposal payload: 3DES + SHA1 + DH
//
ProposalData = (IKEV2_PROPOSAL_DATA *) (TransformData + 1);
ProposalData->ProposalIndex = 2;
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
ProposalData->ProtocolId = IPSEC_PROTO_ISAKMP;
ProposalData->NumTransforms = 4;
} else {
ChildSaSession = IKEV2_CHILD_SA_SESSION_FROM_COMMON (SessionCommon);
ProposalData->ProtocolId = IPSEC_PROTO_IPSEC_ESP;
ProposalData->NumTransforms = 3;
ProposalData->Spi = AllocateZeroPool (sizeof (ChildSaSession->LocalPeerSpi));
ASSERT (ProposalData->Spi != NULL);
CopyMem (
ProposalData->Spi,
&ChildSaSession->LocalPeerSpi,
sizeof(ChildSaSession->LocalPeerSpi)
);
}
//
// Set transform attribute for Encryption Algorithm - AES-CBC
//
TransformData = (IKEV2_TRANSFORM_DATA *) (ProposalData + 1);
TransformData->TransformIndex = 0;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_ENCR;
TransformData->TransformId = IKEV2_TRANSFORM_ID_ENCR_AES_CBC;
Attribute = &TransformData->Attribute;
Attribute->AttrType = IKEV2_ATTRIBUTE_TYPE_KEYLEN;
Attribute->Attr.AttrLength = (UINT16) (8 * IpSecGetEncryptKeyLength (IKEV2_TRANSFORM_ID_ENCR_AES_CBC));
//
// Set transform attribute for Integrity Algorithm - SHA1_96
//
TransformData = (IKEV2_TRANSFORM_DATA *) (TransformData + 1);
TransformData->TransformIndex = 1;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_INTEG;
TransformData->TransformId = IKEV2_TRANSFORM_ID_AUTH_HMAC_SHA1_96;
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
//
// Set transform attribute for Pseduo-Random Function - HAMC_SHA1
//
TransformData = (IKEV2_TRANSFORM_DATA *) (TransformData + 1);
TransformData->TransformIndex = 2;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_PRF;
TransformData->TransformId = IKEV2_TRANSFORM_ID_PRF_HMAC_SHA1;
}
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
//
// Set transform attrbiute for DH Group - DH-1024
//
TransformData = (IKEV2_TRANSFORM_DATA *) (TransformData + 1);
TransformData->TransformIndex = 3;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_DH;
TransformData->TransformId = IKEV2_TRANSFORM_ID_DH_1024MODP;
} else {
//
// Transform type for Extended Sequence Numbers. Currently not support Extended
// Sequence Number.
//
TransformData = (IKEV2_TRANSFORM_DATA *) (TransformData + 1);
TransformData->TransformIndex = 2;
TransformData->TransformType = IKEV2_TRANSFORM_TYPE_ESN;
TransformData->TransformId = 0;
}
return SaData;
}
/**
Store the SA into SAD.
@param[in] ChildSaSession Pointer to IKEV2_CHILD_SA_SESSION.
**/
VOID
Ikev2StoreSaData (
IN IKEV2_CHILD_SA_SESSION *ChildSaSession
)
{
EFI_STATUS Status;
EFI_IPSEC_SA_ID SaId;
EFI_IPSEC_SA_DATA2 SaData;
IKEV2_SESSION_COMMON *SessionCommon;
IPSEC_PRIVATE_DATA *Private;
UINT32 TempAddressCount;
EFI_IP_ADDRESS_INFO *TempAddressInfo;
SessionCommon = &ChildSaSession->SessionCommon;
Private = SessionCommon->Private;
ZeroMem (&SaId, sizeof (EFI_IPSEC_SA_ID));
ZeroMem (&SaData, sizeof (EFI_IPSEC_SA_DATA2));
//
// Create a SpdSelector. In this implementation, one SPD represents
// 2 direction traffic, so in here, there needs to reverse the local address
// and remote address for Remote Peer's SA, then reverse again for the locate
// SA.
//
TempAddressCount = ChildSaSession->SpdSelector->LocalAddressCount;
TempAddressInfo = ChildSaSession->SpdSelector->LocalAddress;
ChildSaSession->SpdSelector->LocalAddressCount = ChildSaSession->SpdSelector->RemoteAddressCount;
ChildSaSession->SpdSelector->LocalAddress = ChildSaSession->SpdSelector->RemoteAddress;
ChildSaSession->SpdSelector->RemoteAddress = TempAddressInfo;
ChildSaSession->SpdSelector->RemoteAddressCount= TempAddressCount;
//
// Set the SaId and SaData.
//
SaId.Spi = ChildSaSession->LocalPeerSpi;
SaId.Proto = EfiIPsecESP;
SaData.AntiReplayWindows = 16;
SaData.SNCount = 0;
SaData.Mode = ChildSaSession->Spd->Data->ProcessingPolicy->Mode;
//
// If it is tunnel mode, should add the TunnelDest and TunnelSource for SaData.
//
if (SaData.Mode == EfiIPsecTunnel) {
CopyMem (
&SaData.TunnelSourceAddress,
&ChildSaSession->Spd->Data->ProcessingPolicy->TunnelOption->RemoteTunnelAddress,
sizeof (EFI_IP_ADDRESS)
);
CopyMem (
&SaData.TunnelDestinationAddress,
&ChildSaSession->Spd->Data->ProcessingPolicy->TunnelOption->LocalTunnelAddress,
sizeof (EFI_IP_ADDRESS)
);
}
CopyMem (&SaId.DestAddress, &ChildSaSession->SessionCommon.LocalPeerIp, sizeof (EFI_IP_ADDRESS));
CopyMem (&SaData.AlgoInfo, &ChildSaSession->ChildKeymats.LocalPeerInfo, sizeof (EFI_IPSEC_ALGO_INFO));
SaData.SpdSelector = ChildSaSession->SpdSelector;
//
// Store the remote SA into SAD.
//
Status = EfiIpSecConfigSetData (
&Private->IpSecConfig,
IPsecConfigDataTypeSad,
(EFI_IPSEC_CONFIG_SELECTOR *) &SaId,
&SaData,
NULL
);
ASSERT_EFI_ERROR (Status);
//
// Store the local SA into SAD.
//
ChildSaSession->SpdSelector->RemoteAddressCount = ChildSaSession->SpdSelector->LocalAddressCount;
ChildSaSession->SpdSelector->RemoteAddress = ChildSaSession->SpdSelector->LocalAddress;
ChildSaSession->SpdSelector->LocalAddress = TempAddressInfo;
ChildSaSession->SpdSelector->LocalAddressCount = TempAddressCount;
SaId.Spi = ChildSaSession->RemotePeerSpi;
CopyMem (&SaId.DestAddress, &ChildSaSession->SessionCommon.RemotePeerIp, sizeof (EFI_IP_ADDRESS));
CopyMem (&SaData.AlgoInfo, &ChildSaSession->ChildKeymats.RemotePeerInfo, sizeof (EFI_IPSEC_ALGO_INFO));
SaData.SpdSelector = ChildSaSession->SpdSelector;
//
// If it is tunnel mode, should add the TunnelDest and TunnelSource for SaData.
//
if (SaData.Mode == EfiIPsecTunnel) {
CopyMem (
&SaData.TunnelSourceAddress,
&ChildSaSession->Spd->Data->ProcessingPolicy->TunnelOption->LocalTunnelAddress,
sizeof (EFI_IP_ADDRESS)
);
CopyMem (
&SaData.TunnelDestinationAddress,
&ChildSaSession->Spd->Data->ProcessingPolicy->TunnelOption->RemoteTunnelAddress,
sizeof (EFI_IP_ADDRESS)
);
}
Status = EfiIpSecConfigSetData (
&Private->IpSecConfig,
IPsecConfigDataTypeSad,
(EFI_IPSEC_CONFIG_SELECTOR *) &SaId,
&SaData,
NULL
);
ASSERT_EFI_ERROR (Status);
}
/**
Call back function of the IKE life time is over.
This function will mark the related IKE SA Session as deleting and trigger a
Information negotiation.
@param[in] Event The signaled Event.
@param[in] Context Pointer to data passed by caller.
**/
VOID
EFIAPI
Ikev2LifetimeNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
IKEV2_SA_SESSION *IkeSaSession;
IKEV2_CHILD_SA_SESSION *ChildSaSession;
IKEV2_SESSION_COMMON *SessionCommon;
ASSERT (Context != NULL);
SessionCommon = (IKEV2_SESSION_COMMON *) Context;
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
IkeSaSession = IKEV2_SA_SESSION_FROM_COMMON (SessionCommon);
DEBUG ((
DEBUG_INFO,
"\n---IkeSa Lifetime is out(cookie_i, cookie_r):(0x%lx, 0x%lx)---\n",
IkeSaSession->InitiatorCookie,
IkeSaSession->ResponderCookie
));
//
// Change the IKE SA Session's State to IKE_STATE_SA_DELETING.
//
IKEV2_DUMP_STATE (IkeSaSession->SessionCommon.State, IkeStateSaDeleting);
IkeSaSession->SessionCommon.State = IkeStateSaDeleting;
} else {
ChildSaSession = IKEV2_CHILD_SA_SESSION_FROM_COMMON (SessionCommon);
IkeSaSession = ChildSaSession->IkeSaSession;
//
// Link the timeout child SA to the DeleteSaList.
//
InsertTailList (&IkeSaSession->DeleteSaList, &ChildSaSession->ByDelete);
//
// Change the Child SA Session's State to IKE_STATE_SA_DELETING.
//
DEBUG ((
DEBUG_INFO,
"\n------ChildSa Lifetime is out(SPI):(0x%x)------\n",
ChildSaSession->LocalPeerSpi
));
}
//
// TODO: Send the delete info packet or delete silently
//
mIkev2Exchange.NegotiateInfo ((UINT8 *) IkeSaSession, NULL);
}
/**
This function will be called if the TimeOut Event is signaled.
@param[in] Event The signaled Event.
@param[in] Context The data passed by caller.
**/
VOID
EFIAPI
Ikev2ResendNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
IPSEC_PRIVATE_DATA *Private;
IKEV2_SA_SESSION *IkeSaSession;
IKEV2_CHILD_SA_SESSION *ChildSaSession;
IKEV2_SESSION_COMMON *SessionCommon;
LIST_ENTRY *ChildSaEntry;
UINT8 Value;
EFI_STATUS Status;
ASSERT (Context != NULL);
IkeSaSession = NULL;
ChildSaSession = NULL;
SessionCommon = (IKEV2_SESSION_COMMON *) Context;
Private = SessionCommon->Private;
//
// Remove the SA session from the processing list if exceed the max retry.
//
if (SessionCommon->RetryCount > IKE_MAX_RETRY) {
if (SessionCommon->IkeSessionType == IkeSessionTypeIkeSa) {
IkeSaSession = IKEV2_SA_SESSION_FROM_COMMON (SessionCommon);
if (IkeSaSession->SessionCommon.State == IkeStateSaDeleting) {
//
// If the IkeSaSession is initiator, delete all its Child SAs before removing IKE SA.
// If the IkesaSession is responder, all ChildSa has been remove in Ikev2HandleInfo();
//
for (ChildSaEntry = IkeSaSession->ChildSaEstablishSessionList.ForwardLink;
ChildSaEntry != &IkeSaSession->ChildSaEstablishSessionList;
) {
ChildSaSession = IKEV2_CHILD_SA_SESSION_BY_IKE_SA (ChildSaEntry);
//
// Move to next ChildSa Entry.
//
ChildSaEntry = ChildSaEntry->ForwardLink;
//
// Delete LocalSpi & RemoteSpi and remove the ChildSaSession from the
// EstablishedChildSaList.
//
Ikev2ChildSaSilentDelete (IkeSaSession, ChildSaSession->LocalPeerSpi);
}
//
// If the IKE SA Delete Payload wasn't sent out successfully, Delete it from the EstablishedList.
//
Ikev2SaSessionRemove (&Private->Ikev2EstablishedList, &SessionCommon->RemotePeerIp);
if (Private != NULL && Private->IsIPsecDisabling) {
//
// After all IKE SAs were deleted, set the IPSEC_STATUS_DISABLED value in
// IPsec status variable.
//
if (IsListEmpty (&Private->Ikev1EstablishedList) && IsListEmpty (&Private->Ikev2EstablishedList)) {
Value = IPSEC_STATUS_DISABLED;
Status = gRT->SetVariable (
IPSECCONFIG_STATUS_NAME,
&gEfiIpSecConfigProtocolGuid,
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
sizeof (Value),
&Value
);
if (!EFI_ERROR (Status)) {
//
// Set the Disabled Flag in Private data.
//
Private->IpSec.DisabledFlag = TRUE;
Private->IsIPsecDisabling = FALSE;
}
}
}
} else {
Ikev2SaSessionRemove (&Private->Ikev2SessionList, &SessionCommon->RemotePeerIp);
}
Ikev2SaSessionFree (IkeSaSession);
} else {
//
// If the packet sent by Child SA.
//
ChildSaSession = IKEV2_CHILD_SA_SESSION_FROM_COMMON (SessionCommon);
IkeSaSession = ChildSaSession->IkeSaSession;
if (ChildSaSession->SessionCommon.State == IkeStateSaDeleting) {
//
// Established Child SA should be remove from the SAD entry and
// DeleteList. The function of Ikev2DeleteChildSaSilent() will remove
// the childSA from the IkeSaSession->ChildSaEstablishedList. So there
// is no need to remove it here.
//
Ikev2ChildSaSilentDelete (IkeSaSession, ChildSaSession->LocalPeerSpi);
Ikev2ChildSaSessionRemove (
&IkeSaSession->DeleteSaList,
ChildSaSession->LocalPeerSpi,
IKEV2_DELET_CHILDSA_LIST
);
} else {
Ikev2ChildSaSessionRemove (
&IkeSaSession->ChildSaSessionList,
ChildSaSession->LocalPeerSpi,
IKEV2_ESTABLISHING_CHILDSA_LIST
);
}
Ikev2ChildSaSessionFree (ChildSaSession);
}
return ;
}
//
// Increase the retry count.
//
SessionCommon->RetryCount++;
DEBUG ((DEBUG_INFO, ">>>Resending the last packet ...\n"));
//
// Resend the last packet.
//
Ikev2SendIkePacket (
SessionCommon->UdpService,
(UINT8*)SessionCommon,
SessionCommon->LastSentPacket,
0
);
}
/**
Copy ChildSaSession->Spd->Selector to ChildSaSession->SpdSelector.
ChildSaSession->SpdSelector stores the real Spdselector for its SA. Sometime,
the SpdSelector in ChildSaSession is more accurated or the scope is smaller
than the one in ChildSaSession->Spd, especially for the tunnel mode.
@param[in, out] ChildSaSession Pointer to IKEV2_CHILD_SA_SESSION related to.
**/
VOID
Ikev2ChildSaSessionSpdSelectorCreate (
IN OUT IKEV2_CHILD_SA_SESSION *ChildSaSession
)
{
if (ChildSaSession->Spd != NULL && ChildSaSession->Spd->Selector != NULL) {
if (ChildSaSession->SpdSelector == NULL) {
ChildSaSession->SpdSelector = AllocateZeroPool (sizeof (EFI_IPSEC_SPD_SELECTOR));
ASSERT (ChildSaSession->SpdSelector != NULL);
}
CopyMem (
ChildSaSession->SpdSelector,
ChildSaSession->Spd->Selector,
sizeof (EFI_IPSEC_SPD_SELECTOR)
);
ChildSaSession->SpdSelector->RemoteAddress = AllocateCopyPool (
ChildSaSession->Spd->Selector->RemoteAddressCount *
sizeof (EFI_IP_ADDRESS_INFO),
ChildSaSession->Spd->Selector->RemoteAddress
);
ChildSaSession->SpdSelector->LocalAddress = AllocateCopyPool (
ChildSaSession->Spd->Selector->LocalAddressCount *
sizeof (EFI_IP_ADDRESS_INFO),
ChildSaSession->Spd->Selector->LocalAddress
);
ASSERT (ChildSaSession->SpdSelector->LocalAddress != NULL);
ASSERT (ChildSaSession->SpdSelector->RemoteAddress != NULL);
ChildSaSession->SpdSelector->RemoteAddressCount = ChildSaSession->Spd->Selector->RemoteAddressCount;
ChildSaSession->SpdSelector->LocalAddressCount = ChildSaSession->Spd->Selector->LocalAddressCount;
}
}
/**
Generate a ChildSa Session and insert it into related IkeSaSession.
@param[in] IkeSaSession Pointer to related IKEV2_SA_SESSION.
@param[in] UdpService Pointer to related IKE_UDP_SERVICE.
@return pointer of IKEV2_CHILD_SA_SESSION.
**/
IKEV2_CHILD_SA_SESSION *
Ikev2ChildSaSessionCreate (
IN IKEV2_SA_SESSION *IkeSaSession,
IN IKE_UDP_SERVICE *UdpService
)
{
IKEV2_CHILD_SA_SESSION *ChildSaSession;
IKEV2_SESSION_COMMON *ChildSaCommon;
//
// Create a new ChildSaSession.Insert it into processing list and initiate the common parameters.
//
ChildSaSession = Ikev2ChildSaSessionAlloc (UdpService, IkeSaSession);
ASSERT (ChildSaSession != NULL);
//
// Set the specific parameters.
//
ChildSaSession->Spd = IkeSaSession->Spd;
ChildSaCommon = &ChildSaSession->SessionCommon;
ChildSaCommon->IsInitiator = IkeSaSession->SessionCommon.IsInitiator;
if (IkeSaSession->SessionCommon.State == IkeStateAuth) {
ChildSaCommon->State = IkeStateAuth;
IKEV2_DUMP_STATE (ChildSaCommon->State, IkeStateAuth);
} else {
ChildSaCommon->State = IkeStateCreateChild;
IKEV2_DUMP_STATE (ChildSaCommon->State, IkeStateCreateChild);
}
//
// If SPD->Selector is not NULL, copy it to the ChildSaSession->SpdSelector.
// The ChildSaSession->SpdSelector might be changed after the traffic selector
// negoniation and it will be copied into the SAData after ChildSA established.
//
Ikev2ChildSaSessionSpdSelectorCreate (ChildSaSession);
//
// Copy first NiBlock and NrBlock to ChildSa Session
//
ChildSaSession->NiBlock = AllocateZeroPool (IkeSaSession->NiBlkSize);
ASSERT (ChildSaSession->NiBlock != NULL);
ChildSaSession->NiBlkSize = IkeSaSession->NiBlkSize;
CopyMem (ChildSaSession->NiBlock, IkeSaSession->NiBlock, IkeSaSession->NiBlkSize);
ChildSaSession->NrBlock = AllocateZeroPool (IkeSaSession->NrBlkSize);
ASSERT (ChildSaSession->NrBlock != NULL);
ChildSaSession->NrBlkSize = IkeSaSession->NrBlkSize;
CopyMem (ChildSaSession->NrBlock, IkeSaSession->NrBlock, IkeSaSession->NrBlkSize);
//
// Only if the Create Child SA is called for the IKE_INIT Exchange and
// IkeSaSession is initiator (Only Initiator's SPD is not NULL), Set the
// Traffic Selectors related information here.
//
if (IkeSaSession->SessionCommon.State == IkeStateAuth && IkeSaSession->Spd != NULL) {
ChildSaSession->ProtoId = IkeSaSession->Spd->Selector->NextLayerProtocol;
ChildSaSession->LocalPort = IkeSaSession->Spd->Selector->LocalPort;
ChildSaSession->RemotePort = IkeSaSession->Spd->Selector->RemotePort;
}
//
// Insert the new ChildSaSession into processing child SA list.
//
Ikev2ChildSaSessionInsert (&IkeSaSession->ChildSaSessionList, ChildSaSession);
return ChildSaSession;
}
/**
Check if the SPD is related to the input Child SA Session.
This function is the subfunction of Ikev1AssociateSpdEntry(). It is the call
back function of IpSecVisitConfigData().
@param[in] Type Type of the input Config Selector.
@param[in] Selector Pointer to the Configure Selector to be checked.
@param[in] Data Pointer to the Configure Selector's Data passed
from the caller.
@param[in] SelectorSize The buffer size of Selector.
@param[in] DataSize The buffer size of the Data.
@param[in] Context The data passed from the caller. It is a Child
SA Session in this context.
@retval EFI_SUCCESS The SPD Selector is not related to the Child SA Session.
@retval EFI_ABORTED The SPD Selector is related to the Child SA session and
set the ChildSaSession->Spd to point to this SPD Selector.
**/
EFI_STATUS
Ikev2MatchSpdEntry (
IN EFI_IPSEC_CONFIG_DATA_TYPE Type,
IN EFI_IPSEC_CONFIG_SELECTOR *Selector,
IN VOID *Data,
IN UINTN SelectorSize,
IN UINTN DataSize,
IN VOID *Context
)
{
IKEV2_CHILD_SA_SESSION *ChildSaSession;
EFI_IPSEC_SPD_SELECTOR *SpdSelector;
EFI_IPSEC_SPD_DATA *SpdData;
BOOLEAN IsMatch;
UINT8 IpVersion;
ASSERT (Type == IPsecConfigDataTypeSpd);
SpdData = (EFI_IPSEC_SPD_DATA *) Data;
//
// Bypass all non-protect SPD entry first
//
if (SpdData->Action != EfiIPsecActionProtect) {
return EFI_SUCCESS;
}
ChildSaSession = (IKEV2_CHILD_SA_SESSION *) Context;
IpVersion = ChildSaSession->SessionCommon.UdpService->IpVersion;
SpdSelector = (EFI_IPSEC_SPD_SELECTOR *) Selector;
IsMatch = TRUE;
if (SpdSelector->NextLayerProtocol == EFI_IP_PROTO_UDP &&
SpdSelector->LocalPort == IKE_DEFAULT_PORT &&
SpdSelector->LocalPortRange == 0 &&
SpdSelector->RemotePort == IKE_DEFAULT_PORT &&
SpdSelector->RemotePortRange == 0
) {
//
// TODO: Skip IKE Policy here or set a SPD entry?
//
return EFI_SUCCESS;
}
if (SpdSelector->NextLayerProtocol != EFI_IPSEC_ANY_PROTOCOL &&
SpdSelector->NextLayerProtocol != ChildSaSession->ProtoId
) {
IsMatch = FALSE;
}
if (SpdSelector->LocalPort != EFI_IPSEC_ANY_PORT && SpdSelector->LocalPort != ChildSaSession->LocalPort) {
IsMatch = FALSE;
}
if (SpdSelector->RemotePort != EFI_IPSEC_ANY_PORT && SpdSelector->RemotePort != ChildSaSession->RemotePort) {
IsMatch = FALSE;
}
IsMatch = (BOOLEAN) (IsMatch &&
IpSecMatchIpAddress (
IpVersion,
&ChildSaSession->SessionCommon.LocalPeerIp,
SpdSelector->LocalAddress,
SpdSelector->LocalAddressCount
));
IsMatch = (BOOLEAN) (IsMatch &&
IpSecMatchIpAddress (
IpVersion,
&ChildSaSession->SessionCommon.RemotePeerIp,
SpdSelector->RemoteAddress,
SpdSelector->RemoteAddressCount
));
if (IsMatch) {
ChildSaSession->Spd = IkeSearchSpdEntry (SpdSelector);
return EFI_ABORTED;
} else {
return EFI_SUCCESS;
}
}
/**
Check if the Algorithm ID is supported.
@param[in] AlgorithmId The specified Algorithm ID.
@param[in] Type The type used to indicate the Algorithm is for Encrypt or
Authentication.
@retval TRUE If the Algorithm ID is supported.
@retval FALSE If the Algorithm ID is not supported.
**/
BOOLEAN
Ikev2IsSupportAlg (
IN UINT16 AlgorithmId,
IN UINT8 Type
)
{
UINT8 Index;
switch (Type) {
case IKE_ENCRYPT_TYPE :
for (Index = 0; Index < IKEV2_SUPPORT_ENCRYPT_ALGORITHM_NUM; Index++) {
if (mIkev2EncryptAlgorithmList[Index] == AlgorithmId) {
return TRUE;
}
}
break;
case IKE_AUTH_TYPE :
for (Index = 0; Index < IKEV2_SUPPORT_AUTH_ALGORITHM_NUM; Index++) {
if (mIkev2AuthAlgorithmList[Index] == AlgorithmId) {
return TRUE;
}
}
break;
case IKE_DH_TYPE :
for (Index = 0; Index < IKEV2_SUPPORT_DH_ALGORITHM_NUM; Index++) {
if (mIkev2DhGroupAlgorithmList[Index] == AlgorithmId) {
return TRUE;
}
}
break;
case IKE_PRF_TYPE :
for (Index = 0; Index < IKEV2_SUPPORT_PRF_ALGORITHM_NUM; Index++) {
if (mIkev2PrfAlgorithmList[Index] == AlgorithmId) {
return TRUE;
}
}
}
return FALSE;
}
/**
Get the preferred algorithm types from ProposalData.
@param[in] ProposalData Pointer to related IKEV2_PROPOSAL_DATA.
@param[out] PreferEncryptAlgorithm Output of preferred encrypt algorithm.
@param[out] PreferIntegrityAlgorithm Output of preferred integrity algorithm.
@param[out] PreferPrfAlgorithm Output of preferred PRF algorithm. Only
for IKE SA.
@param[out] PreferDhGroup Output of preferred DH group. Only for
IKE SA.
@param[out] PreferEncryptKeylength Output of preferred encrypt key length
in bytes.
@param[out] IsSupportEsn Output of value about the Extented Sequence
Number is support or not. Only for Child SA.
@param[in] IsChildSa If it is ture, the ProposalData is for IKE
SA. Otherwise the proposalData is for Child SA.
**/
VOID
Ikev2ParseProposalData (
IN IKEV2_PROPOSAL_DATA *ProposalData,
OUT UINT16 *PreferEncryptAlgorithm,
OUT UINT16 *PreferIntegrityAlgorithm,
OUT UINT16 *PreferPrfAlgorithm,
OUT UINT16 *PreferDhGroup,
OUT UINTN *PreferEncryptKeylength,
OUT BOOLEAN *IsSupportEsn,
IN BOOLEAN IsChildSa
)
{
IKEV2_TRANSFORM_DATA *TransformData;
UINT8 TransformIndex;
//
// Check input parameters.
//
if (ProposalData == NULL ||
PreferEncryptAlgorithm == NULL ||
PreferIntegrityAlgorithm == NULL ||
PreferEncryptKeylength == NULL
) {
return;
}
if (IsChildSa) {
if (IsSupportEsn == NULL) {
return;
}
} else {
if (PreferPrfAlgorithm == NULL || PreferDhGroup == NULL) {
return;
}
}
TransformData = (IKEV2_TRANSFORM_DATA *)(ProposalData + 1);
for (TransformIndex = 0; TransformIndex < ProposalData->NumTransforms; TransformIndex++) {
switch (TransformData->TransformType) {
//
// For IKE SA there are four algorithm types. Encryption Algorithm, Pseudo-random Function,
// Integrity Algorithm, Diffie-Hellman Group. For Child SA, there are three algorithm types.
// Encryption Algorithm, Integrity Algorithm, Extended Sequence Number.
//
case IKEV2_TRANSFORM_TYPE_ENCR:
if (*PreferEncryptAlgorithm == 0 && Ikev2IsSupportAlg (TransformData->TransformId, IKE_ENCRYPT_TYPE)) {
//
// Check the attribute value. According to RFC, only Keylength is support.
//
if (TransformData->Attribute.AttrType == IKEV2_ATTRIBUTE_TYPE_KEYLEN) {
//
// If the Keylength is not support, continue to check the next one.
//
if (IpSecGetEncryptKeyLength ((UINT8)TransformData->TransformId) != (UINTN)(TransformData->Attribute.Attr.AttrValue >> 3)){
break;
} else {
*PreferEncryptKeylength = TransformData->Attribute.Attr.AttrValue;
}
}
*PreferEncryptAlgorithm = TransformData->TransformId;
}
break;
case IKEV2_TRANSFORM_TYPE_PRF :
if (!IsChildSa) {
if (*PreferPrfAlgorithm == 0 && Ikev2IsSupportAlg (TransformData->TransformId, IKE_PRF_TYPE)) {
*PreferPrfAlgorithm = TransformData->TransformId;
}
}
break;
case IKEV2_TRANSFORM_TYPE_INTEG :
if (*PreferIntegrityAlgorithm == 0 && Ikev2IsSupportAlg (TransformData->TransformId, IKE_AUTH_TYPE)) {
*PreferIntegrityAlgorithm = TransformData->TransformId;
}
break;
case IKEV2_TRANSFORM_TYPE_DH :
if (!IsChildSa) {
if (*PreferDhGroup == 0 && Ikev2IsSupportAlg (TransformData->TransformId, IKE_DH_TYPE)) {
*PreferDhGroup = TransformData->TransformId;
}
}
break;
case IKEV2_TRANSFORM_TYPE_ESN :
if (IsChildSa) {
if (TransformData->TransformId != 0) {
*IsSupportEsn = TRUE;
}
}
break;
default:
break;
}
TransformData = (IKEV2_TRANSFORM_DATA *)(TransformData + 1);
}
}
/**
Parse the received Initial Exchange Packet.
This function parse the SA Payload and Key Payload to find out the cryptographic
suite for the further IKE negotiation and fill it into the IKE SA Session's
CommonSession->SaParams.
@param[in, out] IkeSaSession Pointer to related IKEV2_SA_SESSION.
@param[in] SaPayload The received packet.
@param[in] Type The received packet IKE header flag.
@retval TRUE If the SA proposal in Packet is acceptable.
@retval FALSE If the SA proposal in Packet is not acceptable.
**/
BOOLEAN
Ikev2SaParseSaPayload (
IN OUT IKEV2_SA_SESSION *IkeSaSession,
IN IKE_PAYLOAD *SaPayload,
IN UINT8 Type
)
{
IKEV2_PROPOSAL_DATA *ProposalData;
UINT8 ProposalIndex;
UINT16 PreferEncryptAlgorithm;
UINT16 PreferIntegrityAlgorithm;
UINT16 PreferPrfAlgorithm;
UINT16 PreferDhGroup;
UINTN PreferEncryptKeylength;
UINT16 EncryptAlgorithm;
UINT16 IntegrityAlgorithm;
UINT16 PrfAlgorithm;
UINT16 DhGroup;
UINTN EncryptKeylength;
BOOLEAN IsMatch;
UINTN SaDataSize;
PreferPrfAlgorithm = 0;
PreferIntegrityAlgorithm = 0;
PreferDhGroup = 0;
PreferEncryptAlgorithm = 0;
PreferEncryptKeylength = 0;
PrfAlgorithm = 0;
IntegrityAlgorithm = 0;
DhGroup = 0;
EncryptAlgorithm = 0;
EncryptKeylength = 0;
IsMatch = FALSE;
if (Type == IKE_HEADER_FLAGS_INIT) {
ProposalData = (IKEV2_PROPOSAL_DATA *)((IKEV2_SA_DATA *)SaPayload->PayloadBuf + 1);
for (ProposalIndex = 0; ProposalIndex < ((IKEV2_SA_DATA *)SaPayload->PayloadBuf)->NumProposals; ProposalIndex++) {
//
// Iterate each proposal to find the perfered one.
//
if (ProposalData->ProtocolId == IPSEC_PROTO_ISAKMP && ProposalData->NumTransforms >= 4) {
//
// Get the preferred algorithms.
//
Ikev2ParseProposalData (
ProposalData,
&PreferEncryptAlgorithm,
&PreferIntegrityAlgorithm,
&PreferPrfAlgorithm,
&PreferDhGroup,
&PreferEncryptKeylength,
NULL,
FALSE
);
if (PreferEncryptAlgorithm != 0 &&
PreferIntegrityAlgorithm != 0 &&
PreferPrfAlgorithm != 0 &&
PreferDhGroup != 0
) {
//
// Find the matched one.
//
IkeSaSession->SessionCommon.SaParams = AllocateZeroPool (sizeof (IKEV2_SA_PARAMS));
ASSERT (IkeSaSession->SessionCommon.SaParams != NULL);
IkeSaSession->SessionCommon.SaParams->EncAlgId = PreferEncryptAlgorithm;
IkeSaSession->SessionCommon.SaParams->EnckeyLen = PreferEncryptKeylength;
IkeSaSession->SessionCommon.SaParams->DhGroup = PreferDhGroup;
IkeSaSession->SessionCommon.SaParams->Prf = PreferPrfAlgorithm;
IkeSaSession->SessionCommon.SaParams->IntegAlgId = PreferIntegrityAlgorithm;
IkeSaSession->SessionCommon.PreferDhGroup = PreferDhGroup;
//
// Save the matched one in IKEV2_SA_DATA for furthure calculation.
//
SaDataSize = sizeof (IKEV2_SA_DATA) +
sizeof (IKEV2_PROPOSAL_DATA) +
sizeof (IKEV2_TRANSFORM_DATA) * 4;
IkeSaSession->SaData = AllocateZeroPool (SaDataSize);
ASSERT (IkeSaSession->SaData != NULL);
IkeSaSession->SaData->NumProposals = 1;
//
// BUGBUG: Suppose the matched proposal only has 4 transforms. If
// The matched Proposal has more than 4 transforms means it contains
// one than one transform with same type.
//
CopyMem (
(IKEV2_PROPOSAL_DATA *) (IkeSaSession->SaData + 1),
ProposalData,
SaDataSize - sizeof (IKEV2_SA_DATA)
);
((IKEV2_PROPOSAL_DATA *) (IkeSaSession->SaData + 1))->ProposalIndex = 1;
return TRUE;
} else {
PreferEncryptAlgorithm = 0;
PreferIntegrityAlgorithm = 0;
PreferPrfAlgorithm = 0;
PreferDhGroup = 0;
PreferEncryptKeylength = 0;
}
}
//
// Point to next Proposal.
//
ProposalData = (IKEV2_PROPOSAL_DATA*)((UINT8*)(ProposalData + 1) +
ProposalData->NumTransforms * sizeof (IKEV2_TRANSFORM_DATA));
}
} else if (Type == IKE_HEADER_FLAGS_RESPOND) {
//
// First check the SA proposal's ProtoctolID and Transform Numbers. Since it is
// the responded SA proposal, suppose it only has one proposal and the transform Numbers
// is 4.
//
ProposalData = (IKEV2_PROPOSAL_DATA *)((IKEV2_SA_DATA *) SaPayload->PayloadBuf + 1);
if (ProposalData->ProtocolId != IPSEC_PROTO_ISAKMP || ProposalData->NumTransforms != 4) {
return FALSE;
}
//
// Get the preferred algorithms.
//
Ikev2ParseProposalData (
ProposalData,
&PreferEncryptAlgorithm,
&PreferIntegrityAlgorithm,
&PreferPrfAlgorithm,
&PreferDhGroup,
&PreferEncryptKeylength,
NULL,
FALSE
);
//
// Check if the Sa proposal data from received packet is in the IkeSaSession->SaData.
//
ProposalData = (IKEV2_PROPOSAL_DATA *) (IkeSaSession->SaData + 1);
for (ProposalIndex = 0; ProposalIndex < IkeSaSession->SaData->NumProposals && (!IsMatch); ProposalIndex++) {
Ikev2ParseProposalData (
ProposalData,
&EncryptAlgorithm,
&IntegrityAlgorithm,
&PrfAlgorithm,
&DhGroup,
&EncryptKeylength,
NULL,
FALSE
);
if (EncryptAlgorithm == PreferEncryptAlgorithm &&
EncryptKeylength == PreferEncryptKeylength &&
IntegrityAlgorithm == PreferIntegrityAlgorithm &&
PrfAlgorithm == PreferPrfAlgorithm &&
DhGroup == PreferDhGroup
) {
IsMatch = TRUE;
} else {
EncryptAlgorithm = 0;
IntegrityAlgorithm = 0;
PrfAlgorithm = 0;
DhGroup = 0;
EncryptKeylength = 0;
}
ProposalData = (IKEV2_PROPOSAL_DATA*)((UINT8*)(ProposalData + 1) +
ProposalData->NumTransforms * sizeof (IKEV2_TRANSFORM_DATA));
}
if (IsMatch) {
IkeSaSession->SessionCommon.SaParams = AllocateZeroPool (sizeof (IKEV2_SA_PARAMS));
ASSERT (IkeSaSession->SessionCommon.SaParams != NULL);
IkeSaSession->SessionCommon.SaParams->EncAlgId = PreferEncryptAlgorithm;
IkeSaSession->SessionCommon.SaParams->EnckeyLen = PreferEncryptKeylength;
IkeSaSession->SessionCommon.SaParams->DhGroup = PreferDhGroup;
IkeSaSession->SessionCommon.SaParams->Prf = PreferPrfAlgorithm;
IkeSaSession->SessionCommon.SaParams->IntegAlgId = PreferIntegrityAlgorithm;
IkeSaSession->SessionCommon.PreferDhGroup = PreferDhGroup;
return TRUE;
}
}
return FALSE;
}
/**
Parse the received Authentication Exchange Packet.
This function parse the SA Payload and Key Payload to find out the cryptographic
suite for the ESP and fill it into the Child SA Session's CommonSession->SaParams.
@param[in, out] ChildSaSession Pointer to IKEV2_CHILD_SA_SESSION related to
this Authentication Exchange.
@param[in] SaPayload The received packet.
@param[in] Type The IKE header's flag of received packet .
@retval TRUE If the SA proposal in Packet is acceptable.
@retval FALSE If the SA proposal in Packet is not acceptable.
**/
BOOLEAN
Ikev2ChildSaParseSaPayload (
IN OUT IKEV2_CHILD_SA_SESSION *ChildSaSession,
IN IKE_PAYLOAD *SaPayload,
IN UINT8 Type
)
{
IKEV2_PROPOSAL_DATA *ProposalData;
UINT8 ProposalIndex;
UINT16 PreferEncryptAlgorithm;
UINT16 PreferIntegrityAlgorithm;
UINTN PreferEncryptKeylength;
BOOLEAN PreferIsSupportEsn;
UINT16 EncryptAlgorithm;
UINT16 IntegrityAlgorithm;
UINTN EncryptKeylength;
BOOLEAN IsSupportEsn;
BOOLEAN IsMatch;
UINTN SaDataSize;
PreferIntegrityAlgorithm = 0;
PreferEncryptAlgorithm = 0;
PreferEncryptKeylength = 0;
IntegrityAlgorithm = 0;
EncryptAlgorithm = 0;
EncryptKeylength = 0;
IsMatch = TRUE;
IsSupportEsn = FALSE;
PreferIsSupportEsn = FALSE;
if (Type == IKE_HEADER_FLAGS_INIT) {
ProposalData = (IKEV2_PROPOSAL_DATA *)((IKEV2_SA_DATA *) SaPayload->PayloadBuf + 1);
for (ProposalIndex = 0; ProposalIndex < ((IKEV2_SA_DATA *) SaPayload->PayloadBuf)->NumProposals; ProposalIndex++) {
//
// Iterate each proposal to find the preferred one.
//
if (ProposalData->ProtocolId == IPSEC_PROTO_IPSEC_ESP && ProposalData->NumTransforms >= 3) {
//
// Get the preferred algorithm.
//
Ikev2ParseProposalData (
ProposalData,
&PreferEncryptAlgorithm,
&PreferIntegrityAlgorithm,
NULL,
NULL,
&PreferEncryptKeylength,
&IsSupportEsn,
TRUE
);
//
// Don't support the ESN now.
//
if (PreferEncryptAlgorithm != 0 &&
PreferIntegrityAlgorithm != 0 &&
!IsSupportEsn
) {
//
// Find the matched one.
//
ChildSaSession->SessionCommon.SaParams = AllocateZeroPool (sizeof (IKEV2_SA_PARAMS));
ASSERT (ChildSaSession->SessionCommon.SaParams != NULL);
ChildSaSession->SessionCommon.SaParams->EncAlgId = PreferEncryptAlgorithm;
ChildSaSession->SessionCommon.SaParams->EnckeyLen = PreferEncryptKeylength;
ChildSaSession->SessionCommon.SaParams->IntegAlgId = PreferIntegrityAlgorithm;
CopyMem (&ChildSaSession->RemotePeerSpi, ProposalData->Spi, sizeof (ChildSaSession->RemotePeerSpi));
//
// Save the matched one in IKEV2_SA_DATA for furthure calculation.
//
SaDataSize = sizeof (IKEV2_SA_DATA) +
sizeof (IKEV2_PROPOSAL_DATA) +
sizeof (IKEV2_TRANSFORM_DATA) * 4;
ChildSaSession->SaData = AllocateZeroPool (SaDataSize);
ASSERT (ChildSaSession->SaData != NULL);
ChildSaSession->SaData->NumProposals = 1;
//
// BUGBUG: Suppose there are 4 transforms in the matched proposal. If
// the matched Proposal has more than 4 transforms that means there
// are more than one transform with same type.
//
CopyMem (
(IKEV2_PROPOSAL_DATA *) (ChildSaSession->SaData + 1),
ProposalData,
SaDataSize - sizeof (IKEV2_SA_DATA)
);
((IKEV2_PROPOSAL_DATA *) (ChildSaSession->SaData + 1))->ProposalIndex = 1;
((IKEV2_PROPOSAL_DATA *) (ChildSaSession->SaData + 1))->Spi = AllocateCopyPool (
sizeof (ChildSaSession->LocalPeerSpi),
&ChildSaSession->LocalPeerSpi
);
ASSERT (((IKEV2_PROPOSAL_DATA *) (ChildSaSession->SaData + 1))->Spi != NULL);
return TRUE;
} else {
PreferEncryptAlgorithm = 0;
PreferIntegrityAlgorithm = 0;
IsSupportEsn = TRUE;
}
}
//
// Point to next Proposal
//
ProposalData = (IKEV2_PROPOSAL_DATA *)((UINT8 *)(ProposalData + 1) +
ProposalData->NumTransforms * sizeof (IKEV2_TRANSFORM_DATA));
}
} else if (Type == IKE_HEADER_FLAGS_RESPOND) {
//
// First check the SA proposal's ProtoctolID and Transform Numbers. Since it is
// the responded SA proposal, suppose it only has one proposal and the transform Numbers
// is 3.
//
ProposalData = (IKEV2_PROPOSAL_DATA *)((IKEV2_SA_DATA *)SaPayload->PayloadBuf + 1);
if (ProposalData->ProtocolId != IPSEC_PROTO_IPSEC_ESP || ProposalData->NumTransforms != 3) {
return FALSE;
}
//
// Get the preferred algorithms.
//
Ikev2ParseProposalData (
ProposalData,
&PreferEncryptAlgorithm,
&PreferIntegrityAlgorithm,
NULL,
NULL,
&PreferEncryptKeylength,
&PreferIsSupportEsn,
TRUE
);
ProposalData = (IKEV2_PROPOSAL_DATA *) (ChildSaSession->SaData + 1);
for (ProposalIndex = 0; ProposalIndex < ChildSaSession->SaData->NumProposals && (!IsMatch); ProposalIndex++) {
Ikev2ParseProposalData (
ProposalData,
&EncryptAlgorithm,
&IntegrityAlgorithm,
NULL,
NULL,
&EncryptKeylength,
&IsSupportEsn,
TRUE
);
if (EncryptAlgorithm == PreferEncryptAlgorithm &&
EncryptKeylength == PreferEncryptKeylength &&
IntegrityAlgorithm == PreferIntegrityAlgorithm &&
IsSupportEsn == PreferIsSupportEsn
) {
IsMatch = TRUE;
} else {
PreferEncryptAlgorithm = 0;
PreferIntegrityAlgorithm = 0;
IsSupportEsn = TRUE;
}
ProposalData = (IKEV2_PROPOSAL_DATA*)((UINT8*)(ProposalData + 1) +
ProposalData->NumTransforms * sizeof (IKEV2_TRANSFORM_DATA));
}
ProposalData = (IKEV2_PROPOSAL_DATA *)((IKEV2_SA_DATA *)SaPayload->PayloadBuf + 1);
if (IsMatch) {
ChildSaSession->SessionCommon.SaParams = AllocateZeroPool (sizeof (IKEV2_SA_PARAMS));
ASSERT (ChildSaSession->SessionCommon.SaParams != NULL);
ChildSaSession->SessionCommon.SaParams->EncAlgId = PreferEncryptAlgorithm;
ChildSaSession->SessionCommon.SaParams->EnckeyLen = PreferEncryptKeylength;
ChildSaSession->SessionCommon.SaParams->IntegAlgId = PreferIntegrityAlgorithm;
CopyMem (&ChildSaSession->RemotePeerSpi, ProposalData->Spi, sizeof (ChildSaSession->RemotePeerSpi));
return TRUE;
}
}
return FALSE;
}
/**
Generate Key buffer from fragments.
If the digest length of specified HashAlgId is larger than or equal with the
required output key length, derive the key directly. Otherwise, Key Material
needs to be PRF-based concatenation according to 2.13 of RFC 4306:
prf+ (K,S) = T1 | T2 | T3 | T4 | ..., T1 = prf (K, S | 0x01),
T2 = prf (K, T1 | S | 0x02), T3 = prf (K, T2 | S | 0x03),T4 = prf (K, T3 | S | 0x04)
then derive the key from this key material.
@param[in] HashAlgId The Hash Algorithm ID used to generate key.
@param[in] HashKey Pointer to a key buffer which contains hash key.
@param[in] HashKeyLength The length of HashKey in bytes.
@param[in, out] OutputKey Pointer to buffer which is used to receive the
output key.
@param[in] OutputKeyLength The length of OutPutKey buffer.
@param[in] Fragments Pointer to the data to be used to generate key.
@param[in] NumFragments The numbers of the Fragement.
@retval EFI_SUCCESS The operation complete successfully.
@retval EFI_INVALID_PARAMETER If NumFragments is zero.
@retval EFI_OUT_OF_RESOURCES If the required resource can't be allocated.
@retval Others The operation is failed.
**/
EFI_STATUS
Ikev2SaGenerateKey (
IN UINT8 HashAlgId,
IN UINT8 *HashKey,
IN UINTN HashKeyLength,
IN OUT UINT8 *OutputKey,
IN UINTN OutputKeyLength,
IN PRF_DATA_FRAGMENT *Fragments,
IN UINTN NumFragments
)
{
EFI_STATUS Status;
PRF_DATA_FRAGMENT LocalFragments[3];
UINT8 *Digest;
UINTN DigestSize;
UINTN Round;
UINTN Index;
UINTN AuthKeyLength;
UINTN FragmentsSize;
UINT8 TailData;
Status = EFI_SUCCESS;
if (NumFragments == 0) {
return EFI_INVALID_PARAMETER;
}
LocalFragments[0].Data = NULL;
LocalFragments[1].Data = NULL;
LocalFragments[2].Data = NULL;
AuthKeyLength = IpSecGetHmacDigestLength (HashAlgId);
DigestSize = AuthKeyLength;
Digest = AllocateZeroPool (AuthKeyLength);
if (Digest == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// If the required output key length is less than the digest size,
// copy the digest into OutputKey.
//
if (OutputKeyLength <= DigestSize) {
Status = IpSecCryptoIoHmac (
HashAlgId,
HashKey,
HashKeyLength,
(HASH_DATA_FRAGMENT *) Fragments,
NumFragments,
Digest,
DigestSize
);
if (EFI_ERROR (Status)) {
goto Exit;
}
CopyMem (OutputKey, Digest, OutputKeyLength);
goto Exit;
}
//
//Otherwise, Key Material need to be PRF-based concatenation according to 2.13
//of RFC 4306: prf+ (K,S) = T1 | T2 | T3 | T4 | ..., T1 = prf (K, S | 0x01),
//T2 = prf (K, T1 | S | 0x02), T3 = prf (K, T2 | S | 0x03),T4 = prf (K, T3 | S | 0x04)
//then derive the key from this key material.
//
FragmentsSize = 0;
for (Index = 0; Index < NumFragments; Index++) {
FragmentsSize = FragmentsSize + Fragments[Index].DataSize;
}
LocalFragments[1].Data = AllocateZeroPool (FragmentsSize);
ASSERT (LocalFragments[1].Data != NULL);
LocalFragments[1].DataSize = FragmentsSize;
//
// Copy all input fragments into LocalFragments[1];
//
FragmentsSize = 0;
for (Index = 0; Index < NumFragments; Index++) {
CopyMem (
LocalFragments[1].Data + FragmentsSize,
Fragments[Index].Data,
Fragments[Index].DataSize
);
FragmentsSize = FragmentsSize + Fragments[Index].DataSize;
}
//
// Prepare 0x01 as the first tail data.
//
TailData = 0x01;
LocalFragments[2].Data = &TailData;
LocalFragments[2].DataSize = sizeof (TailData);
//
// Allocate buffer for the first fragment
//
LocalFragments[0].Data = AllocateZeroPool (AuthKeyLength);
ASSERT (LocalFragments[0].Data != NULL);
LocalFragments[0].DataSize = AuthKeyLength;
Round = (OutputKeyLength - 1) / AuthKeyLength + 1;
for (Index = 0; Index < Round; Index++) {
Status = IpSecCryptoIoHmac (
HashAlgId,
HashKey,
HashKeyLength,
(HASH_DATA_FRAGMENT *)(Index == 0 ? &LocalFragments[1] : LocalFragments),
Index == 0 ? 2 : 3,
Digest,
DigestSize
);
if (EFI_ERROR(Status)) {
goto Exit;
}
CopyMem (
LocalFragments[0].Data,
Digest,
DigestSize
);
if (OutputKeyLength > DigestSize * (Index + 1)) {
CopyMem (
OutputKey + Index * DigestSize,
Digest,
DigestSize
);
LocalFragments[0].DataSize = DigestSize;
TailData ++;
} else {
//
// The last round
//
CopyMem (
OutputKey + Index * DigestSize,
Digest,
OutputKeyLength - Index * DigestSize
);
}
}
Exit:
//
// Only First and second Framgement Data need to be freed.
//
for (Index = 0 ; Index < 2; Index++) {
if (LocalFragments[Index].Data != NULL) {
FreePool (LocalFragments[Index].Data);
}
}
if (Digest != NULL) {
FreePool (Digest);
}
return Status;
}