windows-nt/Source/XPSP1/NT/net/tcpip/driver/ipsec/sys/esp.c
2020-09-26 16:20:57 +08:00

733 lines
21 KiB
C

/*++
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"
#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;
}
VOID
esp_nullencrypt (
PVOID pState,
PUCHAR pOut,
PUCHAR pIn,
PUCHAR pIV
)
{
RtlCopyMemory(pOut, pIn, DES_BLOCKLEN);
return;
}
VOID
esp_nulldecrypt (
PVOID pState,
PUCHAR pOut,
PUCHAR pIn,
PUCHAR pIV
)
{
return;
}
VOID
esp_desinit (
IN PVOID pState,
IN PUCHAR pKey
)
{
DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
IPSEC_DES_KEY(Table, pKey);
}
VOID
esp_desencrypt (
PVOID pState,
PUCHAR pOut,
PUCHAR pIn,
PUCHAR pIV
)
{
DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
IPSEC_CBC(IPSEC_DES_ALGO,
pOut,
pIn, // pChunk,
Table,
ENCRYPT,
pIV);
}
VOID
esp_desdecrypt (
PVOID pState,
PUCHAR pOut,
PUCHAR pIn,
PUCHAR pIV
)
{
DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
IPSEC_CBC(IPSEC_DES_ALGO,
pOut,
pIn, // pChunk,
Table,
DECRYPT,
pIV);
}
VOID
esp_3_desinit (
IN PVOID pState,
IN PUCHAR pKey
)
{
DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
IPSEC_3DES_KEY(Table, pKey);
}
VOID
esp_3_desencrypt (
PVOID pState,
PUCHAR pOut,
PUCHAR pIn,
PUCHAR pIV
)
{
DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
IPSEC_CBC(IPSEC_3DES_ALGO,
pOut,
pIn, // pChunk,
Table,
ENCRYPT,
pIV);
}
VOID
esp_3_desdecrypt (
PVOID pState,
PUCHAR pOut,
PUCHAR pIn,
PUCHAR pIV
)
{
DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
IPSEC_CBC(IPSEC_3DES_ALGO,
pOut,
pIn, // pChunk,
Table,
DECRYPT,
pIV);
}
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;
IPSEC_DEBUG(ESP, ("Entering IPSecEncryptBuffer: pData: %lx\n", 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(HUGHES, ("pConfAlgo: %lx, blockLen: %lx IV: %lx-%lx\n", 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: %lx, len: %d, pPadBuf: %lx, pPadLen: %d\n", pData, len, pPadBuf, pPadLen);
DbgBreakPoint();
}
#endif
IPSecAllocateBuffer(&status, &pEncryptMdl, &pDest, len, IPSEC_TAG_ESP);
if (!NT_SUCCESS(status)) {
NTSTATUS ntstatus;
//ASSERT(FALSE);
IPSEC_DEBUG(ESP, ("Failed to alloc. encrypt MDL\n"));
return status;
}
IPSEC_DEBUG(ESP, ("Alloc. MDL: %lx, pDest: %lx, len: %d, pData: %lx\n", 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(ESP, ("ESP: pChunk: %lx, bytesLeft: %d, bytesDone: %d\n", pChunk, bytesLeft, bytesDone));
bytesDone = 0;
while (bytesLeft >= blockLen) {
//
// Create the cipher.
//
pConfAlgo->encrypt( (PVOID)&Key,
pDest,
pChunk,
feedback);
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(ESP, ("ESP: pChunk: %lx, bytesLeft: %d\n", 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(ESP, ("special case, offset: %d, bytesLeft: %d, nextSize: %d, pNextChunk: %lx\n",
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);
pConfAlgo->encrypt( (PVOID)&Key,
pDest,
scratch,
feedback);
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(HUGHES, ("IV: %lx-%lx\n", *(PULONG)&feedback[0], *(PULONG)&feedback[4]));
}
#if DBG
{
ULONG totalLen;
IPSEC_GET_TOTAL_LEN(pEncryptMdl, &totalLen);
ASSERT((totalLen % 8) == 0);
IPSEC_DEBUG(ESP, ("total len: %lx\n", totalLen));
}
#endif
IPSEC_DEBUG(ESP, ("Exiting IPSecEncryptBuffer\n"));
*ppNewMdl = pEncryptMdl;
return STATUS_SUCCESS;
}
NTSTATUS
IPSecDecryptBuffer(
IN PVOID pData,
IN PSA_TABLE_ENTRY pSA,
OUT PUCHAR pPadLen,
OUT PUCHAR pPayloadType,
IN ULONG Index
)
{
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 espLen = sizeof(ESP) + pSA->sa_ivlen + pSA->sa_ReplayLen;
LONG blockLen;
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) {
RtlCopyMemory( feedback,
((PUCHAR)(pEsp + 1) + pSA->sa_ReplayLen),
pSA->sa_ivlen);
IPSEC_DEBUG(ESP, ("IV: %lx-%lx\n", *(PULONG)&feedback[0], *(PULONG)&feedback[4]));
} else {
RtlCopyMemory( feedback,
pSA->sa_iv[Index],
DES_BLOCKLEN);
}
//
// Bump the current pointer to after the ESP header
//
((IPRcvBuf *)pData)->ipr_size -= espLen;
savePtr = ((IPRcvBuf *)pData)->ipr_buffer;
((IPRcvBuf *)pData)->ipr_buffer = savePtr + espLen;
//
// 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.
//
{
IPRcvBuf *pBuf = (IPRcvBuf *)pData;
LONG bytesDone = 0;
LONG bytesLeft;
LONG saveBytesLeft;
PUCHAR pChunk;
PUCHAR pSaveChunk;
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(ESP, ("ESP: 1.pChunk: %lx, bytesLeft: %d, bytesDone: %d\n", pChunk, bytesLeft, bytesDone));
bytesDone = 0;
while (bytesLeft >= blockLen) {
//
// Decrypt the cipher.
//
pConfAlgo->decrypt( (PVOID)&Key,
pChunk,
pChunk,
feedback);
pChunk += blockLen;
bytesLeft -= blockLen;
}
IPSEC_DEBUG(ESP, ("ESP: 2.pChunk: %lx, bytesLeft: %d, bytesDone: %d\n", 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(ESP, ("ESP: 3.pChunk: %lx, bytesLeft: %d, bytesDone: %d\n", 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);
pConfAlgo->decrypt( (PVOID)&Key,
scratch,
scratch,
feedback);
//
// 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(ESP, ("special case, offset: %d, bytesLeft: %d, nextSize: %d, pNextChunk: %lx\n",
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);
pConfAlgo->decrypt( (PVOID)&Key,
scratch,
scratch,
feedback);
//
// 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(ESP, ("ESP: 4.pChunk: %lx, saveBytesLeft: %d, bytesDone: %d\n", pChunk, saveBytesLeft, bytesDone));
IPSEC_DEBUG(ESP, ("ESP: HUGHES: will remove pad later\n"));
break;
}
pBuf = (IPRcvBuf *)IPSEC_BUFFER_LINKAGE(pBuf);
}
}
//
// Restore the first MDL
//
((IPRcvBuf *)pData)->ipr_size += espLen;
((IPRcvBuf *)pData)->ipr_buffer = savePtr;
return STATUS_SUCCESS;
}