|
|
/*++
Copyright (c) 1997-2001 Microsoft Corporation
Module Name:
esp.c
Abstract:
This module contains the code to create/verify the ESP headers.
Author:
Sanjay Anand (SanjayAn) 2-January-1997 ChunYe
Environment:
Kernel mode
Revision History:
--*/
#include "precomp.h"
#ifdef RUN_WPP
#include "esp.tmh"
#endif
#ifndef _TEST_PERF
CONFID_ALGO conf_algorithms[] = { { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN}, { esp_desinit, esp_desencrypt, esp_desdecrypt, DES_BLOCKLEN}, { esp_desinit, esp_desencrypt, esp_desdecrypt, DES_BLOCKLEN}, { esp_3_desinit, esp_3_desencrypt, esp_3_desdecrypt, DES_BLOCKLEN}, }; #else
CONFID_ALGO conf_algorithms[] = { { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN}, { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN}, { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN}, { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN}, }; #endif
VOID esp_nullinit ( IN PVOID pState, IN PUCHAR pKey ) { return; }
NTSTATUS esp_nullencrypt ( PVOID pState, PUCHAR pOut, PUCHAR pIn, PUCHAR pIV ) { RtlCopyMemory(pOut, pIn, DES_BLOCKLEN);
return STATUS_SUCCESS; }
NTSTATUS esp_nulldecrypt ( PVOID pState, PUCHAR pOut, PUCHAR pIn, PUCHAR pIV ) { return STATUS_SUCCESS; }
VOID esp_desinit ( IN PVOID pState, IN PUCHAR pKey ) { DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
IPSEC_DES_KEY(Table, pKey); }
NTSTATUS esp_desencrypt ( PVOID pState, PUCHAR pOut, PUCHAR pIn, PUCHAR pIV ) { DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
if (IPSEC_CBC(IPSEC_DES_ALGO, pOut, pIn, // pChunk,
Table, ENCRYPT, pIV)) { return STATUS_SUCCESS; } else { return STATUS_UNSUCCESSFUL; } }
NTSTATUS esp_desdecrypt ( PVOID pState, PUCHAR pOut, PUCHAR pIn, PUCHAR pIV ) { DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
if (IPSEC_CBC(IPSEC_DES_ALGO, pOut, pIn, // pChunk,
Table, DECRYPT, pIV)) { return STATUS_SUCCESS; } else { return STATUS_UNSUCCESSFUL; } }
VOID esp_3_desinit ( IN PVOID pState, IN PUCHAR pKey ) { DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
IPSEC_3DES_KEY(Table, pKey); }
NTSTATUS esp_3_desencrypt ( PVOID pState, PUCHAR pOut, PUCHAR pIn, PUCHAR pIV ) { DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
if (IPSEC_CBC(IPSEC_3DES_ALGO, pOut, pIn, // pChunk,
Table, ENCRYPT, pIV)) { return STATUS_SUCCESS; } else { return STATUS_UNSUCCESSFUL; } }
NTSTATUS esp_3_desdecrypt ( PVOID pState, PUCHAR pOut, PUCHAR pIn, PUCHAR pIV ) { DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
if (IPSEC_CBC(IPSEC_3DES_ALGO, pOut, pIn, // pChunk,
Table, DECRYPT, pIV)) { return STATUS_SUCCESS; } else { return STATUS_UNSUCCESSFUL; } }
IPRcvBuf * CopyToRcvBuf( IN IPRcvBuf *DestBuf, IN PUCHAR SrcBuf, IN ULONG Size, IN PULONG StartOffset ) /*++
Copy a flat buffer to an IPRcvBuf chain.
A utility function to copy a flat buffer to an NDIS buffer chain. We assume that the NDIS_BUFFER chain is big enough to hold the copy amount; in a debug build we'll debugcheck if this isn't true. We return a pointer to the buffer where we stopped copying, and an offset into that buffer. This is useful for copying in pieces into the chain.
Input:
DestBuf - Destination IPRcvBuf chain. SrcBuf - Src flat buffer. Size - Size in bytes to copy. StartOffset - Pointer to start of offset into first buffer in chain. Filled in on return with the offset to copy into next.
Returns:
Pointer to next buffer in chain to copy into.
--*/ { UINT CopySize; UCHAR *DestPtr; UINT DestSize; UINT Offset = *StartOffset; UCHAR *VirtualAddress; UINT Length;
if (DestBuf == NULL || SrcBuf == NULL) { ASSERT(FALSE); return NULL; }
IPSecQueryRcvBuf(DestBuf, &VirtualAddress, &Length); ASSERT(Length >= Offset); DestPtr = VirtualAddress + Offset; DestSize = Length - Offset;
for (;;) { CopySize = MIN(Size, DestSize); RtlCopyMemory(DestPtr, SrcBuf, CopySize);
DestPtr += CopySize; SrcBuf += CopySize;
if ((Size -= CopySize) == 0) break;
if ((DestSize -= CopySize) == 0) { DestBuf = IPSEC_BUFFER_LINKAGE(DestBuf); if (DestBuf == NULL) { ASSERT(FALSE); break; }
IPSecQueryRcvBuf(DestBuf, &VirtualAddress, &Length);
DestPtr = VirtualAddress; DestSize = Length; } }
*StartOffset = (ULONG)(DestPtr - VirtualAddress);
return DestBuf;
}
NTSTATUS IPSecEncryptBuffer( IN PVOID pData, IN PNDIS_BUFFER *ppNewMdl, IN PSA_TABLE_ENTRY pSA, IN PNDIS_BUFFER pPadBuf, OUT PULONG pPadLen, IN ULONG PayloadType, IN ULONG Index, IN PUCHAR feedback ) { CONF_STATE_BUFFER Key; PCONFID_ALGO pConfAlgo; UCHAR scratch[MAX_BLOCKLEN]; // scratch buffer for the encrypt
UCHAR scratch1[MAX_BLOCKLEN]; // scratch buffer for the encrypt
PUCHAR pDest=NULL; PNDIS_BUFFER pEncryptMdl; ULONG len; ULONG blockLen; NTSTATUS status, dummystatus;
IPSEC_DEBUG(LL_A, DBF_ESP, ("Entering IPSecEncryptBuffer: pData: %p", pData));
if (pSA->CONF_ALGO(Index) > NUM_CONF_ALGOS) { ASSERT(FALSE); return STATUS_INVALID_PARAMETER; }
pConfAlgo = &(conf_algorithms[pSA->CONF_ALGO(Index)]); blockLen = pConfAlgo->blocklen;
//
// set up the state buffer
//
pConfAlgo->init((PVOID)&Key, pSA->CONF_KEY(Index));
IPSEC_DEBUG(LL_A, DBF_HUGHES, ("pConfAlgo: %p, blockLen: %lx IV: %lx-%lx", pConfAlgo, blockLen, *(PULONG)&feedback[0], *(PULONG)&feedback[4]));
if (*ppNewMdl == NULL) { //
// We should not encrypt in place: so we alloc a new buffer
// Count up the total size and allocate the new buffer.
// use that buffer as the dest of the encrypt.
//
IPSEC_GET_TOTAL_LEN(pData, &len); #if DBG
if ((len % 8) != 0) { DbgPrint("Length not kosher: pData: %p, len: %d, pPadBuf: %p, pPadLen: %d", pData, len, pPadBuf, pPadLen); DbgBreakPoint(); } #endif
IPSecAllocateBuffer(&status, &pEncryptMdl, &pDest, len, IPSEC_TAG_ESP);
if (!NT_SUCCESS(status)) { NTSTATUS ntstatus; //ASSERT(FALSE);
IPSEC_DEBUG(LL_A, DBF_ESP, ("Failed to alloc. encrypt MDL")); return status; }
IPSEC_DEBUG(LL_A, DBF_ESP, ("Alloc. MDL: %p, pDest: %p, len: %d, pData: %p", pEncryptMdl, pDest, len, pData)); } else { ASSERT(FALSE); IPSecQueryNdisBuf(*ppNewMdl, &pDest, &len); pEncryptMdl = *ppNewMdl; }
//
// Now, send 64 bit (8 octet) chunks to CBC. We need to make sure
// that the data is divided on contiguous 8 byte boundaries across
// different buffers.
//
{ PNDIS_BUFFER pBuf = (PNDIS_BUFFER)pData; ULONG bytesDone = 0; ULONG bytesLeft; PUCHAR pChunk;
while (pBuf) {
IPSecQueryNdisBuf(pBuf, &pChunk, &bytesLeft);
pChunk += bytesDone; bytesLeft -= bytesDone;
IPSEC_DEBUG(LL_A, DBF_ESP, ("ESP: pChunk: %p, bytesLeft: %d, bytesDone: %d", pChunk, bytesLeft, bytesDone));
bytesDone = 0;
while (bytesLeft >= blockLen) { //
// Create the cipher.
//
status = pConfAlgo->encrypt( (PVOID)&Key, pDest, pChunk, feedback); if (!NT_SUCCESS(status)) { IPSecFreeBuffer(&dummystatus, pEncryptMdl); return status; }
pChunk += blockLen; bytesLeft -= blockLen; pDest += blockLen; }
//
// Check here if we need to collate blocks
//
if (NDIS_BUFFER_LINKAGE(pBuf) != NULL) { PUCHAR pNextChunk; ULONG nextSize;
//
// If some left over from prev. buffer, collate with next
// block
//
if (bytesLeft) { ULONG offset = bytesLeft; // offset into scratch
ULONG bytesToCollect = blockLen - bytesLeft; // # of bytes to collect from next few MDLs
IPSEC_DEBUG(LL_A, DBF_ESP, ("ESP: pChunk: %p, bytesLeft: %d", pChunk, bytesLeft));
ASSERT(bytesLeft < blockLen);
//
// Copy into a scratch buffer
//
RtlCopyMemory( scratch, pChunk, bytesLeft);
do { ASSERT(NDIS_BUFFER_LINKAGE(pBuf)); IPSecQueryNdisBuf(NDIS_BUFFER_LINKAGE(pBuf), &pNextChunk, &nextSize);
if (nextSize >= (blockLen - offset)) { RtlCopyMemory( scratch+offset, pNextChunk, blockLen - offset); bytesDone = blockLen - offset;
bytesToCollect -= (blockLen - offset); ASSERT(bytesToCollect == 0); } else { IPSEC_DEBUG(LL_A, DBF_ESP, ("special case, offset: %d, bytesLeft: %d, nextSize: %d, pNextChunk: %p", offset, bytesLeft, nextSize, pNextChunk));
RtlCopyMemory( scratch+offset, pNextChunk, nextSize);
bytesToCollect -= nextSize; ASSERT(bytesToCollect);
offset += nextSize; ASSERT(offset < blockLen);
ASSERT(bytesDone == 0); pBuf = NDIS_BUFFER_LINKAGE(pBuf); } } while (bytesToCollect);
status = pConfAlgo->encrypt( (PVOID)&Key, pDest, scratch, feedback); if (!NT_SUCCESS(status)) { IPSecFreeBuffer(&dummystatus, pEncryptMdl); return status; }
pDest += blockLen; } } else { PUCHAR pPad; ULONG padLen; ULONG bufLen;
//
// End of the chain; pad with length and type to 8 byte boundary
//
ASSERT(bytesLeft < blockLen);
// if ((pSA->sa_eOperation == HUGHES_TRANSPORT) ||
// (pSA->sa_eOperation == HUGHES_TUNNEL)) {
//
// since only hughes is done now, this shd be always true.
//
if (TRUE) { ASSERT(bytesLeft == 0);
//
// DONE: break out
//
break; } } pBuf = NDIS_BUFFER_LINKAGE(pBuf); }
//
// save IV for next encrypt cycle
//
RtlCopyMemory( pSA->sa_iv[Index], feedback, pSA->sa_ivlen);
IPSEC_DEBUG(LL_A, DBF_HUGHES, ("IV: %lx-%lx", *(PULONG)&feedback[0], *(PULONG)&feedback[4])); } #if DBG
{ ULONG totalLen;
IPSEC_GET_TOTAL_LEN(pEncryptMdl, &totalLen); ASSERT((totalLen % 8) == 0); IPSEC_DEBUG(LL_A, DBF_ESP, ("total len: %lx", totalLen)); } #endif
IPSEC_DEBUG(LL_A, DBF_ESP, ("Exiting IPSecEncryptBuffer"));
*ppNewMdl = pEncryptMdl;
return STATUS_SUCCESS; }
NTSTATUS IPSecDecryptBuffer( IN PVOID pData, IN PSA_TABLE_ENTRY pSA, OUT PUCHAR pPadLen, OUT PUCHAR pPayloadType, IN ULONG Index, IN ULONG ESPOffset // offset from start of pData where ESP header starts
) { CONF_STATE_BUFFER Key; PCONFID_ALGO pConfAlgo; UCHAR feedback[MAX_BLOCKLEN]; UCHAR scratch[MAX_BLOCKLEN]; // scratch buffer for the encrypt
UCHAR scratch1[MAX_BLOCKLEN]; // scratch buffer for the encrypt
LONG Len; UCHAR padLen; UCHAR payloadType; LONG hdrLen; IPHeader UNALIGNED *pIPH; ESP UNALIGNED *pEsp; PUCHAR savePtr; LONG saveLen; LONG espLen = sizeof(ESP) + pSA->sa_ivlen + pSA->sa_ReplayLen + ESPOffset; LONG blockLen; NTSTATUS status; IPRcvBuf TmpRcvBuf; IPRcvBuf *pBuf,*SavedMdl=NULL;
if (pSA->CONF_ALGO(Index) > NUM_CONF_ALGOS) { return STATUS_INVALID_PARAMETER; }
pConfAlgo = &(conf_algorithms[pSA->CONF_ALGO(Index)]); blockLen = pConfAlgo->blocklen;
//
// set up the state buffer
//
pConfAlgo->init((PVOID)&Key, pSA->CONF_KEY(Index));
IPSecQueryRcvBuf(pData, (PUCHAR)&pEsp, &Len);
//
// Init the CBC feedback from the IV in the packet
//
// Actually if the sa_ivlen is 0, use the pSA one
//
if (pSA->sa_ivlen) { if (Len >=(LONG)(sizeof(ESP) + pSA->sa_ReplayLen + pSA->sa_ivlen + ESPOffset)) { RtlCopyMemory( feedback, ((PUCHAR)(pEsp + 1) + pSA->sa_ReplayLen + ESPOffset), pSA->sa_ivlen); } else { status = IPSecGetRecvBytesByOffset(pData, sizeof(ESP)+pSA->sa_ReplayLen + ESPOffset, feedback, pSA->sa_ivlen); if (!NT_SUCCESS(status)) { return status; } }
IPSEC_DEBUG(LL_A, DBF_ESP, ("IV: %lx-%lx", *(PULONG)&feedback[0], *(PULONG)&feedback[4])); } else { RtlCopyMemory( feedback, pSA->sa_iv[Index], DES_BLOCKLEN); }
//
// Bump the current pointer to after the ESP header
//
if (((IPRcvBuf *)pData)->ipr_size >= (ULONG)espLen) { ((IPRcvBuf *)pData)->ipr_size -= (ULONG)espLen; savePtr = ((IPRcvBuf *)pData)->ipr_buffer; saveLen = espLen;
((IPRcvBuf *)pData)->ipr_buffer = savePtr + espLen; } else { status = IPSecFindAndSetMdlByOffset((IPRcvBuf*)pData,(ULONG)espLen,&(IPRcvBuf*)pData,&savePtr,&saveLen); if (!NT_SUCCESS(status)) { return status; } }
//
// Now, send 64 bit (8 octet) chunks to CBC. We need to make sure
// that the data is divided on contiguous 8 byte boundaries across
// different buffers.
// NOTE: the algo below assumes that there are a minimum of 8 bytes
// per buffer in the chain.
//
{ LONG bytesDone = 0; LONG bytesLeft; LONG saveBytesLeft; PUCHAR pChunk; PUCHAR pSaveChunk;
pBuf=(IPRcvBuf *)pData;
while (pBuf) {
if (IPSEC_BUFFER_LEN(pBuf) == 0) { pBuf = IPSEC_BUFFER_LINKAGE(pBuf); continue; }
IPSecQueryRcvBuf(pBuf, &pSaveChunk, &saveBytesLeft);
bytesLeft = saveBytesLeft - bytesDone; pChunk = pSaveChunk + bytesDone;
IPSEC_DEBUG(LL_A, DBF_ESP, ("ESP: 1.pChunk: %p, bytesLeft: %d, bytesDone: %d", pChunk, bytesLeft, bytesDone)); bytesDone = 0;
while (bytesLeft >= blockLen) {
//
// Decrypt the cipher.
//
status = pConfAlgo->decrypt( (PVOID)&Key, pChunk, pChunk, feedback); if (!NT_SUCCESS(status)) { return status; }
pChunk += blockLen; bytesLeft -= blockLen; }
IPSEC_DEBUG(LL_A, DBF_ESP, ("ESP: 2.pChunk: %p, bytesLeft: %d, bytesDone: %d", pChunk, bytesLeft, bytesDone));
//
// Check here if we need to collate blocks
//
if (IPSEC_BUFFER_LINKAGE(pBuf) != NULL) { PUCHAR pNextChunk; LONG nextSize;
if (IPSEC_BUFFER_LEN(IPSEC_BUFFER_LINKAGE(pBuf)) == 0) { pBuf = IPSEC_BUFFER_LINKAGE(pBuf); }
//
// If some left over from prev. buffer, collate with next
// block
//
if (bytesLeft) { LONG offset = bytesLeft; IPSEC_DEBUG(LL_A, DBF_ESP, ("ESP: 3.pChunk: %p, bytesLeft: %d, bytesDone: %d", pChunk, bytesLeft, bytesDone));
ASSERT(bytesLeft < blockLen);
//
// Copy into a scratch buffer
//
RtlCopyMemory( scratch, pChunk, bytesLeft);
IPSecQueryRcvBuf(IPSEC_BUFFER_LINKAGE(pBuf), &pNextChunk, &nextSize);
if (nextSize >= (blockLen - bytesLeft)) { //
// Copy remaining bytes into scratch
//
RtlCopyMemory( scratch+bytesLeft, pNextChunk, blockLen - bytesLeft);
status = pConfAlgo->decrypt( (PVOID)&Key, scratch, scratch, feedback); if (!NT_SUCCESS(status)) { return status; }
//
// Copy cipher back into the payload
//
RtlCopyMemory( pChunk, scratch, bytesLeft);
RtlCopyMemory( pNextChunk, scratch+bytesLeft, blockLen - bytesLeft);
bytesDone = blockLen - bytesLeft; } else { //
// Ugh! Collect the remaining bytes from the chain and redistribute them
// after the decryption.
//
LONG bytesToCollect = blockLen - bytesLeft; // # of bytes to collect from next few MDLs
IPRcvBuf *pFirstBuf = IPSEC_BUFFER_LINKAGE(pBuf); // to know where to start the distribution post decryption
do { ASSERT(IPSEC_BUFFER_LINKAGE(pBuf)); IPSecQueryRcvBuf(IPSEC_BUFFER_LINKAGE(pBuf), &pNextChunk, &nextSize);
if (nextSize >= (blockLen - offset)) { RtlCopyMemory( scratch+offset, pNextChunk, blockLen - offset); bytesDone = blockLen - offset;
bytesToCollect -= (blockLen - offset); ASSERT(bytesToCollect == 0); } else { IPSEC_DEBUG(LL_A, DBF_ESP, ("special case, offset: %d, bytesLeft: %d, nextSize: %d, pNextChunk: %p", offset, bytesLeft, nextSize, pNextChunk));
RtlCopyMemory( scratch+offset, pNextChunk, nextSize);
bytesToCollect -= nextSize; ASSERT(bytesToCollect);
offset += nextSize; ASSERT(offset < blockLen);
ASSERT(bytesDone == 0); pBuf = IPSEC_BUFFER_LINKAGE(pBuf); } } while (bytesToCollect);
status = pConfAlgo->decrypt( (PVOID)&Key, scratch, scratch, feedback); if (!NT_SUCCESS(status)) { return status; }
//
// Now distribute the bytes back to the MDLs
//
RtlCopyMemory( pChunk, scratch, bytesLeft);
pBuf = CopyToRcvBuf(pFirstBuf, scratch+bytesLeft, blockLen - bytesLeft, &bytesDone); continue;
} } } else { //
// end of chain.
// should never come here with bytes left over since the
// sender should pad to 8 byte boundary.
//
ASSERT(bytesLeft == 0);
IPSEC_DEBUG(LL_A, DBF_ESP, ("ESP: 4.pChunk: %p, saveBytesLeft: %d, bytesDone: %d", pChunk, saveBytesLeft, bytesDone));
IPSEC_DEBUG(LL_A, DBF_ESP, ("ESP: HUGHES: will remove pad later")); break; }
pBuf = (IPRcvBuf *)IPSEC_BUFFER_LINKAGE(pBuf); } }
//
// Restore the first MDL
//
((IPRcvBuf *)pData)->ipr_size += saveLen; ((IPRcvBuf *)pData)->ipr_buffer = savePtr;
return STATUS_SUCCESS; }
NTSTATUS IPSecFindAndSetMdlByOffset(IN IPRcvBuf *pData, IN ULONG Offset, OUT IPRcvBuf **OutMdl, OUT PUCHAR *savePtr, OUT PLONG saveLen) { ULONG TotalStartOffset=0; //Total start offset into data thus far
BYTE *pBuffer; ULONG CurBufLen;
while (pData) { IPSecQueryRcvBuf(pData,&pBuffer,&CurBufLen); if (Offset < CurBufLen+TotalStartOffset) { // Make the OutMdl start from the given offset
*OutMdl=pData; *saveLen=(Offset - TotalStartOffset); *savePtr=pData->ipr_buffer; (*OutMdl)->ipr_size -= (Offset - TotalStartOffset); (*OutMdl)->ipr_buffer += (Offset - TotalStartOffset);
return STATUS_SUCCESS; } TotalStartOffset +=CurBufLen; pData=IPSEC_BUFFER_LINKAGE(pData); } return STATUS_UNSUCCESSFUL; }
|