windows-nt/Source/XPSP1/NT/net/ias/protocol/proxy/radpack.cpp
2020-09-26 16:20:57 +08:00

517 lines
12 KiB
C++

///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000, Microsoft Corp. All rights reserved.
//
// FILE
//
// radpack.cpp
//
// SYNOPSIS
//
// Defines functions for packing and unpacking RADIUS packets.
//
// MODIFICATION HISTORY
//
// 02/01/2000 Original version.
//
///////////////////////////////////////////////////////////////////////////////
#include <proxypch.h>
#include <align.h>
#include <md5.h>
#include <hmac.h>
#include <radpack.h>
// Avoid dependencies on ntrtl.h
extern "C" ULONG __stdcall RtlRandom(PULONG seed);
// Return the number of bytes required to round 'length' to a multiple of 16.
inline ULONG GetPaddingLength16(ULONG length) throw ()
{
return ROUND_UP_COUNT(length, 16) - length;
}
// Returns 'true' if attr is a Microsoft VSA. The attribute must be of type 26.
inline bool isMicrosoftVSA(const RadiusAttribute& attr) throw ()
{
return attr.length >= 6 && !memcmp(attr.value, "\x00\x00\x01\x37", 4);
}
// Pack a 16-bit integer into a buffer.
inline void InsertUInt16(PBYTE p, USHORT value) throw ()
{
p[0] = (BYTE)(value >> 8);
p[1] = (BYTE)value;
}
// Unpack a 16-bit integer into a buffer.
inline USHORT ExtractUInt16(const BYTE* p) throw ()
{
return (USHORT)(p[0] << 8) | (USHORT)p[1];
}
// Returns the number of bytes of padding that should be added when packing the
// attribute.
ULONG
WINAPI
GetPaddingLength(
const RadiusAttribute& attr
) throw ()
{
switch (attr.type)
{
case RADIUS_USER_PASSWORD:
return GetPaddingLength16(attr.length);
case RADIUS_TUNNEL_PASSWORD:
// Subtract 1 byte for the tag and 2 for the salt.
return GetPaddingLength16(attr.length - 3);
case RADIUS_VENDOR_SPECIFIC:
{
if (isMicrosoftVSA(attr))
{
switch (attr.value[4])
{
case MS_CHAP_MPPE_SEND_KEYS:
case MS_CHAP_MPPE_RECV_KEYS:
// Vendor-Id = 4 bytes
// Vendor-Type = 1 byte
// Vendor-Length = 1 byte
// Salt = 2 bytes
return GetPaddingLength16(attr.length - 8);
}
}
break;
}
}
return 0;
}
// Struct describing how to encrypt an attribute.
struct CryptParameters
{
BOOL encrypted;
BOOL salted;
ULONG offset;
};
// Returns information about how to encrypt/decrypt an attribute.
VOID
WINAPI
GetCryptParameters(
const RadiusAttribute& attr,
CryptParameters& parms
) throw ()
{
memset(&parms, 0, sizeof(parms));
switch (attr.type)
{
case RADIUS_USER_PASSWORD:
{
parms.encrypted = TRUE;
break;
}
case RADIUS_TUNNEL_PASSWORD:
{
parms.encrypted = TRUE;
parms.salted = TRUE;
parms.offset = 1; // Skip the tag.
break;
}
case RADIUS_VENDOR_SPECIFIC:
{
if (isMicrosoftVSA(attr))
{
switch (attr.value[4])
{
case MS_CHAP_MPPE_KEYS:
{
parms.encrypted = TRUE;
parms.offset = 6; // Skip the VSA header.
break;
}
case MS_CHAP_MPPE_SEND_KEYS:
case MS_CHAP_MPPE_RECV_KEYS:
{
parms.encrypted = TRUE;
parms.salted = TRUE;
parms.offset = 6; // Skip the VSA header.
break;
}
}
}
break;
}
}
}
ULONG
WINAPI
GetBufferSizeRequired(
const RadiusPacket& packet,
const RadiusAttribute* proxyState,
BOOL alwaysSign
) throw ()
{
// We'll look for the signature as we iterate through the attributes.
BOOL hasSignature = FALSE;
// We always need 20 bytes for the header.
ULONG nbyte = 20;
// Iterate through the attributes.
for (const RadiusAttribute* attr = packet.begin; attr != packet.end; ++attr)
{
nbyte += 2; // Two bytes for type & length.
nbyte += attr->length;
nbyte += GetPaddingLength(*attr);
if (attr->type == RADIUS_SIGNATURE)
{
hasSignature = TRUE;
}
else if (attr->type == RADIUS_EAP_MESSAGE)
{
alwaysSign = TRUE;
}
}
// Reserve space for the Proxy-State attribute.
if (proxyState) { nbyte += proxyState->length + 2; }
// Reserve space for the signature if necessary.
if (alwaysSign && !hasSignature && packet.code == RADIUS_ACCESS_REQUEST)
{
nbyte += 18;
}
return nbyte <= 4096 ? nbyte : 0;
}
VOID
WINAPI
PackBuffer(
const BYTE* secret,
ULONG secretLength,
RadiusPacket& packet,
const RadiusAttribute* proxyState,
BOOL alwaysSign,
BYTE* buffer
) throw ()
{
// Set up a cursor into the buffer.
BYTE* dst = buffer;
// Pack the header.
*dst++ = packet.code;
*dst++ = packet.identifier;
InsertUInt16(dst, packet.length);
dst += 2;
// Pack the authenticator.
if (packet.code == RADIUS_ACCESS_REQUEST)
{
if (packet.authenticator != 0)
{
memcpy(dst, packet.authenticator, 16);
}
else
{
static ULONG seed;
if (seed == 0)
{
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
seed = ft.dwLowDateTime | ft.dwHighDateTime;
}
ULONG auth[4];
auth[0] = RtlRandom(&seed);
auth[1] = RtlRandom(&seed);
auth[2] = RtlRandom(&seed);
auth[3] = RtlRandom(&seed);
memcpy(dst, auth, 16);
}
}
else
{
memset(dst, 0, 16);
}
dst += 16;
// We'll look for the signature as we iterate through the attributes.
BYTE* signature = NULL;
for (const RadiusAttribute* attr = packet.begin; attr != packet.end; ++attr)
{
// Pack the type.
*dst++ = attr->type;
// Pack the length.
ULONG paddingLength = GetPaddingLength(*attr);
ULONG valueLength = attr->length + paddingLength;
*dst++ = (BYTE)(2 + valueLength);
if (attr->type == RADIUS_SIGNATURE)
{
signature = dst;
}
else if (attr->type == RADIUS_EAP_MESSAGE)
{
alwaysSign = TRUE;
}
// Pack the value ...
memcpy(dst, attr->value, attr->length);
// ... and add the padding.
memset(dst + attr->length, 0, paddingLength);
// Do we need to encrypt this attribute ?
CryptParameters parms;
GetCryptParameters(*attr, parms);
if (parms.encrypted)
{
// Yes.
IASRadiusCrypt(
TRUE,
parms.salted,
secret,
secretLength,
buffer + 4,
dst + parms.offset,
valueLength - parms.offset
);
}
dst += valueLength;
}
// Add the Proxy-State
if (proxyState)
{
*dst++ = proxyState->type;
*dst++ = proxyState->length + 2;
memcpy(dst, proxyState->value, proxyState->length);
dst += proxyState->length;
}
if (packet.code == RADIUS_ACCESS_REQUEST)
{
if (alwaysSign)
{
// If we didn't find a signature, ...
if (!signature)
{
// ... then add one at the end of the packet.
*dst++ = RADIUS_SIGNATURE;
*dst++ = 18;
signature = dst;
}
// Compute the signature.
memset(signature, 0, 16);
HMACMD5_CTX context;
HMACMD5Init(&context, (BYTE*)secret, secretLength);
HMACMD5Update(&context, buffer, packet.length);
HMACMD5Final(&context, signature);
}
}
else
{
// For everything but Access-Request, we compute the authenticator.
MD5_CTX context;
MD5Init(&context);
MD5Update(&context, buffer, packet.length);
MD5Update(&context, secret, secretLength);
MD5Final(&context);
memcpy(buffer + 4, context.digest, 16);
}
}
RadiusAttribute*
WINAPI
FindAttribute(
const RadiusPacket& packet,
BYTE type
)
{
for (const RadiusAttribute* i = packet.begin; i != packet.end; ++i)
{
if (i->type == type) { return const_cast<RadiusAttribute*>(i); }
}
return NULL;
}
ULONG
WINAPI
GetAttributeCount(
const BYTE* buffer,
ULONG bufferLength
) throw ()
{
if (bufferLength >= 20 && ExtractUInt16(buffer + 2) == bufferLength)
{
ULONG count = 0;
const BYTE* end = buffer + bufferLength;
for (const BYTE* p = buffer + 20; p < end; p += p[1])
{
++count;
}
if (p == end) { return count; }
}
return MALFORMED_PACKET;
}
VOID
WINAPI
UnpackBuffer(
BYTE* buffer,
ULONG bufferLength,
RadiusPacket& packet
) throw ()
{
// Set up a cursor into the buffer.
BYTE* src = buffer;
packet.code = *src++;
packet.identifier = *src++;
packet.length = ExtractUInt16(src);
src +=2;
packet.authenticator = src;
src += 16;
RadiusAttribute* dst = packet.begin;
const BYTE* end = buffer + bufferLength;
while (src < end)
{
dst->type = *src++;
dst->length = *src++ - 2;
dst->value = src;
src += dst->length;
++dst;
}
}
BYTE*
WINAPI
FindRawAttribute(
BYTE type,
BYTE* buffer,
ULONG bufferLength
)
{
BYTE* end = buffer + bufferLength;
for (BYTE* p = buffer + 20; p < buffer + bufferLength; p += p[1])
{
if (*p == type) { return p; }
}
return NULL;
}
AuthResult
WINAPI
AuthenticateAndDecrypt(
const BYTE* requestAuthenticator,
const BYTE* secret,
ULONG secretLength,
BYTE* buffer,
ULONG bufferLength,
RadiusPacket& packet
) throw ()
{
AuthResult result = AUTH_UNKNOWN;
if (!requestAuthenticator) { requestAuthenticator = buffer + 4; }
// Check the authenticator for everything but Access-Request.
if (buffer[0] != RADIUS_ACCESS_REQUEST)
{
MD5_CTX context;
MD5Init(&context);
MD5Update(&context, buffer, 4);
MD5Update(&context, requestAuthenticator, 16);
MD5Update(&context, buffer + 20, bufferLength - 20);
MD5Update(&context, secret, secretLength);
MD5Final(&context);
if (memcmp(context.digest, buffer + 4, 16))
{
return AUTH_BAD_AUTHENTICATOR;
}
result = AUTH_AUTHENTIC;
}
// Look for a signature.
BYTE* signature = FindRawAttribute(
RADIUS_SIGNATURE,
buffer,
bufferLength
);
if (signature)
{
if (signature[1] != 18) { return AUTH_BAD_SIGNATURE; }
signature += 2;
BYTE sent[16];
memcpy(sent, signature, 16);
memset(signature, 0, 16);
HMACMD5_CTX context;
HMACMD5Init(&context, (BYTE*)secret, secretLength);
HMACMD5Update(&context, buffer, 4);
HMACMD5Update(&context, (BYTE*)requestAuthenticator, 16);
HMACMD5Update(&context, buffer + 20, bufferLength - 20);
HMACMD5Final(&context, signature);
if (memcmp(signature, sent, 16)) { return AUTH_BAD_SIGNATURE; }
result = AUTH_AUTHENTIC;
}
else if (FindRawAttribute(RADIUS_EAP_MESSAGE, buffer, bufferLength))
{
return AUTH_MISSING_SIGNATURE;
}
// The buffer is authentic, so decrypt the attributes.
for (const RadiusAttribute* attr = packet.begin; attr != packet.end; ++attr)
{
// Do we need to decrypt this attribute ?
CryptParameters parms;
GetCryptParameters(*attr, parms);
if (parms.encrypted)
{
// Yes.
IASRadiusCrypt(
FALSE,
parms.salted,
secret,
secretLength,
requestAuthenticator,
attr->value + parms.offset,
attr->length - parms.offset
);
}
}
return result;
}