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.
999 lines
30 KiB
999 lines
30 KiB
/*++
|
|
|
|
Copyright (c) 1997-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
ah.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the code to create/verify Authentication Headers.
|
|
|
|
Author:
|
|
|
|
Sanjay Anand (SanjayAn) 2-January-1997
|
|
ChunYe
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
|
|
#ifdef RUN_WPP
|
|
#include "ah.tmh"
|
|
#endif
|
|
|
|
//
|
|
// This array assumes one-to-one correspondence with the algoIds and
|
|
// their order in ipsec.h.
|
|
//
|
|
#ifndef _TEST_PERF
|
|
AUTH_ALGO auth_algorithms[] = {
|
|
{ ah_nullinit, ah_nullupdate, ah_nullfinish, MD5DIGESTLEN},
|
|
{ ah_hmacmd5init, ah_hmacmd5update, ah_hmacmd5finish, MD5DIGESTLEN},
|
|
{ ah_hmacshainit, ah_hmacshaupdate, ah_hmacshafinish, A_SHA_DIGEST_LEN},
|
|
};
|
|
#else
|
|
AUTH_ALGO auth_algorithms[] = {
|
|
{ ah_nullinit, ah_nullupdate, ah_nullfinish, MD5DIGESTLEN},
|
|
{ ah_nullinit, ah_nullupdate, ah_nullfinish, MD5DIGESTLEN},
|
|
{ ah_nullinit, ah_nullupdate, ah_nullfinish, A_SHA_DIGEST_LEN},
|
|
};
|
|
#endif
|
|
|
|
|
|
NTSTATUS
|
|
IPSecCreateAH(
|
|
IN PUCHAR pIPHeader,
|
|
IN PVOID pData,
|
|
IN PVOID IPContext,
|
|
IN PSA_TABLE_ENTRY pSA,
|
|
IN ULONG Index,
|
|
OUT PVOID *ppNewData,
|
|
OUT PVOID *ppSCContext,
|
|
OUT PULONG pExtraBytes,
|
|
IN ULONG HdrSpace,
|
|
IN BOOLEAN fSrcRoute,
|
|
IN BOOLEAN fCryptoOnly
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create the AH, given the packet. On the send side.
|
|
|
|
Arguments:
|
|
|
|
pIPHeader - points to start of IP header.
|
|
|
|
pData - points to the data after the IP header. PNDIS_BUFFER
|
|
|
|
pSA - Sec. Assoc. entry
|
|
|
|
ppNewData - the new MDL chain to be used by TCPIP
|
|
|
|
ppSCContext - send complete context used to clean up IPSEC headers
|
|
|
|
pExtraBytes - the header expansion caused by this IPSEC header
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS
|
|
Others:
|
|
STATUS_INSUFFICIENT_RESOURCES
|
|
STATUS_UNSUCCESSFUL (error in algo.)
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PNDIS_BUFFER pAHBuffer;
|
|
PNDIS_BUFFER pHdrBuf = NULL;
|
|
PNDIS_BUFFER pOptBuf = NULL;
|
|
AH UNALIGNED *pAH;
|
|
IPHeader UNALIGNED * pIPH;
|
|
ULONG hdrLen;
|
|
PIPSEC_SEND_COMPLETE_CONTEXT pContext;
|
|
PAUTH_ALGO pAlgo;
|
|
ULONG ahLen;
|
|
ULONG ipNext;
|
|
IPHeader UNALIGNED * pIPH2;
|
|
UCHAR pAHData[MAX_AH_OUTPUT_LEN];
|
|
ULONG totalBytes = 0;
|
|
ULONG saveFlags = 0;
|
|
ULONG Seq;
|
|
USHORT IPLength;
|
|
PNDIS_BUFFER pSaveDataLinkage = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
PNDIS_BUFFER pSaveOptLinkage = NULL;
|
|
BOOLEAN fOuterAH = ((pSA->sa_Flags & FLAGS_SA_TUNNEL) &&
|
|
(((Index == 1) && !pSA->COMP_ALGO(0)) || (Index == 2)));
|
|
BOOLEAN fTunnel = ((pSA->sa_Flags & FLAGS_SA_TUNNEL) &&
|
|
((Index == 0) || ((Index == 1) && pSA->COMP_ALGO(0))));
|
|
BOOLEAN fMuteDest = fSrcRoute && !fTunnel;
|
|
Interface * DestIF = (Interface *) IPContext;
|
|
|
|
|
|
IPSEC_DEBUG(LL_A, DBF_AH, ("Entering IPSecCreateAH"));
|
|
|
|
#if DBG
|
|
IPSEC_DEBUG(LL_A,DBF_MDL, ("Entering IPSecCreateAH"));
|
|
IPSEC_PRINT_CONTEXT(*ppSCContext);
|
|
IPSEC_PRINT_MDL(pData);
|
|
#endif
|
|
|
|
ASSERT(pSA->sa_Operation[Index] == Auth);
|
|
|
|
if (pSA->INT_ALGO(Index) > NUM_AUTH_ALGOS) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
pAlgo = &(auth_algorithms[pSA->INT_ALGO(Index)]);
|
|
|
|
ahLen = sizeof(AH) + pSA->sa_TruncatedLen * sizeof(UCHAR);
|
|
|
|
//
|
|
// If ESP was done previously, then dont alloc the context since we
|
|
// can use the one alloced in ESP processing
|
|
//
|
|
if (*ppSCContext == NULL) {
|
|
pContext = IPSecAllocateSendCompleteCtx(IPSEC_TAG_AH);
|
|
|
|
if (!pContext) {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Failed to alloc. SendCtx"));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
IPSEC_INCREMENT(g_ipsec.NumSends);
|
|
|
|
IPSecZeroMemory(pContext, sizeof(IPSEC_SEND_COMPLETE_CONTEXT));
|
|
|
|
#if DBG
|
|
RtlCopyMemory(pContext->Signature, "ISC1", 4);
|
|
#endif
|
|
*ppSCContext = pContext;
|
|
} else {
|
|
//
|
|
// Piggybacking on ESP Context
|
|
//
|
|
pContext = *ppSCContext;
|
|
saveFlags = pContext->Flags;
|
|
}
|
|
|
|
//
|
|
// Get buffer for AH since no space reserved in the stack. Allocate enough for
|
|
// the full hash, but hack the len to only truncated length.
|
|
//
|
|
IPSecAllocateBuffer(&status,
|
|
&pAHBuffer,
|
|
(PUCHAR *)&pAH,
|
|
ahLen+(pAlgo->OutputLen - pSA->sa_TruncatedLen),
|
|
IPSEC_TAG_AH);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Failed to alloc. AH MDL"));
|
|
pContext->Flags = saveFlags;
|
|
return status;
|
|
}
|
|
|
|
NdisAdjustBufferLength(pAHBuffer, ahLen);
|
|
|
|
pIPH = (IPHeader UNALIGNED *)pIPHeader;
|
|
hdrLen = (pIPH->iph_verlen & (UCHAR)~IP_VER_FLAG) << 2;
|
|
|
|
if (fTunnel) {
|
|
PNDIS_BUFFER pSrcOptBuf;
|
|
PUCHAR pOpt;
|
|
PUCHAR pSrcOpt;
|
|
ULONG optLen = 0;
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("AH Tunnel mode..."));
|
|
|
|
//
|
|
// Allocate an MDL for the new cleartext IP header
|
|
//
|
|
IPSecAllocateBuffer(&status,
|
|
&pHdrBuf,
|
|
(PUCHAR *)&pIPH2,
|
|
sizeof(IPHeader),
|
|
IPSEC_TAG_AH);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
NTSTATUS ntstatus;
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Failed to alloc. PAD MDL"));
|
|
IPSecFreeBuffer(&ntstatus, pAHBuffer);
|
|
pContext->Flags = saveFlags;
|
|
return status;
|
|
}
|
|
|
|
*pExtraBytes += ahLen + sizeof(IPHeader);
|
|
|
|
//
|
|
// if we are going to fragment, and were tunneling, then, copy over the options, if present.
|
|
// Also, use the original IP header on the outside and the new fabricated on the inside.
|
|
// This is to make sure we free headers appropriately on the send completes.
|
|
//
|
|
//
|
|
|
|
//
|
|
// Now hookup the MDLs
|
|
//
|
|
pContext->Flags |= SCF_AH_TU;
|
|
pContext->AHTuMdl = pAHBuffer;
|
|
pContext->PrevTuMdl = (PNDIS_BUFFER)pData;
|
|
pContext->OriTuMdl = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
|
|
NDIS_BUFFER_LINKAGE(pAHBuffer) = pHdrBuf;
|
|
|
|
if (hdrLen > sizeof(IPHeader)) {
|
|
if (HdrSpace < *pExtraBytes) {
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Going to frag."));
|
|
|
|
pSrcOptBuf = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
pSaveOptLinkage = NDIS_BUFFER_LINKAGE(pSrcOptBuf);
|
|
IPSecQueryNdisBuf(pSrcOptBuf, &pSrcOpt, &optLen);
|
|
IPSecAllocateBuffer(&status,
|
|
&pOptBuf,
|
|
(PUCHAR *)&pOpt,
|
|
hdrLen - sizeof(IPHeader),
|
|
IPSEC_TAG_AH);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
NTSTATUS ntstatus;
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Failed to alloc. PAD MDL"));
|
|
NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData) = pSaveDataLinkage;
|
|
IPSecFreeBuffer(&ntstatus, pAHBuffer);
|
|
IPSecFreeBuffer(&ntstatus, pHdrBuf);
|
|
pContext->Flags = saveFlags;
|
|
return status;
|
|
}
|
|
|
|
RtlCopyMemory(pOpt, pSrcOpt, hdrLen-sizeof(IPHeader));
|
|
pContext->OptMdl = pOptBuf;
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Copying options. S: %p, D: %p",pSrcOptBuf, pOptBuf));
|
|
|
|
//
|
|
// replace the original Opt Mdl with ours.
|
|
//
|
|
NDIS_BUFFER_LINKAGE(pOptBuf) = NDIS_BUFFER_LINKAGE(pSrcOptBuf);
|
|
NDIS_BUFFER_LINKAGE(pHdrBuf) = pOptBuf;
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Options; pointed Hdrbuf: %p to pOptBuf: %p", pHdrBuf, pOptBuf));
|
|
*pExtraBytes += hdrLen-sizeof(IPHeader);
|
|
|
|
} else {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Options; pointed Hdrbuf: %p to link(pData): %p", pHdrBuf, NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData)));
|
|
|
|
NDIS_BUFFER_LINKAGE(pHdrBuf) = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
}
|
|
} else {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("No options; pointed Hdrbuf: %p to link(pData): %p", pHdrBuf, NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData)));
|
|
|
|
NDIS_BUFFER_LINKAGE(pHdrBuf) = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
}
|
|
|
|
NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData) = pAHBuffer;
|
|
|
|
//
|
|
// xsum the new IP header since we expect that to be the case
|
|
// at this stage in tpt mode.
|
|
//
|
|
RtlCopyMemory(pIPH2, pIPH, sizeof(IPHeader));
|
|
|
|
//
|
|
// no options in the outer header; reset the len.
|
|
//
|
|
pIPH->iph_verlen = IP_VERSION + (sizeof(IPHeader) >> 2);
|
|
|
|
//
|
|
// also reset the frag. params.
|
|
//
|
|
pIPH->iph_offset &= ~(IP_MF_FLAG | IP_OFFSET_MASK);
|
|
|
|
if (DestIF->if_dfencap == ClearDfEncap) {
|
|
pIPH->iph_offset &= ~(IP_DF_FLAG | IP_OFFSET_MASK);
|
|
}
|
|
|
|
ASSERT(pSA->sa_TunnelAddr);
|
|
|
|
//
|
|
// Tunnel starts here; replace dest addr to point to Tunnel end if specified
|
|
// else tunnel ends at final dest
|
|
//
|
|
pIPH->iph_dest = pSA->sa_TunnelAddr;
|
|
|
|
//
|
|
// The first pended packet on a gateway (proxy negotiating for two subnets)
|
|
// would come via the transmit path. Hence the source address would not be
|
|
// kosher. We need to replace the src address in that case also.
|
|
// We get this from the corresponding inbound SA's tunnel addr.
|
|
//
|
|
pIPH->iph_src = pSA->sa_SrcTunnelAddr;
|
|
|
|
pIPH->iph_id = (ushort) TCPIP_GEN_IPID();
|
|
pIPH->iph_xsum = 0;
|
|
pIPH->iph_xsum = ~xsum(pIPH, sizeof(IPHeader));
|
|
|
|
//
|
|
// Set up headers so CreateHash works as in Tpt mode.
|
|
//
|
|
pIPHeader = (PUCHAR)pIPH;
|
|
*ppNewData = (PVOID)pData;
|
|
ipNext = ((UNALIGNED IPHeader *)pIPHeader)->iph_protocol;
|
|
pAH->ah_next = (UCHAR)IP_IN_IP;
|
|
} else {
|
|
*pExtraBytes += ahLen;
|
|
|
|
if (hdrLen > sizeof(IPHeader)) {
|
|
//
|
|
// Options present - chain AH after options
|
|
//
|
|
if (fOuterAH) {
|
|
pContext->Flags |= SCF_AH_2;
|
|
pContext->OriAHMdl2 = NDIS_BUFFER_LINKAGE(NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData));
|
|
pContext->PrevAHMdl2 = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
pAHBuffer->Next = pContext->OriAHMdl2;
|
|
} else {
|
|
pContext->Flags |= SCF_AH;
|
|
pContext->OriAHMdl = NDIS_BUFFER_LINKAGE(NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData));
|
|
pContext->PrevMdl = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
pAHBuffer->Next = pContext->OriAHMdl;
|
|
}
|
|
NDIS_BUFFER_LINKAGE(NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData)) = pAHBuffer;
|
|
} else {
|
|
//
|
|
// Chain the AH buffer after IP header
|
|
//
|
|
if (fOuterAH) {
|
|
pContext->Flags |= SCF_AH_2;
|
|
pContext->OriAHMdl2 = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
pContext->PrevAHMdl2 = (PNDIS_BUFFER)pData;
|
|
pAHBuffer->Next = pContext->OriAHMdl2;
|
|
} else {
|
|
pContext->Flags |= SCF_AH;
|
|
pContext->OriAHMdl = NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData);
|
|
pContext->PrevMdl = (PNDIS_BUFFER)pData;
|
|
pAHBuffer->Next = pContext->OriAHMdl;
|
|
}
|
|
NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData) = pAHBuffer;
|
|
}
|
|
if (fOuterAH) {
|
|
pContext->AHMdl2 = pAHBuffer;
|
|
} else {
|
|
pContext->AHMdl = pAHBuffer;
|
|
}
|
|
|
|
pAH->ah_next = ((UNALIGNED IPHeader *)pIPHeader)->iph_protocol;
|
|
}
|
|
|
|
//
|
|
// Initialize the other fields of the AH header
|
|
//
|
|
pAH->ah_len = (UCHAR)((pSA->sa_TruncatedLen + pSA->sa_ReplayLen) >> 2);
|
|
pAH->ah_reserved = 0;
|
|
pAH->ah_spi = HOST_TO_NET_LONG(pSA->sa_OtherSPIs[Index]);
|
|
Seq = IPSEC_INCREMENT(pSA->sa_ReplaySendSeq[Index]);
|
|
pAH->ah_replay = HOST_TO_NET_LONG(Seq);
|
|
|
|
//
|
|
// Update the IP total length to reflect the AH header
|
|
//
|
|
IPLength = NET_SHORT(pIPH->iph_length) + (USHORT)ahLen;
|
|
if (fTunnel) {
|
|
IPLength += sizeof(IPHeader);
|
|
}
|
|
|
|
UpdateIPLength(pIPH, NET_SHORT(IPLength));
|
|
UpdateIPProtocol(pIPH, PROTOCOL_AH);
|
|
|
|
ADD_TO_LARGE_INTEGER(
|
|
&pSA->sa_Stats.AuthenticatedBytesSent,
|
|
NET_SHORT(pIPH->iph_length));
|
|
|
|
ADD_TO_LARGE_INTEGER(
|
|
&g_ipsec.Statistics.uAuthenticatedBytesSent,
|
|
NET_SHORT(pIPH->iph_length));
|
|
|
|
//
|
|
// Generate the Hash.
|
|
//
|
|
if (!fCryptoOnly) {
|
|
status = IPSecGenerateHash( pIPHeader,
|
|
(PVOID)pData,
|
|
pSA,
|
|
(PUCHAR)(pAH + 1),
|
|
fMuteDest,
|
|
FALSE, // not on recv path
|
|
pAlgo,
|
|
Index);
|
|
if (!NT_SUCCESS(status)) {
|
|
NTSTATUS ntstatus;
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Failed to hash, pAH: %p", pAH));
|
|
NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData) = pSaveDataLinkage;
|
|
if (pSaveOptLinkage) {
|
|
NDIS_BUFFER_LINKAGE(NDIS_BUFFER_LINKAGE((PNDIS_BUFFER)pData)) = pSaveOptLinkage;
|
|
}
|
|
IPSecFreeBuffer(&ntstatus, pAHBuffer);
|
|
if (pHdrBuf) {
|
|
IPSecFreeBuffer(&ntstatus, pHdrBuf);
|
|
}
|
|
if (pOptBuf) {
|
|
IPSecFreeBuffer(&ntstatus, pOptBuf);
|
|
}
|
|
pContext->Flags = saveFlags;
|
|
*ppNewData = NULL;
|
|
return status;
|
|
}
|
|
} else {
|
|
//
|
|
// Zero out the hash.
|
|
//
|
|
IPSecZeroMemory((PUCHAR)(pAH + 1), pSA->sa_TruncatedLen);
|
|
}
|
|
|
|
//
|
|
// Bump up the bytes transformed count.
|
|
//
|
|
ADD_TO_LARGE_INTEGER(
|
|
&pSA->sa_TotalBytesTransformed,
|
|
NET_SHORT(pIPH->iph_length));
|
|
|
|
//
|
|
// Return modified packet.
|
|
//
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Exiting IPSecCreateAH, ahLen: %lx, status: %lx", ahLen, status));
|
|
|
|
#if DBG
|
|
IPSEC_DEBUG(LL_A,DBF_MDL, ("Exiting IPSecCreateAH"));
|
|
IPSEC_PRINT_CONTEXT(*ppSCContext);
|
|
if (*ppNewData) {
|
|
IPSEC_PRINT_MDL(*ppNewData);
|
|
}
|
|
else {
|
|
IPSEC_PRINT_MDL(pData);
|
|
}
|
|
#endif
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
IPSecVerifyAH(
|
|
IN PUCHAR *pIPHeader,
|
|
IN PVOID pData,
|
|
IN PSA_TABLE_ENTRY pSA,
|
|
IN ULONG Index,
|
|
OUT PULONG pExtraBytes,
|
|
IN BOOLEAN fSrcRoute,
|
|
IN BOOLEAN fCryptoDone,
|
|
IN BOOLEAN fFastRcv
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Verify the AH, given the packet. If AH kosher, strips off the AH from
|
|
pData.
|
|
|
|
Arguments:
|
|
|
|
pIPHeader - points to start of IP header.
|
|
|
|
pData - points to the data after the IP header.
|
|
|
|
pSA - Sec. Assoc. entry
|
|
|
|
pExtraBytes - out param to inform IP on recv path how many bytes IPSEC took off.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS
|
|
Others:
|
|
STATUS_UNSUCCESSFUL (packet not kosher - bad AH)
|
|
STATUS_INSUFFICIENT_RESOURCES
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PUCHAR pPyld;
|
|
ULONG Len;
|
|
LONG ahLen;
|
|
LONG totalLen;
|
|
UCHAR Buf[MAX_AH_OUTPUT_LEN];
|
|
PUCHAR pAHData = Buf;
|
|
IPHeader UNALIGNED *pIPH = (IPHeader UNALIGNED *)*pIPHeader;
|
|
ULONG extraBytes = 0;
|
|
ULONG hdrLen;
|
|
PAUTH_ALGO pAlgo;
|
|
USHORT FilterFlags;
|
|
BOOLEAN fTunnel = ((pSA->sa_Flags & FLAGS_SA_TUNNEL) &&
|
|
((Index == 0) ||
|
|
((Index == 1) && (pSA->sa_Operation[0] == Compress))));
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Entering IPSecVerifyAH"));
|
|
|
|
ASSERT(pSA->sa_Operation[Index] == Auth);
|
|
|
|
if (pSA->INT_ALGO(Index) > NUM_AUTH_ALGOS) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
hdrLen = (pIPH->iph_verlen & (UCHAR)~IP_VER_FLAG) << 2;
|
|
|
|
pAlgo = &(auth_algorithms[pSA->INT_ALGO(Index)]);
|
|
|
|
ahLen = sizeof(AH) + pSA->sa_TruncatedLen * sizeof(UCHAR);
|
|
|
|
IPSEC_GET_TOTAL_LEN_RCV_BUF(pData, &totalLen);
|
|
|
|
//
|
|
// Do we have enough in the buffer?
|
|
//
|
|
if (totalLen < ahLen) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Compare the hash with the AH from packet
|
|
// First buffer has the AH
|
|
//
|
|
IPSecQueryRcvBuf(pData, &pPyld, &Len);
|
|
|
|
//
|
|
// Size OK?
|
|
//
|
|
if (((UNALIGNED AH *)pPyld)->ah_len !=
|
|
(UCHAR)((pSA->sa_TruncatedLen + pSA->sa_ReplayLen) >> 2)) {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Failed size check: in: %x, need: %x",
|
|
((UNALIGNED AH *)pPyld)->ah_len,
|
|
(UCHAR)((pSA->sa_TruncatedLen + pSA->sa_ReplayLen) >> 2)));
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Generate the Hash
|
|
//
|
|
if (!fCryptoDone) {
|
|
status = IPSecGenerateHash( *pIPHeader,
|
|
pData,
|
|
pSA,
|
|
pAHData,
|
|
fSrcRoute,
|
|
TRUE,
|
|
pAlgo,
|
|
Index); // on recv path
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Failed to hash, pData: %p", pData));
|
|
return status;
|
|
}
|
|
|
|
if (!IPSecEqualMemory( pAHData,
|
|
pPyld + sizeof(AH),
|
|
pSA->sa_TruncatedLen)) {
|
|
|
|
IPSecBufferEvent( pIPH->iph_src,
|
|
EVENT_IPSEC_AUTH_FAILURE,
|
|
1,
|
|
TRUE);
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Failed to compare, pPyld: %p, pAHData: %p", pPyld, pAHData));
|
|
IPSEC_DEBUG(LL_A,DBF_GENHASH, ("AHData: %lx-%lx-%lx",
|
|
*(ULONG *)&(pAHData)[0],
|
|
*(ULONG *)&(pAHData)[4],
|
|
*(ULONG *)&(pAHData)[8]));
|
|
IPSEC_DEBUG(LL_A,DBF_GENHASH, ("PyldHash: %lx-%lx-%lx",
|
|
*(ULONG *)&((UCHAR *)(pPyld + sizeof(AH)))[0],
|
|
*(ULONG *)&((UCHAR *)(pPyld + sizeof(AH)))[4],
|
|
*(ULONG *)&((UCHAR *)(pPyld + sizeof(AH)))[8]));
|
|
IPSEC_INC_STATISTIC(dwNumPacketsNotAuthenticated);
|
|
|
|
return IPSEC_INVALID_AH;
|
|
}
|
|
}
|
|
|
|
ADD_TO_LARGE_INTEGER(
|
|
&pSA->sa_Stats.AuthenticatedBytesReceived,
|
|
NET_SHORT(pIPH->iph_length));
|
|
|
|
ADD_TO_LARGE_INTEGER(
|
|
&g_ipsec.Statistics.uAuthenticatedBytesReceived,
|
|
NET_SHORT(pIPH->iph_length));
|
|
|
|
//
|
|
// Check the replay window
|
|
//
|
|
status=IPSecChkReplayWindow(
|
|
NET_TO_HOST_LONG(((UNALIGNED AH *)pPyld)->ah_replay),
|
|
pSA,
|
|
Index);
|
|
if (!NT_SUCCESS(status)) {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Replay check failed, pPyld: %p, pAHData: %p", pPyld, pAHData));
|
|
IPSEC_INC_STATISTIC(dwNumPacketsWithReplayDetection);
|
|
return status;
|
|
}
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("IP Len: %lx", pIPH->iph_length));
|
|
|
|
pIPH->iph_length = NET_SHORT(NET_SHORT(pIPH->iph_length) - (USHORT)ahLen);
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("IP Len: %lx", pIPH->iph_length));
|
|
|
|
//
|
|
// Restore the protocol from AH header
|
|
//
|
|
pIPH->iph_protocol = ((UNALIGNED AH *)pPyld)->ah_next;
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Matched!! Restored protocol %x", pIPH->iph_protocol));
|
|
|
|
//
|
|
// Remove the AH from the packet
|
|
//
|
|
IPSEC_SET_OFFSET_IN_BUFFER(pData, ahLen);
|
|
|
|
//
|
|
// Move the IP header forward for filter/firewall hook, fast path only.
|
|
//
|
|
if (fFastRcv) {
|
|
IPSecMoveMemory(((PUCHAR)pIPH) + ahLen, (PUCHAR)pIPH, hdrLen);
|
|
*pIPHeader=(PUCHAR)pIPH+ahLen;
|
|
pIPH = (IPHeader UNALIGNED *)*pIPHeader;
|
|
}
|
|
|
|
extraBytes += ahLen;
|
|
|
|
//
|
|
// Bump up the bytes transformed count.
|
|
//
|
|
ADD_TO_LARGE_INTEGER(
|
|
&pSA->sa_TotalBytesTransformed,
|
|
NET_SHORT(pIPH->iph_length));
|
|
|
|
if (fTunnel) {
|
|
if (pIPH->iph_protocol != IP_IN_IP) {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("BAD protocol in IP: %x", pIPH->iph_protocol));
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
*pExtraBytes += extraBytes;
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Exiting IPSecVerifyAH"));
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
IPSecGenerateHash(
|
|
IN PUCHAR pIPHeader,
|
|
IN PVOID pData,
|
|
IN PSA_TABLE_ENTRY pSA,
|
|
IN PUCHAR pAHData,
|
|
IN BOOLEAN fMuteDest,
|
|
IN BOOLEAN fIncoming,
|
|
IN PAUTH_ALGO pAlgo,
|
|
IN ULONG Index
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Arguments:
|
|
|
|
pIPHeader - points to start of IP header.
|
|
|
|
pData - points to the entire IP datagram, starting at the IP Header
|
|
|
|
pSA - Sec. Assoc. entry
|
|
|
|
pAHData - buffer to contain the generated hash
|
|
|
|
fIncoming - TRUE if on recv path.
|
|
|
|
pAlgo - the auth_algo being used
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS
|
|
Others:
|
|
STATUS_UNSUCCESSFUL (packet not kosher - bad AH)
|
|
STATUS_INSUFFICIENT_RESOURCES
|
|
|
|
--*/
|
|
{
|
|
ULONG numBytesPayload;
|
|
ULONG i;
|
|
PUCHAR pPayload;
|
|
IPHeader UNALIGNED *pIPH = (UNALIGNED IPHeader *)pIPHeader;
|
|
PUCHAR pOptions;
|
|
PNDIS_BUFFER pBuf = (PNDIS_BUFFER)pData;
|
|
ULONG hdrLen;
|
|
ULONG ahLen;
|
|
NTSTATUS status;
|
|
ALGO_STATE State = {0};
|
|
BOOLEAN fTunnel = ( (pSA->sa_Flags & FLAGS_SA_TUNNEL) &&
|
|
((Index == 0) ||
|
|
((Index == 1) && (pSA->sa_Operation[0] == Compress))));
|
|
|
|
//
|
|
// These are saved since they can change enroute
|
|
//
|
|
//
|
|
// Scratch array used for AH calculation
|
|
//
|
|
UCHAR zero[MAX_IP_OPTION_SIZE];
|
|
UCHAR savetos; // Type of service.
|
|
USHORT saveoffset; // Flags and fragment offset.
|
|
UCHAR savettl; // Time to live.
|
|
USHORT savexsum; // Header checksum.
|
|
IPAddr savedest; // Dest address.
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Entering IPSecGenerateHash"));
|
|
|
|
ahLen = sizeof(AH) + pSA->sa_TruncatedLen * sizeof(UCHAR);
|
|
|
|
State.as_sa = pSA;
|
|
IPSecZeroMemory(zero, sizeof(zero));
|
|
|
|
status = pAlgo->init(&State, Index);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("init failed: %lx", status));
|
|
}
|
|
|
|
//
|
|
// Save, then zero out fields that can change enroute
|
|
//
|
|
savetos = pIPH->iph_tos;
|
|
saveoffset = pIPH->iph_offset;
|
|
savettl = pIPH->iph_ttl;
|
|
savexsum = pIPH->iph_xsum;
|
|
|
|
pIPH->iph_tos = 0;
|
|
pIPH->iph_offset = 0;
|
|
pIPH->iph_ttl = 0;
|
|
pIPH->iph_xsum = 0;
|
|
|
|
//
|
|
// Mute dest address as well if source routing
|
|
//
|
|
if (fMuteDest) {
|
|
savedest = pIPH->iph_dest;
|
|
pIPH->iph_dest = 0;
|
|
}
|
|
|
|
//
|
|
// Call MD5 to create the header hash
|
|
//
|
|
pAlgo->update(&State, pIPHeader, sizeof(IPHeader));
|
|
|
|
#if DBG
|
|
if (fIncoming) {
|
|
IPSEC_DEBUG(LL_A,DBF_GENHASH, ("IPHeader to Hash: %lx-%lx-%lx-%lx-%lx",
|
|
*(ULONG *)&(pIPHeader)[0],
|
|
*(ULONG *)&(pIPHeader)[4],
|
|
*(ULONG *)&(pIPHeader)[8],
|
|
*(ULONG *)&(pIPHeader)[12],
|
|
*(ULONG *)&(pIPHeader)[16]));
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Restore the zeroed fields
|
|
//
|
|
pIPH->iph_tos = savetos;
|
|
pIPH->iph_offset = saveoffset;
|
|
pIPH->iph_ttl = savettl;
|
|
pIPH->iph_xsum = savexsum;
|
|
|
|
//
|
|
// Restore dest address as well for source routing
|
|
//
|
|
if (fMuteDest) {
|
|
pIPH->iph_dest = savedest;
|
|
}
|
|
|
|
//
|
|
// Now, do the options if they exist
|
|
//
|
|
hdrLen = (pIPH->iph_verlen & (UCHAR)~IP_VER_FLAG) << 2;
|
|
|
|
if (hdrLen > sizeof(IPHeader)) {
|
|
UCHAR cLength;
|
|
ULONG uIndex = 0;
|
|
ULONG uOptLen = hdrLen - sizeof(IPHeader);
|
|
|
|
ASSERT(!fTunnel);
|
|
|
|
if (fIncoming) {
|
|
pOptions = (PUCHAR)(pIPH + 1);
|
|
} else {
|
|
//
|
|
// Options are in second MDL... on send side
|
|
//
|
|
pBuf = NDIS_BUFFER_LINKAGE(pBuf);
|
|
IPSecQueryNdisBuf(pBuf, &pOptions, &uOptLen);
|
|
}
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Got options: %p", pOptions));
|
|
|
|
//
|
|
// Some options may need to be zeroed out...
|
|
//
|
|
while (uIndex < uOptLen) {
|
|
switch (*pOptions) {
|
|
case IP_OPT_EOL:
|
|
pAlgo->update(&State, zero, 1);
|
|
uIndex = uOptLen;
|
|
break;
|
|
|
|
//
|
|
// Zeroed for AH calculation
|
|
//
|
|
case IP_OPT_NOP:
|
|
pAlgo->update(&State, zero, 1);
|
|
uIndex++;
|
|
pOptions++;
|
|
break;
|
|
|
|
case IP_OPT_LSRR:
|
|
case IP_OPT_SSRR:
|
|
case IP_OPT_RR:
|
|
case IP_OPT_TS:
|
|
cLength = pOptions[IP_OPT_LENGTH];
|
|
pAlgo->update(&State, zero, cLength);
|
|
uIndex += cLength;
|
|
pOptions += cLength;
|
|
break;
|
|
|
|
//
|
|
// Assumed invariant; used for AH calc
|
|
//
|
|
case IP_OPT_ROUTER_ALERT:
|
|
case IP_OPT_SECURITY:
|
|
default:
|
|
cLength = pOptions[IP_OPT_LENGTH];
|
|
pAlgo->update(&State, pOptions, cLength);
|
|
uIndex += cLength;
|
|
pOptions += cLength;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Go over the remaining payload, creating the hash
|
|
//
|
|
// NOTE: We differentiate between the send and recv since the
|
|
// buffer formats are different
|
|
//
|
|
if (fIncoming) {
|
|
IPRcvBuf *pRcvBuf = (IPRcvBuf *)pData;
|
|
ULONG Len;
|
|
LONG remainLen;
|
|
|
|
UCHAR UNALIGNED *pPyld;
|
|
|
|
//
|
|
// First buffer shd be the AH itself
|
|
//
|
|
IPSecQueryRcvBuf(pRcvBuf, &pPyld, &Len);
|
|
|
|
//
|
|
// Do the first portion of the header.
|
|
//
|
|
pAlgo->update(&State, pPyld, sizeof(AH));
|
|
|
|
#if DBG
|
|
if (fIncoming) {
|
|
IPSEC_DEBUG(LL_A,DBF_GENHASH, ("AHHeader to Hash: %lx-%lx-%lx",
|
|
*(ULONG *)&(pPyld)[0],
|
|
*(ULONG *)&(pPyld)[4],
|
|
*(ULONG *)&(pPyld)[8]));
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// The authentication data should be considered as 0.
|
|
// In our case, the data length is fixed at pSA->sa_TruncatedLen bytes
|
|
//
|
|
pAlgo->update(&State, zero, pSA->sa_TruncatedLen);
|
|
|
|
//
|
|
// Jump over the remaining AH: need to take care of situations
|
|
// where ICV is chained (Raid 146275).
|
|
//
|
|
if (((LONG)Len - (LONG)ahLen) >= 0) {
|
|
pPyld += ahLen;
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Jumped over IPSEC res: %p, len: %lx", pPyld, Len));
|
|
|
|
//
|
|
// Tpt header is right after AH
|
|
//
|
|
pAlgo->update(&State, pPyld, Len - ahLen);
|
|
} else {
|
|
//
|
|
// Need to jump over ICV if it expands over multiple buffers
|
|
//
|
|
remainLen = pSA->sa_TruncatedLen - (Len - sizeof(AH));
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Jumped over IPSEC res: %p, remainlen: %lx", pPyld, remainLen));
|
|
while (remainLen > 0 && (pRcvBuf = IPSEC_BUFFER_LINKAGE(pRcvBuf))) {
|
|
IPSecQueryRcvBuf(pRcvBuf, &pPyld, &Len);
|
|
remainLen -= Len;
|
|
}
|
|
|
|
//
|
|
// Do the possible partial data after AH
|
|
//
|
|
if (remainLen < 0 && pRcvBuf) {
|
|
pPyld += Len + remainLen;
|
|
pAlgo->update(&State, pPyld, -remainLen);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now do the remaining chain
|
|
//
|
|
while (pRcvBuf = IPSEC_BUFFER_LINKAGE(pRcvBuf)) {
|
|
IPSecQueryRcvBuf(pRcvBuf, &pPyld, &Len);
|
|
pAlgo->update(&State, pPyld, Len);
|
|
}
|
|
} else {
|
|
UCHAR UNALIGNED *pPyld;
|
|
ULONG Len;
|
|
|
|
//
|
|
// Second (or third if options present) buffer shd be the AH itself
|
|
//
|
|
pBuf = NDIS_BUFFER_LINKAGE(pBuf);
|
|
IPSecQueryNdisBuf(pBuf, &pPyld, &Len);
|
|
|
|
//
|
|
// Do the first portion of the header.
|
|
//
|
|
pAlgo->update(&State, pPyld, sizeof(AH));
|
|
|
|
//
|
|
// The authentication data should be considered as 0.
|
|
// In our case, the data length is fixed at pSA->sa_TruncatedLen bytes
|
|
//
|
|
pAlgo->update(&State, zero, pSA->sa_TruncatedLen);
|
|
|
|
//
|
|
// Skip over the remaining AH section
|
|
//
|
|
pPyld += ahLen;
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Jumped over IPSEC Len: %lx, hdrlen: %lx", Len, hdrLen));
|
|
|
|
pAlgo->update(&State, pPyld, Len - ahLen);
|
|
|
|
//
|
|
// Now do the remaining chain
|
|
//
|
|
while (pBuf = NDIS_BUFFER_LINKAGE(pBuf)) {
|
|
IPSecQueryNdisBuf(pBuf, &pPyld, &Len);
|
|
pAlgo->update(&State, pPyld, Len);
|
|
}
|
|
}
|
|
|
|
pAlgo->finish(&State, pAHData, Index);
|
|
|
|
//
|
|
// Copy out the hash - get the truncated hash out, then zero out the rest
|
|
//
|
|
TRUNCATE(pAHData, pAHData, pSA->sa_TruncatedLen, MD5DIGESTLEN);
|
|
|
|
IPSEC_DEBUG(LL_A,DBF_AH, ("Exiting IPSecGenerateMD5"));
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|