Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2969 lines
91 KiB

// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
//
// Copyright (c) 1985-2000 Microsoft Corporation
//
// This file is part of the Microsoft Research IPv6 Network Protocol Stack.
// You should have received a copy of the Microsoft End-User License Agreement
// for this software along with this release; see the file "license.txt".
// If not, please see http://www.research.microsoft.com/msripv6/license.htm,
// or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
//
// Abstract:
//
// IP security routines for Internet Protocol Version 6.
//
#include "oscfg.h"
#include "ndis.h"
#include "ip6imp.h"
#include "ip6def.h"
#include "ipsec.h"
#include "security.h"
#include "alloca.h"
//
// Global Variables.
//
KSPIN_LOCK IPSecLock;
SecurityPolicy *SecurityPolicyList;
SecurityAssociation *SecurityAssociationList = NULL;
ulong SecurityStateValidationCounter;
uchar Zero[max(MAXUCHAR, MAX_RESULT_SIZE)] = {0};
SecurityAlgorithm AlgorithmTable[NUM_ALGORITHMS];
#ifdef IPSEC_DEBUG
void dump_encoded_mesg(uchar *buff, uint len)
{
uint i, cnt = 0;
uint bytes = 0;
uint wrds = 0;
uchar *buf = buff;
for (i = 0; i < len; i++) {
if (wrds == 0) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"&%02x: ", cnt));
}
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"%02x", *buf));
buf++;
bytes++;
if (!(bytes % 4)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
" "));
bytes = 0;
}
wrds++;
if (!(wrds % 16)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"\n"));
wrds = 0;
cnt += 16;
}
}
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"\n"));
}
void DumpKey(uchar *buff, uint len)
{
uint i;
for (i = 0; i < len; i++) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"|%c", buff[i]));
}
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"|\n"));
}
#endif
//* SPCheckAddr - Compare IP address in packet to IP address in policy.
//
// SPAddrField specifies the type of comparison:
// WILDCARD_VALUE, SINGLE_VALUE, or RANGE_VALUE.
//
int
SPCheckAddr(
IPv6Addr *PacketAddr,
uint SPAddrField,
IPv6Addr *SPAddr,
IPv6Addr *SPAddrData)
{
int Result;
switch (SPAddrField) {
case WILDCARD_VALUE:
//
// Always a match since the address is don't care.
//
Result = TRUE;
break;
case SINGLE_VALUE:
//
// Check if the address of the packet matches the SP selector.
//
Result = IP6_ADDR_EQUAL(PacketAddr, SPAddr);
break;
case RANGE_VALUE:
//
// Check if the address is in the specified selector range.
//
Result = (IP6_ADDR_LTEQ(SPAddr, PacketAddr) &&
IP6_ADDR_LTEQ(PacketAddr, SPAddrData));
break;
default:
//
// Should never happen.
//
ASSERT(!"SPCheckAddr: invalid SPAddrField value");
Result = FALSE;
}
return Result;
}
//* SPCheckPort - Compare port in packet to port in policy.
//
uint
SPCheckPort(
ushort PacketPort,
uint SPPortField,
ushort SPPort,
ushort SPPortData)
{
uint Result = FALSE;
switch (SPPortField) {
case WILDCARD_VALUE:
// Always a match since the port is don't care.
Result = TRUE;
break;
case SINGLE_VALUE:
// Check if the port of the packet matches the SP selector.
if (PacketPort == SPPort) {
Result = TRUE;
}
break;
case RANGE_VALUE:
// Check if port is between range.
if (PacketPort >= SPPort && PacketPort <= SPPortData) {
Result = TRUE;
}
break;
default:
//
// Should never happen.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"SPCheckPort: invalid value for SPPortField (%u)\n",
SPPortField));
}
return Result;
}
//* ReleaseSA
//
// Releases a reference to an SA.
//
void
ReleaseSA(SecurityAssociation *SA)
{
if (InterlockedDecrement(&SA->RefCnt) == 0) {
//
// No more references, so deallocate it.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE,
"Freeing SA: %p\n", SA));
RemoveSecurityAssociation(SA);
ExFreePool(SA);
}
}
//* DeleteSA - Invalidate a security association.
//
// The SA is removed from the SA chain. All pointers from the SA entry are
// removed and the related reference counts decremented. The SP pointers to
// the SA can be removed; however, there could be references from the temp SA
// holders used during IPSec traffic processing.
//
// The temp SA references (IPSecProc and SALinkage) remove the references
// when traffic processing is done. The case can occur where the SA is
// deleted but the temp SA holder still has a reference. In that case,
// the SA is not removed from the global list.
//
int
DeleteSA(
SecurityAssociation *SA)
{
SecurityAssociation *FirstSA, *PrevSA = NULL;
uint Direction;
//
// Get the start of the SA Chain.
//
Direction = SA->DirectionFlag;
if (Direction == INBOUND) {
FirstSA = SA->SecPolicy->InboundSA;
} else {
FirstSA = SA->SecPolicy->OutboundSA;
}
//
// Find the invalid SA and keep track of the SA before it.
//
while (FirstSA != SA) {
PrevSA = FirstSA;
if (PrevSA == NULL) {
// This is a problem it should never happen.
// REVIEW: Can we change this to an ASSERT?
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
"DeleteSA: SA was not found\n"));
return FALSE;
}
FirstSA = FirstSA->ChainedSecAssoc;
}
//
// Remove the SA from the SA Chain.
//
// Check if the invalid SA is the First SA of the chain.
if (PrevSA == NULL) {
// The invalid SA is the first SA so the SP needs to be adjusted.
if (Direction == INBOUND) {
SA->SecPolicy->InboundSA = FirstSA->ChainedSecAssoc;
} else {
SA->SecPolicy->OutboundSA = FirstSA->ChainedSecAssoc;
}
} else {
// Just a entry in the Chain.
PrevSA->ChainedSecAssoc = FirstSA->ChainedSecAssoc;
}
// Decrement the reference count of the SP.
SA->SecPolicy->RefCnt--;
// Remove the reference to the SP.
SA->SecPolicy = NULL;
SA->Valid = SA_REMOVED;
// Decrement the reference count of the SA.
ReleaseSA(SA);
InvalidateSecurityState();
return TRUE;
}
//* RemoveSecurityPolicy
//
// Remove a policy from the global list.
// References are not held for the global list links.
//
void
RemoveSecurityPolicy(SecurityPolicy *SP)
{
if (SP->Prev == NULL) {
SecurityPolicyList = SP->Next;
} else {
SP->Prev->Next = SP->Next;
}
if (SP->Next != NULL) {
SP->Next->Prev = SP->Prev;
}
}
//* DeleteSP - Removes an SP entry from the kernel.
//
// The removal of an SP makes all the SAs belonging to the SP invalid.
// Unlike the SA removal, this removes every reference to the invalid SP.
// Therefore, a check does not need to be made to ensure the SP is valid.
//
// Called with the security lock held.
//
int
DeleteSP(
SecurityPolicy *SP)
{
SecurityPolicy *IFSP, *PrevSP = NULL;
//
// Remove the SP's SAs.
//
while (SP->InboundSA != NULL) {
if (!(DeleteSA(SP->InboundSA))) {
return FALSE;
}
}
while (SP->OutboundSA != NULL) {
if (!(DeleteSA(SP->OutboundSA))) {
return FALSE;
}
}
//
// Take it off the global list.
//
RemoveSecurityPolicy(SP);
// Check if this is part of an SA bundle.
if (SP->SABundle != NULL) {
SecurityPolicy *PrevSABundle, *NextSABundle;
//
// The SP pointer being removed is a middle or first SABundle pointer.
//
PrevSABundle = SP->PrevSABundle;
NextSABundle = SP->SABundle;
NextSABundle->PrevSABundle = PrevSABundle;
if (PrevSABundle == NULL) {
// First SABundle pointer.
NextSABundle->RefCnt--;
} else {
//
// Clean up the SABundle deletion affects on other SP pointers.
//
while (PrevSABundle != NULL) {
PrevSABundle->NestCount--;
PrevSABundle->SABundle = NextSABundle;
NextSABundle = PrevSABundle;
PrevSABundle = PrevSABundle->PrevSABundle;
}
SP->RefCnt--;
}
SP->RefCnt--;
}
//
// Check if anything else is referencing the invalid SP.
// All the interfaces and SA references have been removed.
// The only thing left are SABundle pointers.
//
if (SP->RefCnt != 0) {
SecurityPolicy *PrevSABundle, *NextSABundle;
//
// The SP pointer being removed is the last of the bundle pointers.
//
PrevSABundle = SP->PrevSABundle;
NextSABundle = SP->SABundle;
ASSERT(PrevSABundle != NULL);
ASSERT(NextSABundle == NULL);
PrevSABundle->RefCnt--;
//
// Cleanup the SABundle deletion affects on other SP pointers.
//
while (PrevSABundle != NULL) {
PrevSABundle->NestCount--;
PrevSABundle->SABundle = NextSABundle;
NextSABundle = PrevSABundle;
PrevSABundle = PrevSABundle->PrevSABundle;
}
SP->RefCnt--;
// Now the reference count better be zero.
if (SP->RefCnt != 0) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
"DeleteSP: The SP list is corrupt!\n"));
return FALSE;
}
}
// Free the memory.
ExFreePool(SP);
InvalidateSecurityState();
return TRUE;
}
//* RemoveSecurityAssociation
//
// Remove an association from the global list.
// References are not held for the global list links.
//
void
RemoveSecurityAssociation(SecurityAssociation *SA)
{
if (SA->Prev == NULL) {
SecurityAssociationList = SA->Next;
} else {
SA->Prev->Next = SA->Next;
}
if (SA->Next != NULL) {
SA->Next->Prev = SA->Prev;
}
}
//* FreeIPSecToDo
//
void
FreeIPSecToDo(
IPSecProc *IPSecToDo,
uint Number)
{
uint i;
for (i = 0; i < Number; i++) {
ReleaseSA(IPSecToDo[i].SA);
}
ExFreePool(IPSecToDo);
}
//* InboundSAFind - find a SA in the Security Association Database.
//
// A Security Association on a receiving machine is uniquely identified
// by the tuple of SPI, IP Destination, and security protocol.
//
// REVIEW: Since we can choose our SPI's to be system-wide unique, we
// REVIEW: could do the lookup solely via SPI and just verify the others.
//
// REVIEW: Should we do our IP Destination lookup via ADE? Faster.
//
SecurityAssociation *
InboundSAFind(
ulong SPI, // Security Parameter Index.
IPv6Addr *Dest, // Destination address.
uint Protocol) // Security protocol in use (e.g. AH or ESP).
{
SecurityAssociation *SA;
KIRQL OldIrql;
// Get Security Lock.
KeAcquireSpinLock(&IPSecLock, &OldIrql);
// Start at the first SA entry.
for (SA = SecurityAssociationList; SA != NULL; SA = SA->Next) {
// Check SPI.
if (SPI == SA->SPI) {
// Check destination IP address and IPSec protocol.
if (IP6_ADDR_EQUAL(Dest, &SA->SADestAddr) &&
(Protocol == SA->IPSecProto)) {
// Check direction.
if (SA->DirectionFlag == INBOUND) {
// Check if the SA entry is valid.
if (SA->Valid == SA_VALID) {
AddRefSA(SA);
break;
}
// Not valid so continue checking.
continue;
}
}
}
}
// Release lock.
KeReleaseSpinLock(&IPSecLock, OldIrql);
return SA;
}
//* InboundSALookup - Check the matched SP for a matching SA.
//
// In the SABundle case, this function is called recursively to compare all
// the SA entries. Note, the selectors are not compared for SABundles.
//
uint
InboundSALookup(
SecurityPolicy *SP,
SALinkage *SAPerformed)
{
SecurityAssociation *SA;
uint Result = FALSE;
for (SA = SP->InboundSA; SA != NULL; SA = SA->ChainedSecAssoc) {
if (SA == SAPerformed->This && SA->DirectionFlag == INBOUND) {
//
// Check if the SP entry is a bundle.
//
if (SP->SABundle != NULL && SAPerformed->Next != NULL) {
// Recursive call.
if (InboundSALookup(SP->SABundle, SAPerformed->Next)) {
Result = TRUE;
break;
}
} else if (SP->SABundle == NULL && SAPerformed->Next == NULL) {
// Found a match and no bundle to check.
if (SA->Valid == SA_VALID) {
Result = TRUE;
} else {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
"InboundSALookup: Invalid SA\n"));
}
break;
} else {
// SAs in packet disagree with SABundle so no match.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
"InboundSALookup: SA seen disagrees with SA "
"in SABundle\n"));
break;
}
}
}
return Result;
}
//* InboundSecurityCheck - IPSec processing verification.
//
// This function is called from the transport layer. The policy selectors
// are compared with the packet to find a match. The search continues
// until there is a match.
//
// The RFC says that the inbound SPD does not need to be ordered.
// However if that is the case, then Bypass and discard mode couldn't
// be used to quickly handle a packet. Also since most of the SPs are
// bidirectional, the SP entries are ordered. We require the administrator
// to order the policies.
//
int
InboundSecurityCheck(
IPv6Packet *Packet,
ushort TransportProtocol,
ushort SourcePort,
ushort DestPort,
Interface *IF)
{
SecurityPolicy *SP;
int Result = FALSE;
KIRQL OldIrql;
//
// Get IPSec lock then get interface lock.
// REVIEW: Do we still need to grab the IF lock here?
//
KeAcquireSpinLock(&IPSecLock, &OldIrql);
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
for (SP = SecurityPolicyList; SP != NULL; SP = SP->Next) {
// Check Interface.
if ((SP->IFIndex != 0) && (SP->IFIndex != IF->Index))
continue;
// Check Direction of SP.
if (!(SP->DirectionFlag == INBOUND ||
SP->DirectionFlag == BIDIRECTIONAL)) {
continue;
}
// Check Remote Address.
if (!SPCheckAddr(AlignAddr(&Packet->IP->Source), SP->RemoteAddrField,
&SP->RemoteAddr, &SP->RemoteAddrData)) {
continue;
}
// Check Local Address.
if (!SPCheckAddr(AlignAddr(&Packet->IP->Dest), SP->LocalAddrField,
&SP->LocalAddr, &SP->LocalAddrData)) {
continue;
}
// Check Transport Protocol.
if (SP->TransportProto == NONE) {
// None so protocol passed.
} else {
if (SP->TransportProto != TransportProtocol) {
continue;
} else {
// Check Remote Port.
if (!SPCheckPort(SourcePort, SP->RemotePortField,
SP->RemotePort, SP->RemotePortData)) {
continue;
}
// Check Local Port.
if (!SPCheckPort(DestPort, SP->LocalPortField,
SP->LocalPort, SP->LocalPortData)) {
continue;
}
}
}
// Check if the packet should be dropped.
if (SP->SecPolicyFlag == IPSEC_DISCARD) {
//
// Packet is dropped by transport layer.
// This essentially denies traffic.
//
break;
}
// Check if packet bypassed IPSec processing.
if (Packet->SAPerformed == NULL) {
//
// Check if this is bypass mode.
//
if (SP->SecPolicyFlag == IPSEC_BYPASS) {
// Packet is okay to be processed by transport layer.
Result = TRUE;
break;
}
// Check other policies, may change this to dropping later.
continue;
}
//
// Getting here means the packet saw an SA.
//
// Check IPSec mode.
if (SP->IPSecSpec.Mode != Packet->SAPerformed->Mode) {
//
// Wrong mode for this traffic drop packet.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"InboundSecurityCheck: Wrong IPSec mode for traffic "
"Policy #%d\n", SP->Index));
break;
}
// Check SA pointer.
if (!InboundSALookup(SP, Packet->SAPerformed)) {
//
// SA lookup failed.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"InboundSecurityCheck: SA lookup failed Policy #%d\n",
SP->Index));
break;
}
// Successful verification of IPSec.
Result = TRUE;
break;
}
// Release locks.
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
KeReleaseSpinLock(&IPSecLock, OldIrql);
return Result;
}
//* OutboundSALookup - Find a SA for the matching SP.
//
// This function is called after an SP match is found. The SAs associated
// with the SP are searched for a match. If match is found, a check to see
// if the SP contains a bundle is done. A bundle causes a similar lookup.
// If any of the bundle SAs are not found, the lookup is a failure.
//
IPSecProc *
OutboundSALookup(
IPv6Addr *SourceAddr,
IPv6Addr *DestAddr,
ushort TransportProtocol,
ushort DestPort,
ushort SourcePort,
SecurityPolicy *SP,
uint *Action)
{
SecurityAssociation *SA;
uint i;
uint BundleCount = 0;
IPSecProc *IPSecToDo = NULL;
*Action = LOOKUP_DROP;
//
// Find the SA entry associated with the found SP entry.
// If there is a bundle, a subsequent search finds the bundled SAs.
//
for (SA = SP->OutboundSA; SA != NULL; SA = SA->ChainedSecAssoc) {
if (SP->RemoteAddrSelector == PACKET_SELECTOR) {
// Check Remote Address.
if (!IP6_ADDR_EQUAL(DestAddr, &SA->DestAddr)) {
continue;
}
}
if (SP->LocalAddrSelector == PACKET_SELECTOR) {
// Check Remote Address.
if (!IP6_ADDR_EQUAL(SourceAddr, &SA->SrcAddr)) {
continue;
}
}
if (SP->RemotePortSelector == PACKET_SELECTOR) {
if (DestPort != SA->DestPort) {
continue;
}
}
if (SP->LocalPortSelector == PACKET_SELECTOR) {
if (SourcePort != SA->SrcPort) {
continue;
}
}
if (SP->TransportProtoSelector == PACKET_SELECTOR) {
if (TransportProtocol != SA->TransportProto) {
continue;
}
}
// Check if the SA is valid.
if (SA->Valid != SA_VALID) {
// SA is invalid continue checking.
continue;
}
//
// Match found.
//
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Send using SP->Index=%d, SA->Index=%d\n",
SP->Index, SA->Index));
#endif
BundleCount = SP->NestCount;
// Allocate the IPSecToDo array.
IPSecToDo = ExAllocatePool(NonPagedPool,
(sizeof *IPSecToDo) * BundleCount);
if (IPSecToDo == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"OutboundSALookup: "
"Couldn't allocate memory for IPSecToDo!?!\n"));
return NULL;
}
//
// Fill in IPSecToDo first entry.
//
IPSecToDo[0].SA = SA;
AddRefSA(SA);
IPSecToDo[0].Mode = SP->IPSecSpec.Mode;
IPSecToDo[0].BundleSize = SP->NestCount;
*Action = LOOKUP_CONT;
break;
} // end of for (SA = SP->OutboundSA; ...)
// Check if there is a bundled SA.
for (i = 1; i < BundleCount; i++) {
*Action = LOOKUP_DROP;
// Check to make sure the bundle pointer is not null (safety check).
if (SP->SABundle == NULL) {
// This is bad so exit loop.
// Free IPSecToDo memory.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"OutboundSALookup: SP entry %d SABundle pointer is "
"NULL\n", SP->Index));
FreeIPSecToDo(IPSecToDo, i);
break;
}
SP = SP->SABundle;
// Search through the SAs for this SP.
for (SA = SP->OutboundSA; SA != NULL; SA = SA->ChainedSecAssoc) {
if (SP->RemoteAddrSelector == PACKET_SELECTOR) {
// Check Remote Address.
if (!IP6_ADDR_EQUAL(DestAddr, &SA->DestAddr)) {
continue;
}
}
if (SP->LocalAddrSelector == PACKET_SELECTOR) {
// Check Remote Address.
if (!IP6_ADDR_EQUAL(SourceAddr, &SA->SrcAddr)) {
continue;
}
}
if (SP->RemotePortSelector == PACKET_SELECTOR) {
if (DestPort != SA->DestPort) {
continue;
}
}
if (SP->LocalPortSelector == PACKET_SELECTOR) {
if (SourcePort != SA->SrcPort) {
continue;
}
}
if (SP->TransportProtoSelector == PACKET_SELECTOR) {
if (TransportProtocol != SA->TransportProto) {
continue;
}
}
// Check if the SA is valid.
if (SA->Valid != SA_VALID) {
// SA is invalid continue checking.
continue;
}
//
// Match found.
//
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Send using SP->Index=%d, SA->Index=%d\n",
SP->Index, SA->Index));
#endif
//
// Fill in IPSecToDo entry.
//
IPSecToDo[i].SA = SA;
AddRefSA(SA);
IPSecToDo[i].Mode = SP->IPSecSpec.Mode;
IPSecToDo[i].BundleSize = SP->NestCount;
*Action = LOOKUP_CONT;
break;
} // end of for (SA = SP->OutboundSA; ...)
// Check if the match was found.
if (*Action == LOOKUP_DROP) {
// No match so free IPSecToDo memory.
FreeIPSecToDo(IPSecToDo, i);
break;
}
} // end of for (i = 1; ...)
return IPSecToDo;
}
//* OutboundSPLookup - Do the IPSec processing associated with an outbound SP.
//
// This function is called from the transport layer to find an appropriate
// SA or SABundle associated with the traffic. The Outbound SPD is sorted
// so the first SP found is for this traffic.
//
IPSecProc *
OutboundSPLookup(
IPv6Addr *SourceAddr, // Source Address.
IPv6Addr *DestAddr, // Destination Address.
ushort TransportProtocol, // Transport Protocol.
ushort SourcePort, // Source Port.
ushort DestPort, // Destination Port.
Interface *IF, // Interface Pointer.
uint *Action) // Action to do.
{
SecurityPolicy *SP;
KIRQL OldIrql;
IPSecProc *IPSecToDo;
IPSecToDo = NULL;
*Action = LOOKUP_DROP;
//
// Get IPSec lock then get interface lock.
// REVIEW: Do we still need to grab the IF lock here?
//
KeAcquireSpinLock(&IPSecLock, &OldIrql);
KeAcquireSpinLockAtDpcLevel(&IF->Lock);
for (SP = SecurityPolicyList; SP != NULL; SP = SP->Next) {
// Check Interface.
if ((SP->IFIndex != 0) && (SP->IFIndex != IF->Index))
continue;
// Check Direction of SP.
if (!(SP->DirectionFlag == OUTBOUND ||
SP->DirectionFlag == BIDIRECTIONAL)) {
continue;
}
// Check Remote Address.
if (!SPCheckAddr(DestAddr, SP->RemoteAddrField,
&SP->RemoteAddr, &SP->RemoteAddrData)) {
continue;
}
// Check Local Address.
if (!SPCheckAddr(SourceAddr, SP->LocalAddrField,
&SP->LocalAddr, &SP->LocalAddrData)) {
continue;
}
// Check Transport Protocol.
if (SP->TransportProto == NONE) {
// None so protocol passed.
} else {
if (SP->TransportProto != TransportProtocol) {
continue;
} else {
// Check Remote Port.
if (!SPCheckPort(DestPort, SP->RemotePortField,
SP->RemotePort, SP->RemotePortData)) {
continue;
}
// Check Local Port.
if (!SPCheckPort(SourcePort, SP->LocalPortField,
SP->LocalPort, SP->LocalPortData)) {
continue;
}
}
}
//
// Check IPSec Action.
//
if (SP->SecPolicyFlag == IPSEC_APPLY) {
// Search for an SA entry that matches.
IPSecToDo = OutboundSALookup(SourceAddr, DestAddr,
TransportProtocol, DestPort,
SourcePort, SP, Action);
if (IPSecToDo == NULL) {
// No SA was found for the outgoing traffic.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"OutboundSPLookup: No SA found for SP entry %d\n",
SP->Index));
*Action = LOOKUP_DROP;
}
} else {
if (SP->SecPolicyFlag == IPSEC_DISCARD) {
// Packet is dropped.
IPSecToDo = NULL;
*Action = LOOKUP_DROP;
} else {
//
// This is Bypass or "App determines" mode.
// REVIEW: What is the app determine mode?
//
IPSecToDo = NULL;
*Action = LOOKUP_BYPASS;
}
}
break;
}
// Release locks.
KeReleaseSpinLockFromDpcLevel(&IF->Lock);
KeReleaseSpinLock(&IPSecLock, OldIrql);
return IPSecToDo;
}
//* PerformDeferredAHProcessing - Helper for AuthenticationHeaderReceive
//
// This routine handles processing the AH authentication algorithm over
// a given extension header once we know which header logically follows it.
//
void
PerformDeferredAHProcessing(
SecurityAlgorithm *Alg, // Authentication algorithm to use.
void *Context, // Context to use with algorithm.
uchar *Key, // Key to use with algorithm.
uint AmountSkipped, // Size of headers not included in AH validation.
void *Data, // Start of header we're currently processing.
uchar ThisHeader, // Which header we're currently processing.
uchar NextHeader) // Header logically following this one.
{
uint Dummy;
ushort PayloadLength;
switch(ThisHeader) {
case IP_PROTOCOL_V6: {
IPv6Header UNALIGNED *IP = (IPv6Header UNALIGNED *)Data;
//
// REVIEW: Cache IPv6 header so we can give it to Operate as a single
// REVIEW: chunk and avoid all these individual calls? More efficient?
//
// In VersClassFlow, only the IP version is immutable.
Dummy = IP_VERSION;
//
// For non-jumbograms, the payload length needs to be altered to
// reflect the lack of those headers which aren't included in the
// authentication check.
//
PayloadLength = net_short(IP->PayloadLength);
if (PayloadLength != 0) {
PayloadLength = PayloadLength - AmountSkipped;
}
PayloadLength = net_short(PayloadLength);
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"\nAH Receive Data:\n"));
dump_encoded_mesg((uchar *)&Dummy, 4);
dump_encoded_mesg((uchar *)&PayloadLength, 2);
dump_encoded_mesg((uchar *)&NextHeader, 1);
#endif
(*Alg->Operate)(Context, Key, (uchar *)&Dummy, 4);
(*Alg->Operate)(Context, Key, (uchar *)&PayloadLength, 2);
(*Alg->Operate)(Context, Key, (uchar *)&NextHeader, 1);
Dummy = 0; // Hop Limit is mutable.
#ifdef IPSEC_DEBUG
dump_encoded_mesg((uchar *)&Dummy, 1);
dump_encoded_mesg((uchar *)&IP->Source, 2 * sizeof(IPv6Addr));
#endif
(*Alg->Operate)(Context, Key, (uchar *)&Dummy, 1);
(*Alg->Operate)(Context, Key, (uchar *)&IP->Source,
2 * sizeof(IPv6Addr));
break;
}
case IP_PROTOCOL_HOP_BY_HOP:
case IP_PROTOCOL_DEST_OPTS: {
IPv6OptionsHeader UNALIGNED *Ext;
uint HdrLen, Amount;
uchar *Start, *Current;
//
// The options headers have the NextHeader field as the first byte.
//
ASSERT(FIELD_OFFSET(IPv6OptionsHeader, NextHeader) == 0);
//
// First feed the NextHeader field into the algorithm.
// We use the one that logically follows, not the one in the header.
//
#ifdef IPSEC_DEBUG
dump_encoded_mesg(&NextHeader, 1);
#endif
(*Alg->Operate)(Context, Key, &NextHeader, 1);
//
// Now feed the rest of this header into the algorithm.
// This includes the remainder of the base header and any
// non-mutable options. For mutable options, we feed the
// algorithm with the equivalent number of zeroes.
//
Ext = (IPv6OptionsHeader UNALIGNED *)Data;
HdrLen = (Ext->HeaderExtLength + 1) * EXT_LEN_UNIT;
Start = (uchar *)Data + 1;
Current = (uchar *)Data + sizeof(IPv6OptionsHeader);
HdrLen -= sizeof(IPv6OptionsHeader);
while (HdrLen) {
if (*Current == OPT6_PAD_1) {
//
// This is the special one byte pad option. Immutable.
//
Current++;
HdrLen--;
continue;
}
if ((*Current == OPT6_JUMBO_PAYLOAD) && (AmountSkipped != 0 )) {
//
// Special case for jumbo payload option where we have to
// update the payload length to reflect skipped headers.
//
//
// First feed in everything up to the option data.
//
Amount = (uint)(Current - Start) + sizeof(OptionHeader);
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Start, Amount);
#endif
(*Alg->Operate)(Context, Key, Start, Amount);
//
// Adjust the payload length before feeding it in.
//
Current += sizeof(OptionHeader);
Dummy = net_long(net_long(*(ulong *)Current) - AmountSkipped);
#ifdef IPSEC_DEBUG
dump_encoded_mesg((uchar *)&Dummy, 4);
#endif
(*Alg->Operate)(Context, Key, (uchar *)&Dummy, 4);
HdrLen -= sizeof(OptionHeader) + sizeof(ulong);
Current += sizeof(ulong);
Start = Current;
continue;
}
if (OPT6_ISMUTABLE(*Current)) {
//
// This option's data is mutable. Everything preceeding
// the option data is not.
//
Amount = (uint)(Current - Start) + 2; // Immutable amount.
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Start, Amount);
#endif
(*Alg->Operate)(Context, Key, Start, Amount);
Current++; // Now on option data length byte.
Amount = *Current; // Mutable amount.
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Zero, Amount);
#endif
(*Alg->Operate)(Context, Key, Zero, Amount);
HdrLen -= Amount + 2;
Current += Amount + 1;
Start = Current;
} else {
//
// This option's data is not mutable.
// Just skip over it.
//
Current++; // Now on option data length byte.
Amount = *Current;
HdrLen -= Amount + 2;
Current += Amount + 1;
}
}
if (Start != Current) {
//
// Option block ends with an immutable region.
//
Amount = (uint)(Current - Start);
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Start, Amount);
#endif
(*Alg->Operate)(Context, Key, Start, Amount);
}
break;
}
case IP_PROTOCOL_ROUTING: {
IPv6RoutingHeader UNALIGNED *Route;
uint HdrLen;
//
// The routing header has the NextHeader field as the first byte.
//
ASSERT(FIELD_OFFSET(IPv6RoutingHeader, NextHeader) == 0);
//
// First feed the NextHeader field into the algorithm.
// We use the one that logically follows, not the one in the header.
//
#ifdef IPSEC_DEBUG
dump_encoded_mesg(&NextHeader, 1);
#endif
(*Alg->Operate)(Context, Key, &NextHeader, 1);
//
// Now feed the rest of this header into the algorithm.
// It's all immutable.
//
Route = (IPv6RoutingHeader UNALIGNED *)Data;
HdrLen = ((Route->HeaderExtLength + 1) * EXT_LEN_UNIT) - 1;
((uchar *)Data)++;
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Data, HdrLen);
#endif
(*Alg->Operate)(Context, Key, Data, HdrLen);
break;
}
default:
//
// Unrecognized header.
// The only way this can happen is if somebody later adds code
// to AuthenticationHeaderReceive to call this function for a
// new header and neglects to add corresponding support here.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"PerformDeferredAHProcessing: "
"Unsupported header = %d\n",
ThisHeader));
ASSERT(FALSE);
}
}
//* AuthenticationHeaderReceive - Handle an IPv6 AH header.
//
// This is the routine called to process an Authentication Header,
// next header value of 51.
//
uchar
AuthenticationHeaderReceive(
IPv6Packet *Packet) // Packet handed to us by IPv6Receive.
{
AHHeader UNALIGNED *AH;
SecurityAssociation *SA;
SecurityAlgorithm *Alg;
uint ResultSize, AHHeaderLen;
void *Context;
uchar *Result, *AuthData;
SALinkage *SAPerformed;
uint SavePosition;
void *SaveData;
uint SaveContigSize;
uint SaveTotalSize;
void *SaveAuxList;
uchar NextHeader, DeferredHeader;
void *DeferredData;
uint Done;
//
// Verify that we have enough contiguous data to overlay an Authentication
// Header structure on the incoming packet. Then do so and skip over it.
//
if (! PacketPullup(Packet, sizeof(AHHeader), 1, 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof(AHHeader))
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"AuthenticationHeaderReceive: Incoming packet too small"
" to contain authentication header\n"));
return IP_PROTOCOL_NONE; // Drop packet.
}
AH = (AHHeader UNALIGNED *)Packet->Data;
// Remember offset to this header's NextHeader field.
Packet->NextHeaderPosition = Packet->Position +
FIELD_OFFSET(AHHeader, NextHeader);
AdjustPacketParams(Packet, sizeof(AHHeader));
//
// Lookup Security Association in the Security Association Database.
//
SA = InboundSAFind(net_long(AH->SPI),
AlignAddr(&Packet->IP->Dest),
IP_PROTOCOL_AH);
if (SA == NULL) {
// No SA exists for this packet.
// Drop packet. NOTE: This is an auditable event.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"AuthenticationHeaderReceive: "
"No matching SA in database\n"));
return IP_PROTOCOL_NONE;
}
//
// Verify the Sequence Number if required to do so by the SA.
// Since we only support manual keying currently, we treat all SAs
// as not requiring this check.
// TBD: Will need to change this when we add support for dynamic
// TBD: keying (IKE).
//
//
// Perform Integrity check.
//
// First ensure that the amount of Authentication Data claimed to exist
// in this packet by the AH header's PayloadLen field is large enough to
// contain the amount that is required by the algorithm specified in the
// SA. Note that the former may contain padding to make it a multiple
// of 32 bits. Then check the packet size to ensure that it is big
// enough to hold what the header claims is present.
//
AHHeaderLen = (AH->PayloadLen + 2) * 4;
if (AHHeaderLen < sizeof (AHHeader)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"AuthenticationHeaderReceive: Bogus AH header length\n"));
goto ErrorReturn;
}
AHHeaderLen -= sizeof(AHHeader); // May include padding.
Alg = &AlgorithmTable[SA->AlgorithmId];
ResultSize = Alg->ResultSize; // Does not include padding.
if (ResultSize > AHHeaderLen) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"AuthenticationHeaderReceive: Incoming packet's AHHeader"
" length inconsistent with algorithm's AuthData size\n"));
goto ErrorReturn;
}
if (! PacketPullup(Packet, AHHeaderLen, 1, 0)) {
// Pullup failed.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"AuthenticationHeaderReceive: Incoming packet too small"
" to contain authentication data\n"));
goto ErrorReturn;
}
AuthData = (uchar *)Packet->Data;
AdjustPacketParams(Packet, AHHeaderLen);
//
// AH authenticates everything (expect mutable fields) starting from
// the previous IPv6 header. Stash away our current position (so we can
// restore it later) and backup to the previous IPv6 header.
//
SavePosition = Packet->Position;
SaveData = Packet->Data;
SaveContigSize = Packet->ContigSize;
SaveTotalSize = Packet->TotalSize;
SaveAuxList = Packet->AuxList;
PositionPacketAt(Packet, Packet->IPPosition);
Packet->AuxList = NULL;
//
// Initialize this particular algorithm.
//
Context = alloca(Alg->ContextSize);
(*Alg->Initialize)(Context, SA->Key);
//
// Run algorithm over packet data. We start with the IP header that
// encapsulates this AH header. We proceed through the end of the
// packet, skipping over certain headers which are not part of the
// logical packet being secured. We also treat any mutable fields
// as zero for the purpose of the algorithm calculation.
//
// Note: We only search for mutable fields in Destination Options
// headers that appear before this AH header. While the spec doesn't
// explicitly spell this out anywhere, this is the behavior that makes
// the most sense and we've verified this interpretation in the working
// group. However, because of this, our interpretation fails a TAHI test.
// TAHI will hopefully fix their test, if they haven't already.
//
//
// Start by pulling up the IP header and seeing which header physically
// follows it.
//
if (! PacketPullup(Packet, sizeof(IPv6Header),
__builtin_alignof(IPv6Addr), 0)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"AuthenticationHeaderReceive: Out of memory!?!\n"));
goto ErrorReturn;
}
NextHeader = Packet->IP->NextHeader;
//
// Defer processing of this header until after we've determined
// whether or not we'll be skipping the following header. This allows us
// to use the correct NextHeader field value when running the algorithm.
//
DeferredHeader = IP_PROTOCOL_V6;
DeferredData = Packet->Data;
AdjustPacketParams(Packet, sizeof(IPv6Header));
//
// Continue over the various extension headers until we reach the
// AH header for which we're running this authentication algoritm.
// We've already parsed this far, so we know these headers are legit.
//
for (Done = FALSE; !Done;) {
switch (NextHeader) {
case IP_PROTOCOL_HOP_BY_HOP:
case IP_PROTOCOL_DEST_OPTS: {
IPv6OptionsHeader *Ext;
uint HdrLen;
//
// These headers are not skipped, so process the header
// logically preceeding this one. Its NextHeader field
// will contain the Protocol value for this header.
//
PerformDeferredAHProcessing(Alg, Context, SA->Key,
Packet->SkippedHeaderLength,
DeferredData, DeferredHeader,
NextHeader);
//
// Remember this header for deferred processing.
//
DeferredHeader = NextHeader;
//
// Get the extension header and all the options pulled up
// into one nice contiguous chunk.
//
if (! PacketPullup(Packet, sizeof(ExtensionHeader),
__builtin_alignof(ExtensionHeader), 0)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"AuthenticationHeaderReceive: "
"Out of mem!?!\n"));
goto ErrorReturn;
}
Ext = (IPv6OptionsHeader *)Packet->Data;
NextHeader = Ext->NextHeader;
HdrLen = (Ext->HeaderExtLength + 1) * EXT_LEN_UNIT;
if (! PacketPullup(Packet, HdrLen, 1, 0)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"AuthenticationHeaderReceive: "
"Out of mem!?!\n"));
goto ErrorReturn;
}
//
// Remember where this header starts for deferred processing.
//
DeferredData = Packet->Data;
AdjustPacketParams(Packet, HdrLen);
break;
}
case IP_PROTOCOL_ROUTING: {
IPv6RoutingHeader *Route;
uint HdrLen;
//
// This header is not skipped, so process the header
// logically preceeding this one. Its NextHeader field
// will contain the Protocol value for this header.
//
PerformDeferredAHProcessing(Alg, Context, SA->Key,
Packet->SkippedHeaderLength,
DeferredData, DeferredHeader,
IP_PROTOCOL_ROUTING);
//
// Remember this header for deferred processing.
//
DeferredHeader = IP_PROTOCOL_ROUTING;
//
// Get the extension header and all the options pulled up
// into one nice contiguous chunk.
//
if (! PacketPullup(Packet, sizeof(IPv6RoutingHeader),
__builtin_alignof(IPv6RoutingHeader), 0)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"AuthenticationHeaderReceive: "
"Out of mem!?!\n"));
goto ErrorReturn;
}
Route = (IPv6RoutingHeader *)Packet->Data;
NextHeader = Route->NextHeader;
HdrLen = (Route->HeaderExtLength + 1) * EXT_LEN_UNIT;
if (! PacketPullup(Packet, HdrLen, 1, 0)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"AuthenticationHeaderReceive: "
"Out of mem!?!\n"));
goto ErrorReturn;
}
//
// Remember where this header starts for deferred processing.
//
DeferredData = Packet->Data;
AdjustPacketParams(Packet, HdrLen);
break;
}
case IP_PROTOCOL_AH: {
//
// We don't know yet whether we'll be including this AH header
// in the algorithm calculation we're currently running.
// See below.
//
AHHeader UNALIGNED *ThisAH;
uint ThisHdrLen;
uint Padding;
//
// Pullup the AH header.
//
if (! PacketPullup(Packet, sizeof(AHHeader), 1, 0)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"AuthenticationHeaderReceive: "
"Out of mem!?!\n"));
goto ErrorReturn;
}
ThisAH = (AHHeader UNALIGNED *)Packet->Data;
AdjustPacketParams(Packet, sizeof(AHHeader));
ThisHdrLen = ((ThisAH->PayloadLen + 2) * 4) - sizeof(AHHeader);
if (! PacketPullup(Packet, ThisHdrLen, 1, 0)) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"AuthenticationHeaderReceive: "
"Out of mem!?!\n"));
goto ErrorReturn;
}
AdjustPacketParams(Packet, ThisHdrLen);
//
// If this is another AH header encapsulating the one we are
// currently processing, then don't include it in the integrity
// check as per AH spec section 3.3.
//
if (Packet->Position != SavePosition) {
NextHeader = ThisAH->NextHeader;
break;
}
//
// Otherwise this is the AH header that we're currently processing,
// and we include it in its own integrity check. But first we
// need to process the header logically preceeding this one (which
// we previously defered). Its NextHeader field will contain the
// Protocol value for this header.
//
PerformDeferredAHProcessing(Alg, Context, SA->Key,
Packet->SkippedHeaderLength,
DeferredData, DeferredHeader,
IP_PROTOCOL_AH);
//
// Now process this AH header. We do not need to defer processing
// of this header, since everything following it is included in
// the check. The Authentication Data is mutable, the rest of the
// AH header is not.
//
ASSERT(Packet->TotalSize == SaveTotalSize);
#ifdef IPSEC_DEBUG
dump_encoded_mesg((uchar *)AH, sizeof(AHHeader));
#endif
(*Alg->Operate)(Context, SA->Key, (uchar *)AH, sizeof(AHHeader));
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Zero, ResultSize);
#endif
(*Alg->Operate)(Context, SA->Key, Zero, ResultSize);
//
// The Authentication Data may be padded. This padding is
// included as non-mutable in the integrity calculation.
// REVIEW: We should double-check our interpretation of the RFC
// about this with the IPSec working group.
//
Padding = AHHeaderLen - ResultSize;
if (Padding != 0) {
#ifdef IPSEC_DEBUG
dump_encoded_mesg((uchar *)(Packet->Data) - Padding, Padding);
#endif
(*Alg->Operate)(Context, SA->Key,
(uchar *)(Packet->Data) - Padding, Padding);
}
Done = TRUE;
break;
}
case IP_PROTOCOL_ESP: {
//
// We don't include other IPSec headers in the integrity check
// as per AH spec section 3.3. So just skip over this. Tricky
// part is that the NextHeader was in the ESP trailer which we've
// already thrown away at this point.
//
ESPHeader UNALIGNED *ThisESP;
ulong ThisSPI;
SALinkage *ThisSAL;
//
// Get the SPI out of the ESP header so we can identify its
// SALinkage entry on the SAPerformed chain. Skip over the
// ESP header while we're at it.
//
if (! PacketPullup(Packet, sizeof(ESPHeader), 1, 0)) {
// Pullup failed.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"AuthenticationHeaderReceive: Out of mem!?!\n"));
goto ErrorReturn;
}
ThisESP = (ESPHeader UNALIGNED *)Packet->Data;
AdjustPacketParams(Packet, sizeof(ESPHeader));
ThisSPI = net_long(ThisESP->SPI);
//
// Find the SALinkage entry on the SAPerformed chain with the
// matching SPI. It must be present.
// REVIEW: This code assumes we made SPIs system-wide unique.
//
for (ThisSAL = Packet->SAPerformed;
ThisSAL->This->SPI != ThisSPI; ThisSAL = ThisSAL->Next)
ASSERT(ThisSAL->Next != NULL);
//
// Pull NextHeader value out of the SALinkage (where we stashed
// it back in EncapsulatingSecurityPayloadReceive).
//
NextHeader = (uchar)ThisSAL->NextHeader;
break;
}
case IP_PROTOCOL_FRAGMENT: {
//
// We normally won't encounter a fragment header here,
// since reassembly will occur before authentication.
// However, our implementation optimizes the reassembly of
// single-fragment packets by leaving the fragment header in
// place. When performing the authentication calculation,
// we treat such fragment headers as if they didn't exist.
//
FragmentHeader UNALIGNED *Frag;
if (! PacketPullup(Packet, sizeof(FragmentHeader), 1, 0)) {
KdPrintEx((DPFLTR_TCPIP_ID, DPFLTR_NTOS_ERROR,
"AuthenticationHeaderReceive: Out of mem!?\n"));
goto ErrorReturn;
}
Frag = (FragmentHeader UNALIGNED *)Packet->Data;
NextHeader = Frag->NextHeader;
AdjustPacketParams(Packet, sizeof(FragmentHeader));
break;
}
default:
// Unrecognized header.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"AuthenticationHeaderReceive: "
"Unsupported header = %d\n",
NextHeader));
goto ErrorReturn;
}
}
//
// Everything inside this AH header is treated as immutable.
//
// REVIEW: For performance reasons, the ContigSize check could be moved
// REVIEW: before the loop for the additional code space cost of
// REVIEW: duplicating the PacketPullup call.
//
while (Packet->TotalSize != 0) {
if (Packet->ContigSize == 0) {
//
// Ran out of contiguous data.
// Get next buffer in packet.
//
PacketPullupSubr(Packet, 0, 1, 0); // Moves to next buffer.
}
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Packet->Data, Packet->ContigSize);
#endif
(*Alg->Operate)(Context, SA->Key, Packet->Data, Packet->ContigSize);
AdjustPacketParams(Packet, Packet->ContigSize);
}
//
// Get final result from the algorithm.
//
Result = alloca(ResultSize);
(*Alg->Finalize)(Context, SA->Key, Result);
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Recv Key (%d bytes)): ", SA->RawKeyLength));
DumpKey(SA->RawKey, SA->RawKeyLength);
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Recv AuthData:\n"));
dump_encoded_mesg(Result, ResultSize);
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Sent AuthData:\n"));
dump_encoded_mesg(AuthData, ResultSize);
#endif
//
// Compare result to authentication data in packet. They should match.
//
if (RtlCompareMemory(Result, AuthData, ResultSize) != ResultSize) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"AuthenticationHeaderReceive: Failed integrity check\n"));
goto ErrorReturn;
}
//
// Restore our packet position (to just after AH Header).
//
PacketPullupCleanup(Packet);
Packet->Position = SavePosition;
Packet->Data = SaveData;
Packet->ContigSize = SaveContigSize;
Packet->TotalSize = SaveTotalSize;
Packet->AuxList = SaveAuxList;
//
// Nested AH headers don't include this one in their calculations.
//
Packet->SkippedHeaderLength += sizeof(AHHeader) + AHHeaderLen;
//
// Add this SA to the list of those that this packet has passed.
//
SAPerformed = ExAllocatePool(NonPagedPool, sizeof *SAPerformed);
if (SAPerformed == NULL) {
ErrorReturn:
ReleaseSA(SA);
return IP_PROTOCOL_NONE; // Drop packet.
}
SAPerformed->This = SA;
SAPerformed->Next = Packet->SAPerformed; // This SA is now first on list.
SAPerformed->Mode = TRANSPORT; // Assume trans until we see an IPv6Header.
Packet->SAPerformed = SAPerformed;
return AH->NextHeader;
}
//* EncapsulatingSecurityPayloadReceive - Handle an IPv6 ESP header.
//
// This is the routine called to process an Encapsulating Security Payload,
// next header value of 50.
//
uchar
EncapsulatingSecurityPayloadReceive(
IPv6Packet *Packet) // Packet handed to us by IPv6Receive.
{
ESPHeader UNALIGNED *ESP;
ESPTrailer TrailerBuffer;
ESPTrailer UNALIGNED *ESPT;
SecurityAssociation *SA;
PNDIS_BUFFER NdisBuffer;
SALinkage *SAPerformed;
//
// Verify that we have enough contiguous data to overlay an Encapsulating
// Security Payload Header structure on the incoming packet. Since the
// authentication check covers the ESP header, we don't skip over it yet.
//
if (! PacketPullup(Packet, sizeof(ESPHeader), 1, 0)) {
// Pullup failed.
if (Packet->TotalSize < sizeof(ESPHeader))
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"EncapsulatingSecurityPayloadReceive: "
"Incoming packet too small to contain ESP header\n"));
return IP_PROTOCOL_NONE; // Drop packet.
}
ESP = (ESPHeader UNALIGNED *)Packet->Data;
//
// Lookup Security Association in the Security Association Database.
//
SA = InboundSAFind(net_long(ESP->SPI),
AlignAddr(&Packet->IP->Dest),
IP_PROTOCOL_ESP);
if (SA == NULL){
// No SA exists for this packet.
// Drop packet. NOTE: This is an auditable event.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"EncapsulatingSecurityPayloadReceive: "
"No SA found for the packet\n"));
return IP_PROTOCOL_NONE;
}
//
// Verify the Sequence Number if required to do so by the SA.
// Since we only support manual keying currently, we treat all SAs
// as not requiring this check.
// TBD: Will need to change this when we add support for dynamic
// TBD: keying (IKE).
//
//
// Perform integrity check if authentication has been selected.
// TBD: When (if?) we add encryption support, we'll want to check the
// TBD: SA to see if authentication is desired. Hardwired for now.
//
if (1) {
SecurityAlgorithm *Alg;
uint AuthDataSize;
uint PayloadLength;
void *Context;
IPv6Packet Clone;
uint DoNow;
uchar *AuthData;
uchar *Result;
//
// First ensure that the incoming packet is large enough to hold the
// Authentication Data required by the algorithm specified in the SA.
// Then calculate the amount of data covered by authentication.
//
Alg = &AlgorithmTable[SA->AlgorithmId];
AuthDataSize = Alg->ResultSize;
if (Packet->TotalSize < sizeof(ESPHeader) + sizeof(ESPTrailer) +
AuthDataSize) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"EncapsulatingSecurityPaylofadReceive: "
"Packet too short to hold Authentication Data\n"));
goto ErrorReturn;
}
PayloadLength = Packet->TotalSize - AuthDataSize;
//
// Clone the packet positioning information so we can step through
// the packet without losing our current place. Start clone with
// a fresh pullup history, however.
//
Clone = *Packet;
Clone.AuxList = NULL;
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"\nESP Receive Data:\n"));
#endif
//
// Initialize this particular algorithm.
//
Context = alloca(Alg->ContextSize);
(*Alg->Initialize)(Context, SA->Key);
//
// Run algorithm over packet data.
// ESP authenticates everything beginning with the ESP Header and
// ending just prior to the Authentication Data.
//
while (PayloadLength != 0) {
DoNow = MIN(PayloadLength, Clone.ContigSize);
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Clone.Data, DoNow);
#endif
(*Alg->Operate)(Context, SA->Key, Clone.Data, DoNow);
if (DoNow < PayloadLength) {
//
// Not done yet, must have run out of contiguous data.
// Get next buffer in packet.
//
AdjustPacketParams(&Clone, DoNow);
PacketPullupSubr(&Clone, 0, 1, 0); // Moves to next buffer.
}
PayloadLength -= DoNow;
}
AdjustPacketParams(&Clone, DoNow);
//
// Get final result from the algorithm.
//
Result = alloca(AuthDataSize);
(*Alg->Finalize)(Context, SA->Key, Result);
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Calculated AuthData:\n"));
dump_encoded_mesg(Result, AuthDataSize);
#endif
//
// The Authentication Data immediately follows the region of
// authentication coverage. So our clone should be positioned
// at the beginning of it. Ensure that it's contiguous.
//
if (! PacketPullup(&Clone, AuthDataSize, 1, 0)) {
// Pullup failed. Should never happen due to earlier check.
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"EncapsulatingSecurityPayloadReceive: "
"Incoming packet too small for Auth Data\n"));
goto ErrorReturn;
}
// Point to Authentication data.
AuthData = Clone.Data;
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Received AuthData:\n"));
dump_encoded_mesg(AuthData, AuthDataSize);
#endif
//
// Compare our result to the Authentication Data. They should match.
//
if (RtlCompareMemory(Result, AuthData, AuthDataSize) != AuthDataSize) {
//
// Integrity check failed. NOTE: This is an auditable event.
//
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"EncapsulatingSecurityPayloadReceive: "
"Failed integrity check\n"));
PacketPullupCleanup(&Clone);
goto ErrorReturn;
}
//
// Done with the clone, clean up after it.
//
PacketPullupCleanup(&Clone);
//
// Truncate our packet to no longer include the Authentication Data.
//
Packet->TotalSize -= AuthDataSize;
if (Packet->ContigSize > Packet->TotalSize)
Packet->ContigSize = Packet->TotalSize;
}
//
// We can consume the ESP Header now since it isn't
// covered by confidentiality.
//
AdjustPacketParams(Packet, sizeof(ESPHeader));
//
// Decrypt Packet if confidentiality has been selected.
// TBD: When (if?) we add encryption support, we'll want to check the
// TBD: SA to see if encryption is desired. Hardwired for now.
//
if (0) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NET_ERROR,
"EncapsulatingSecurityPaylofadReceive: "
"SA requested confidentiality -- unsupported feature\n"));
goto ErrorReturn;
}
//
// Remove trailer and padding (if any). Note that padding may appear
// even in the no-encryption case in order to align the Authentication
// Data on a four byte boundary.
//
if (Packet->NdisPacket == NULL) {
//
// This packet must be just a single contiguous region.
// Finding the trailer is a simple matter of arithmetic.
//
ESPT = (ESPTrailer UNALIGNED *)
((uchar *)Packet->Data + Packet->TotalSize - sizeof(ESPTrailer));
} else {
//
// Need to find the trailer in the Ndis buffer chain.
//
NdisQueryPacket(Packet->NdisPacket, NULL, NULL, &NdisBuffer, NULL);
ESPT = (ESPTrailer UNALIGNED *)
GetDataFromNdis(NdisBuffer,
Packet->Position + Packet->TotalSize -
sizeof(ESPTrailer),
sizeof(ESPTrailer),
(uchar *)&TrailerBuffer);
}
Packet->TotalSize -= sizeof(ESPTrailer);
if (ESPT->PadLength > Packet->TotalSize) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_BAD_PACKET,
"EncapsulatingSecurityPayloadReceive: "
"PadLength impossibly large (%u of %u bytes)\n",
ESPT->PadLength, Packet->TotalSize));
goto ErrorReturn;
}
// Remember offset to this header's NextHeader field.
Packet->NextHeaderPosition = Packet->Position + Packet->TotalSize +
FIELD_OFFSET(ESPTrailer, NextHeader);
// Remove padding.
Packet->TotalSize -= ESPT->PadLength;
if (Packet->ContigSize > Packet->TotalSize)
Packet->ContigSize = Packet->TotalSize;
//
// Encapsulated AH headers don't include this ESP header when
// authenticating the packet.
//
Packet->SkippedHeaderLength += sizeof(ESPHeader) + sizeof(ESPTrailer) +
ESPT->PadLength;
//
// Add this SA to the list of those that this packet has passed.
//
SAPerformed = ExAllocatePool(NonPagedPool, sizeof *SAPerformed);
if (SAPerformed == NULL) {
ErrorReturn:
ReleaseSA(SA);
return IP_PROTOCOL_NONE; // Drop packet.
}
SAPerformed->This = SA;
SAPerformed->Next = Packet->SAPerformed; // This SA is now first on list.
SAPerformed->Mode = TRANSPORT; // Assume trans until we see an IPv6Header.
SAPerformed->NextHeader = ESPT->NextHeader;
Packet->SAPerformed = SAPerformed;
return ESPT->NextHeader;
}
//* InsertSecurityPolicy
//
// Add a security policy to the global list (a.k.a. "SecurityPolicyList").
// The global list is doubly-linked, ordered by the index value with the
// higher numbers (more specific policies) first.
//
// Called with security lock held.
//
int
InsertSecurityPolicy(
SecurityPolicy *NewSP) // Policy to insert.
{
SecurityPolicy *CurrentSP, *PrevSP;
//
// Run through the SP list looking for place to insert.
//
CurrentSP = PrevSP = SecurityPolicyList;
while (CurrentSP != NULL) {
if (CurrentSP->Index <= NewSP->Index) {
break;
}
// Move down the list.
PrevSP = CurrentSP;
CurrentSP = CurrentSP->Next;
}
//
// See where we ended up.
//
if (CurrentSP == NULL) {
//
// Ran off the end of the list.
// New entry will become the last element.
//
NewSP->Next = NULL;
} else {
//
// Check for duplicate entries.
//
if (CurrentSP->Index == NewSP->Index) {
// A policy with this index value already exists.
return FALSE;
}
//
// Put new one before 'current'.
//
NewSP->Next = CurrentSP;
NewSP->Prev = CurrentSP->Prev;
CurrentSP->Prev = NewSP;
}
if (CurrentSP == SecurityPolicyList) {
//
// Still at the front of the list.
// New entry becomes new list head.
//
NewSP->Prev = NULL;
SecurityPolicyList = NewSP;
} else {
//
// Add new entry after 'previous'.
//
NewSP->Prev = PrevSP;
PrevSP->Next = NewSP;
}
InvalidateSecurityState();
return TRUE;
}
//* InsertSecurityAssociation - Insert SA entry on SecurityAssociationList.
//
// Add a security association to the global list.
// The global list is doubly-linked, ordered by the index value with the
// higher numbers first.
// REVIEW: the order is arbitrary - just to look nicer when print out.
//
int
InsertSecurityAssociation(
SecurityAssociation *NewSA) // Association to insert.
{
SecurityAssociation *CurrentSA, *PrevSA;
//
// Run through the SA list looking for place to insert.
//
CurrentSA = PrevSA = SecurityAssociationList;
while (CurrentSA != NULL) {
if (CurrentSA->Index <= NewSA->Index) {
break;
}
// Move down the list.
PrevSA = CurrentSA;
CurrentSA = CurrentSA->Next;
}
//
// See where we ended up.
//
if (CurrentSA == NULL) {
//
// Ran off the end of the list.
// New entry will become the last element.
//
NewSA->Next = NULL;
} else {
//
// Check for duplicate entries.
//
if (CurrentSA->Index == NewSA->Index) {
// An association with this index value already exists.
return FALSE;
}
//
// Put new one before 'current'.
//
NewSA->Next = CurrentSA;
NewSA->Prev = CurrentSA->Prev;
CurrentSA->Prev = NewSA;
}
if (CurrentSA == SecurityAssociationList) {
//
// Still at the front of the list.
// New entry becomes new list head.
//
NewSA->Prev = NULL;
SecurityAssociationList = NewSA;
} else {
//
// Add new entry after 'previous'.
//
NewSA->Prev = PrevSA;
PrevSA->Next = NewSA;
}
InvalidateSecurityState();
return TRUE;
}
//* FindSecurityPolicyMatch - Find matching SP entry.
//
// Called with security lock held.
//
SecurityPolicy *
FindSecurityPolicyMatch(
SecurityPolicy *Start, // Head of list to search.
uint InterfaceIndex, // Interface number to match, 0 to wildcard.
uint PolicyIndex) // Policy number to match, 0 to wildcard.
{
SecurityPolicy *ThisSP;
//
// Search the Security Policy List for a match.
//
for (ThisSP = Start; ThisSP != NULL; ThisSP = ThisSP->Next) {
//
// Desired policy must be wildcarded or match.
//
if ((PolicyIndex != 0) && (PolicyIndex != ThisSP->Index))
continue;
//
// Interface must be wildcarded or match. Note that the policy,
// as well as the query, may have a wildcarded interface index.
//
if ((InterfaceIndex != 0) && (ThisSP->IFIndex != 0) &&
(InterfaceIndex != ThisSP->IFIndex))
continue;
break; // Match.
}
return ThisSP;
}
//* FindSecurityAssociationMatch - Find SA Entry corresponding to index value.
//
// Called with security lock held.
//
SecurityAssociation *
FindSecurityAssociationMatch(
ulong Index) // Association number to match, 0 to wildcard.
{
SecurityAssociation *ThisSA;
//
// Search the Security Association List starting with the first SA.
//
for (ThisSA = SecurityAssociationList; ThisSA != NULL;
ThisSA = ThisSA->Next) {
//
// Desired association must be wildcarded or match.
//
if ((Index == 0) || (Index == ThisSA->Index))
break;
}
return ThisSA;
}
//* GetSecurityPolicyIndex - Return SP Index or NONE.
//
ulong
GetSecurityPolicyIndex(
SecurityPolicy *SP)
{
ulong Index = NONE;
// Get Index from SP.
if (SP != NULL) {
Index = SP->Index;
}
return Index;
}
//* IPSecInit - Initialize the Common SPD.
//
int
IPSecInit(void)
{
SecurityPolicy *SP;
Interface *IF;
// Allocate memory for Security Policy.
SP = ExAllocatePool(NonPagedPool, sizeof *SP);
if (SP == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"IPSecInit - couldn't allocate pool for SP!?!\n"));
return FALSE;
}
//
// Initialize a default common policy that allows everything.
//
SP->Next = NULL;
SP->Prev = NULL;
SP->RemoteAddrField = WILDCARD_VALUE;
SP->RemoteAddr = UnspecifiedAddr;
SP->RemoteAddrData = UnspecifiedAddr;
SP->RemoteAddrSelector = POLICY_SELECTOR;
SP->LocalAddrField = WILDCARD_VALUE;
SP->LocalAddr = UnspecifiedAddr;
SP->LocalAddrData = UnspecifiedAddr;
SP->LocalAddrSelector = POLICY_SELECTOR;
SP->TransportProto = NONE;
SP->TransportProtoSelector = POLICY_SELECTOR;
SP->RemotePortField = WILDCARD_VALUE;
SP->RemotePort = NONE;
SP->RemotePortData = NONE;
SP->RemotePortSelector = POLICY_SELECTOR;
SP->LocalPortField = WILDCARD_VALUE;
SP->LocalPort = NONE;
SP->LocalPortData = NONE;
SP->LocalPortSelector = POLICY_SELECTOR;
SP->SecPolicyFlag = IPSEC_BYPASS;
SP->IPSecSpec.Protocol = NONE;
SP->IPSecSpec.Mode = NONE;
SP->IPSecSpec.RemoteSecGWIPAddr = UnspecifiedAddr;
SP->DirectionFlag = BIDIRECTIONAL;
SP->OutboundSA = NULL;
SP->InboundSA = NULL;
SP->SABundle = NULL;
SP->Index = 1;
SP->RefCnt = 0;
SP->IFIndex = 0;
//
// Initialize the global Security Policy list with the default policy.
//
SecurityPolicyList = SP;
KeInitializeSpinLock(&IPSecLock);
//
// Initialize the security algorithms table.
//
AlgorithmsInit();
return(TRUE);
}
//* IPSecUnload
//
// Cleanup and prepare for stack unload.
//
void
IPSecUnload(void)
{
KIRQL OldIrql;
// Get Security Lock.
KeAcquireSpinLock(&IPSecLock, &OldIrql);
//
// Delete all the policies on the global Security Policy list.
// This will take out any associations hanging off them as well.
//
while (SecurityPolicyList != NULL) {
DeleteSP(SecurityPolicyList);
}
// Release lock.
KeReleaseSpinLock(&IPSecLock, OldIrql);
}
//* IPSecBytesToInsert
//
uint
IPSecBytesToInsert(
IPSecProc *IPSecToDo,
int *TunnelStart,
uint *TrailerLength)
{
uint i, Padding;
uint BytesInHeader, BytesToInsert = 0, BytesForTrailer = 0;
SecurityAlgorithm *Alg;
SecurityAssociation *SA;
uint IPSEC_TUNNEL = FALSE;
for (i = 0; i < IPSecToDo->BundleSize; i++) {
SA = IPSecToDo[i].SA;
Alg = &AlgorithmTable[SA->AlgorithmId];
//
// Calculate bytes to insert for each IPSec header..
//
// Check if this is tunnel or transport mode.
if (IPSecToDo[i].Mode == TUNNEL) {
// Outer IPv6 header.
BytesToInsert += sizeof(IPv6Header);
if (!IPSEC_TUNNEL) {
// Set the tunnel start location.
*TunnelStart = i;
IPSEC_TUNNEL = TRUE;
}
}
// Check which IPSec protocol.
if (SA->IPSecProto == IP_PROTOCOL_AH) {
BytesInHeader = (sizeof(AHHeader) + Alg->ResultSize);
//
// The AH header must be a integral multiple of 64 bits in length.
// Check if padding needs to be added to the ICV result to make
// the Auth Data field a legitimate length.
//
Padding = BytesInHeader % 8;
if (Padding != 0) {
BytesInHeader += (8 - Padding);
}
ASSERT(BytesInHeader % 8 == 0);
} else {
BytesInHeader = sizeof(ESPHeader);
BytesForTrailer += (sizeof(ESPTrailer) + Alg->ResultSize);
}
// Store the byte size of the IPSec header.
IPSecToDo[i].ByteSize = BytesInHeader;
// Add the IPSec header size to the total bytes to insert.
BytesToInsert += BytesInHeader;
}
// See if our caller wants the trailer length too.
if (TrailerLength != NULL)
*TrailerLength = BytesForTrailer;
return BytesToInsert;
}
//* IPSecInsertHeaders
//
uint
IPSecInsertHeaders(
uint Mode, // Transport or Tunnel.
IPSecProc *IPSecToDo,
uchar **InsertPoint,
uchar *NewMemory,
PNDIS_PACKET Packet,
uint *TotalPacketSize,
uchar *PrevNextHdr,
uint TunnelStart,
uint *BytesInserted,
uint *NumESPTrailers,
uint *JUST_ESP)
{
uint i, NumHeaders = 0;
AHHeader *AH;
ESPHeader *ESP;
uchar NextHeader;
uint Padding, j;
ESPTrailer *ESPTlr;
PNDIS_BUFFER ESPTlrBuffer, Buffer, NextBuffer;
uchar *ESPTlrMemory;
uint ESPTlrBufSize;
NDIS_STATUS Status;
SecurityAlgorithm *Alg;
SecurityAssociation *SA;
uint Action = LOOKUP_CONT;
uint BufferCount;
NextHeader = *PrevNextHdr;
if (Mode == TRANSPORT) {
i = 0;
if (TunnelStart != NO_TUNNEL) {
NumHeaders = TunnelStart;
} else {
NumHeaders = IPSecToDo->BundleSize;
}
} else {
// Tunnel.
i = TunnelStart;
// Get the end of the tunnels.
for (j = TunnelStart + 1; j < IPSecToDo->BundleSize; j++) {
if (IPSecToDo[j].Mode == TUNNEL) {
// Another tunnel.
NumHeaders = j;
break;
}
}
if (NumHeaders == 0) {
// No other tunnels.
NumHeaders = IPSecToDo->BundleSize;
}
}
*JUST_ESP = TRUE;
for (i; i < NumHeaders; i++) {
SA = IPSecToDo[i].SA;
if (SA->IPSecProto == IP_PROTOCOL_AH) {
*JUST_ESP = FALSE;
// Move insert point up to start of AH Header.
*InsertPoint -= IPSecToDo[i].ByteSize;
*BytesInserted += IPSecToDo[i].ByteSize;
AH = (AHHeader *)(*InsertPoint);
//
// Insert AH Header.
//
AH->NextHeader = NextHeader;
// Set previous header's next header field to AH.
NextHeader = IP_PROTOCOL_AH;
// Payload length is in 32 bit counts.
AH->PayloadLen = (IPSecToDo[i].ByteSize / 4) - 2;
AH->Reserved = 0;
AH->SPI = net_long(SA->SPI);
// TBD: Note that when we add support for dynamic keying,
// TBD: we'll also need to check for sequence number wrap here.
AH->Seq = net_long(InterlockedIncrement(&SA->SequenceNum));
//
// Store point where to put AH Auth Data after authentication.
//
IPSecToDo[i].AuthData = (*InsertPoint) + sizeof(AHHeader);
//
// Zero out Auth Data and padding.
// The Auth Data value will be filled in later.
//
RtlZeroMemory(IPSecToDo[i].AuthData,
IPSecToDo[i].ByteSize - sizeof(AHHeader));
} else {
// ESP.
Alg = &AlgorithmTable[SA->AlgorithmId];
// Move insert point up to start of ESP Header.
*InsertPoint -= IPSecToDo[i].ByteSize;
*BytesInserted += IPSecToDo[i].ByteSize;
ESP = (ESPHeader *)(*InsertPoint);
//
// Insert ESP Header.
//
ESP->SPI = net_long(SA->SPI);
// TBD: Note that when we add support for dynamic keying,
// TBD: we'll also need to check for sequence number wrap here.
ESP->Seq = net_long(InterlockedIncrement(&SA->SequenceNum));
//
// Compute padding that needs to be added in ESP Trailer.
// The PadLength and Next header must end of a 4-byte boundary
// with respect to the start of the IPv6 header.
// TotalPacketSize is the length of everything in the original
// packet from the start of the IPv6 header.
//
Padding = *TotalPacketSize % 4;
if (Padding == 0) {
Padding = 2;
} else if (Padding == 2) {
Padding = 0;
}
// Adjust total packet size to account for padding.
*TotalPacketSize += Padding;
// Where to start the Authentication for this ESP header.
IPSecToDo[i].Offset = (uint)((*InsertPoint) - NewMemory);
ESPTlrBufSize = Padding + sizeof(ESPTrailer) + Alg->ResultSize;
*BytesInserted += ESPTlrBufSize;
//
// Allocate ESP Trailer.
//
ESPTlrMemory = ExAllocatePool(NonPagedPool, ESPTlrBufSize);
if (ESPTlrMemory == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"InsertTransportIPSec: "
"Couldn't allocate ESPTlrMemory!?!\n"));
Action = LOOKUP_DROP;
break;
}
NdisAllocateBuffer(&Status, &ESPTlrBuffer, IPv6BufferPool,
ESPTlrMemory, ESPTlrBufSize);
if (Status != NDIS_STATUS_SUCCESS) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"InsertTransportIPSec: "
"Couldn't allocate ESP Trailer buffer!?!\n"));
ExFreePool(ESPTlrMemory);
Action = LOOKUP_DROP;
break;
}
// Format Padding.
for (j = 0; j < Padding; j++) {
// Add padding.
*(ESPTlrMemory + j) = j + 1;
}
ESPTlr = (ESPTrailer *)(ESPTlrMemory + Padding);
//
// Format ESP Trailer.
//
ESPTlr->PadLength = (uchar)j;
ESPTlr->NextHeader = NextHeader;
// Set previous header's next header field to ESP.
NextHeader = IP_PROTOCOL_ESP;
//
// Store pointer of where to put ESP Authentication Data.
//
IPSecToDo[i].AuthData = ESPTlrMemory + Padding +
sizeof(ESPTrailer);
// Set Authentication Data to 0s (MUTABLE).
RtlZeroMemory(IPSecToDo[i].AuthData, Alg->ResultSize);
// Link the ESP trailer to the back of the buffer chain.
NdisChainBufferAtBack(Packet, ESPTlrBuffer);
// Increment the number of ESP trailers present.
*NumESPTrailers += 1;
} // end of else
} // end of for (i; i < NumHeaders; i++)
*PrevNextHdr = NextHeader;
return Action;
}
//* IPSecAdjustMutableFields
//
uint
IPSecAdjustMutableFields(
uchar *Data,
IPv6RoutingHeader *SavedRtHdr)
{
uint i;
uchar NextHeader;
IPv6Header UNALIGNED *IP;
//
// Walk original buffer starting at IP header and continuing to the end
// of the mutable headers, zeroing the mutable fields.
//
IP = (IPv6Header UNALIGNED *)Data;
// In VersClassFlow, only the IP version is immutable, so zero the rest.
IP->VersClassFlow = IP_VERSION;
// Hop limit is mutable.
IP->HopLimit = 0;
NextHeader = IP->NextHeader;
Data = (uchar *)(IP + 1);
//
// Loop through the original headers. Zero out the mutable fields.
//
for (;;) {
switch (NextHeader) {
case IP_PROTOCOL_AH:
case IP_PROTOCOL_ESP:
// done.
return LOOKUP_CONT;
case IP_PROTOCOL_HOP_BY_HOP:
case IP_PROTOCOL_DEST_OPTS: {
IPv6OptionsHeader *NewOptHdr;
uint HdrLen, Amount;
uchar *Current;
NewOptHdr = (IPv6OptionsHeader *)Data;
HdrLen = (NewOptHdr->HeaderExtLength + 1) * EXT_LEN_UNIT;
Data += HdrLen;
//
// Run through options to see if any are mutable.
//
Current = (uchar *)NewOptHdr + sizeof(IPv6OptionsHeader);
HdrLen -= sizeof(IPv6OptionsHeader);
while (HdrLen) {
if (*Current == OPT6_PAD_1) {
//
// This is the special one byte pad option. Immutable.
//
Current++;
HdrLen--;
continue;
}
if (OPT6_ISMUTABLE(*Current)) {
//
// This option's data is mutable. Everything preceeding
// the option data is not.
//
Current++; // Now on option data length byte.
Amount = *Current; // Mutable amount.
Current++; // Now on first data byte.
RtlZeroMemory(Current, Amount);
HdrLen -= Amount + 2;
Current += Amount;
} else {
//
// This option's data is not mutable.
// Just skip over it.
//
Current++; // Now on option data length byte.
Amount = *Current;
HdrLen -= Amount + 2;
Current += Amount + 1;
}
}
NextHeader = NewOptHdr->NextHeader;
break;
}
case IP_PROTOCOL_ROUTING: {
IPv6RoutingHeader *NewRtHdr;
IPv6Addr *RecvRtAddr, *SendRtAddr;
// Verify there is a SavedRtHdr.
if (SavedRtHdr == NULL) {
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"IPSecAdjustMutableFields: "
"No Saved routing header"));
return LOOKUP_DROP;
}
// Point to the saved first routing address.
SendRtAddr = (IPv6Addr *)(SavedRtHdr + 1);
// New buffer routing header.
NewRtHdr = (IPv6RoutingHeader *)Data;
// Point to the first routing address.
RecvRtAddr = (IPv6Addr *)(NewRtHdr + 1);
NewRtHdr->SegmentsLeft = 0;
// Copy the IP dest addr to first routing address.
RtlCopyMemory(RecvRtAddr, &IP->Dest, sizeof(IPv6Addr));
for (i = 0; i < (uint)(SavedRtHdr->SegmentsLeft - 1); i++) {
//
// Copy the routing addresses as they will look
// at receive.
//
RtlCopyMemory(&RecvRtAddr[i+1], &SendRtAddr[i],
sizeof(IPv6Addr));
}
// Copy the last routing address to the IP dest address.
RtlCopyMemory(&IP->Dest, &SendRtAddr[i], sizeof(IPv6Addr));
Data += (NewRtHdr->HeaderExtLength + 1) * 8;
NextHeader = NewRtHdr->NextHeader;
break;
}
default:
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
"IPSecAdjustMutableFields: Don't support header %d\n",
NextHeader));
return LOOKUP_DROP;
}// end of switch(NextHeader);
} // end of for (;;)
return LOOKUP_CONT;
}
//* IPSecAuthenticatePacket
//
void
IPSecAuthenticatePacket(
uint Mode,
IPSecProc *IPSecToDo,
uchar *InsertPoint,
uint *TunnelStart,
uchar *NewMemory,
uchar *EndNewMemory,
PNDIS_BUFFER NewBuffer1)
{
uchar *Data;
uint i, NumHeaders = 0, DataLen, j;
void *Context;
void *VirtualAddr;
PNDIS_BUFFER NextBuffer;
SecurityAlgorithm *Alg;
SecurityAssociation *SA;
if (Mode == TRANSPORT) {
i = 0;
if (*TunnelStart != NO_TUNNEL) {
NumHeaders = *TunnelStart;
} else {
NumHeaders = IPSecToDo->BundleSize;
}
} else {
// Tunnel.
i = *TunnelStart;
// Get the end of the tunnels.
for (j = *TunnelStart + 1; j < IPSecToDo->BundleSize; j++) {
if (IPSecToDo[j].Mode == TUNNEL) {
// Another tunnel.
NumHeaders = j;
break;
}
}
if (NumHeaders == 0) {
// No other tunnels.
NumHeaders = IPSecToDo->BundleSize;
}
// Set TunnelStart for loop in IPv6Send2().
*TunnelStart = NumHeaders;
}
for (i; i < NumHeaders; i++) {
SA = IPSecToDo[i].SA;
Alg = &AlgorithmTable[SA->AlgorithmId];
if (SA->IPSecProto == IP_PROTOCOL_AH) {
uint FirstIPSecHeader = NumHeaders - 1;
// AH starts at the IP header.
Data = InsertPoint;
//
// Check if there are other IPSec headers after this in the
// same IP area (IP_"after"<IP Area>_Data). Other IPSec headers
// need to be ignored in the authentication calculation.
// NOTE: This is not a required IPSec header combination.
//
if (i < FirstIPSecHeader) {
uchar *StopPoint;
uint n;
n = i + 1;
//
// There are some other IPSec headers.
// Need to authenticate from the IP header to
// the last header before the first IPSec header hit.
// Then if there is no ESP, just authenticate from the
// current IPSec header to the end of the packet.
// If there is ESP, need to ignore the ESP trailers.
//
//
// Calculate start of first IPSec header.
//
if (IPSecToDo[FirstIPSecHeader].SA->IPSecProto ==
IP_PROTOCOL_AH) {
StopPoint = (IPSecToDo[FirstIPSecHeader].AuthData -
sizeof(AHHeader));
} else {
StopPoint = NewMemory + IPSecToDo[FirstIPSecHeader].Offset;
}
// Length from IP to first IPSec header.
DataLen = (uint)(StopPoint - Data);
// Initialize Context.
Context = alloca(Alg->ContextSize);
(*Alg->Initialize)(Context, SA->Key);
// Authentication to the first IPSec header.
(*Alg->Operate)(Context, SA->Key, Data, DataLen);
// Set the data start to the current IPSec header.
Data = IPSecToDo[i].AuthData - sizeof(AHHeader);
DataLen = (uint)(EndNewMemory - Data);
//
// Authenticate from current IPSec header to the
// end of the new allocated buffer.
//
(*Alg->Operate)(Context, SA->Key, Data, DataLen);
//
// Need to authenticate other buffers if there are any.
// Ignore the ESP trailers.
//
// Check for an ESP header closest to the current IPSec header.
while (n < NumHeaders) {
if (IPSecToDo[n].SA->IPSecProto == IP_PROTOCOL_ESP) {
break;
}
n++;
}
// Get the next buffer in the packet.
NdisGetNextBuffer(NewBuffer1, &NextBuffer);
while (NextBuffer != NULL) {
// Get the start of the data and the data length.
NdisQueryBuffer(NextBuffer, &VirtualAddr, &DataLen);
Data = (uchar *)VirtualAddr;
//
// Check if this is the ESP Trailer by seeing if the
// AuthData storage is in the buffer.
//
if (n < NumHeaders)
if (IPSecToDo[n].AuthData > Data &&
IPSecToDo[n].AuthData < (Data + DataLen)) {
// Stop here this is the ESP trailer.
break;
}
// Feed the buffer to the Authentication function.
(*Alg->Operate)(Context, SA->Key, Data, DataLen);
// Get the next buffer in the packet.
NdisGetNextBuffer(NextBuffer, &NextBuffer)
} // end of while (NextBuffer != NULL)
// Get the Authentication Data.
(*Alg->Finalize)(Context, SA->Key, IPSecToDo[i].AuthData);
// Resume loop for other IPSec headers.
continue;
}
} else { // ESP.
// ESP starts the authentication at the ESP header.
Data = NewMemory + IPSecToDo[i].Offset;
}
DataLen = (uint)(EndNewMemory - Data);
// Initialize Context.
Context = alloca(Alg->ContextSize);
(*Alg->Initialize)(Context, SA->Key);
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"\nSend Data[%d]:\n", i));
dump_encoded_mesg(Data, DataLen);
#endif
// Feed the new buffer to the Authentication function.
(*Alg->Operate)(Context, SA->Key, Data, DataLen);
// Get the next buffer in the packet.
NdisGetNextBuffer(NewBuffer1, &NextBuffer);
while (NextBuffer != NULL) {
// Get the start of the data and the data length.
NdisQueryBuffer(NextBuffer, &VirtualAddr, &DataLen);
Data = (uchar *)VirtualAddr;
//
// Check if this is the ESP Trailer by seeing if the
// AuthData storage is in the buffer.
//
if (SA->IPSecProto == IP_PROTOCOL_ESP &&
IPSecToDo[i].AuthData > Data &&
IPSecToDo[i].AuthData < (Data + DataLen)) {
// Don't include the Authentication Data
// in the ICV calculation.
DataLen = (uint)(IPSecToDo[i].AuthData - Data);
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Data, DataLen);
#endif
// Feed the buffer to the Authentication function.
(*Alg->Operate)(Context, SA->Key, Data, DataLen);
break;
}
#ifdef IPSEC_DEBUG
dump_encoded_mesg(Data, DataLen);
#endif
// Feed the buffer to the Authentication function.
(*Alg->Operate)(Context, SA->Key, Data, DataLen);
// Get the next buffer in the packet.
NdisGetNextBuffer(NextBuffer, &NextBuffer)
} // end of while (NextBuffer != NULL)
// Get the Authentication Data.
(*Alg->Finalize)(Context, SA->Key, IPSecToDo[i].AuthData);
#ifdef IPSEC_DEBUG
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Send Key (%d bytes): ", SA->RawKeyLength));
DumpKey(SA->RawKey, SA->RawKeyLength);
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_IPSEC,
"Send AuthData:\n"));
dump_encoded_mesg(IPSecToDo[i].AuthData, Alg->ResultSize);
#endif
} // end of for (i = 0; ...)
}