Windows2003-3790/base/boot/bootssp/ntlmsspv2.c
2020-09-30 16:53:55 +02:00

2495 lines
60 KiB
C

/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
ntlmsspv2.c
Abstract:
NTLM v2 specific modules
Author:
Larry Zhu (LZhu) 29-August-2001
Environment: User Mode
Revision History:
--*/
#include "ntlmsspv2.h"
#include <ntlmsspi.h>
#include <ntlmssp.h>
SECURITY_STATUS
SspNtStatusToSecStatus(
IN NTSTATUS NtStatus,
IN SECURITY_STATUS DefaultStatus
)
/*++
Routine Description:
Convert an NtStatus code to the corresponding Security status code. For
particular errors that are required to be returned as is (for setup code)
don't map the errors.
Arguments:
NtStatus - NT status to convert
DefaultStatus - default security status if NtStatus is not mapped
Return Value:
Returns security status code.
--*/
{
SECURITY_STATUS SecStatus;
//
// Check for security status and let them through
//
if (HRESULT_FACILITY(NtStatus) == FACILITY_SECURITY)
{
return (NtStatus);
}
switch (NtStatus)
{
case STATUS_SUCCESS:
SecStatus = SEC_E_OK;
break;
case STATUS_NO_MEMORY:
case STATUS_INSUFFICIENT_RESOURCES:
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
break;
case STATUS_NETLOGON_NOT_STARTED:
case STATUS_DOMAIN_CONTROLLER_NOT_FOUND:
case STATUS_NO_LOGON_SERVERS:
case STATUS_NO_SUCH_DOMAIN:
case STATUS_BAD_NETWORK_PATH:
case STATUS_TRUST_FAILURE:
case STATUS_TRUSTED_RELATIONSHIP_FAILURE:
case STATUS_NETWORK_UNREACHABLE:
SecStatus = SEC_E_NO_AUTHENTICATING_AUTHORITY;
break;
case STATUS_NO_SUCH_LOGON_SESSION:
SecStatus = SEC_E_UNKNOWN_CREDENTIALS;
break;
case STATUS_INVALID_PARAMETER:
case STATUS_PARTIAL_COPY:
SecStatus = SEC_E_INVALID_TOKEN;
break;
case STATUS_PRIVILEGE_NOT_HELD:
SecStatus = SEC_E_NOT_OWNER;
break;
case STATUS_INVALID_HANDLE:
SecStatus = SEC_E_INVALID_HANDLE;
break;
case STATUS_BUFFER_TOO_SMALL:
SecStatus = SEC_E_BUFFER_TOO_SMALL;
break;
case STATUS_NOT_SUPPORTED:
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
break;
case STATUS_OBJECT_NAME_NOT_FOUND:
case STATUS_NO_TRUST_SAM_ACCOUNT:
SecStatus = SEC_E_TARGET_UNKNOWN;
break;
case STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT:
case STATUS_NOLOGON_SERVER_TRUST_ACCOUNT:
case STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT:
case STATUS_TRUSTED_DOMAIN_FAILURE:
SecStatus = NtStatus;
break;
case STATUS_LOGON_FAILURE:
case STATUS_NO_SUCH_USER:
case STATUS_ACCOUNT_DISABLED:
case STATUS_ACCOUNT_RESTRICTION:
case STATUS_ACCOUNT_LOCKED_OUT:
case STATUS_WRONG_PASSWORD:
case STATUS_ACCOUNT_EXPIRED:
case STATUS_PASSWORD_EXPIRED:
case STATUS_PASSWORD_MUST_CHANGE:
case STATUS_LOGON_TYPE_NOT_GRANTED:
SecStatus = SEC_E_LOGON_DENIED;
break;
case STATUS_NAME_TOO_LONG:
case STATUS_ILL_FORMED_PASSWORD:
SecStatus = SEC_E_INVALID_TOKEN;
break;
case STATUS_TIME_DIFFERENCE_AT_DC:
SecStatus = SEC_E_TIME_SKEW;
break;
case STATUS_SHUTDOWN_IN_PROGRESS:
SecStatus = SEC_E_SHUTDOWN_IN_PROGRESS;
break;
case STATUS_INTERNAL_ERROR:
SecStatus = SEC_E_INTERNAL_ERROR;
ASSERT(FALSE);
break;
default:
SecStatus = DefaultStatus;
break;
}
return (SecStatus);
}
NTSTATUS
SspInitUnicodeStringNoAlloc(
IN PCSTR pszSource,
IN OUT UNICODE_STRING* pDestination
)
/*++
Routine Description:
Initialize unicode string. This routine does not allocate memory.
Arguments:
pszSource - source string
pDestination - unicode string
Return Value:
NTSTATUS
--*/
{
STRING OemString;
RtlInitString(&OemString, pszSource);
return SspOemStringToUnicodeString(pDestination, &OemString, FALSE);
}
VOID
SspFreeStringEx(
IN OUT STRING* pString
)
/*++
Routine Description:
Free string.
Arguments:
pString - string to free
Return Value:
none
--*/
{
if (pString->MaximumLength && pString->Buffer)
{
_fmemset(pString->Buffer, 0, pString->Length);
SspFree(pString->Buffer);
pString->MaximumLength = pString->Length = 0;
pString->Buffer = NULL;
}
}
VOID
SspFreeUnicodeString(
IN OUT UNICODE_STRING* pUnicodeString
)
/*++
Routine Description:
Free unicode string.
Arguments:
pUnicodeString - unicode string to free
Return Value:
none
--*/
{
SspFreeStringEx((STRING *) pUnicodeString);
}
MSV1_0_AV_PAIR*
SspAvlInit(
IN VOID* pAvList
)
/*++
Routine Description:
Initialize AV pair list
Arguments:
pAvList - first pair of AV pair list
Return Value:
AV list
--*/
{
MSV1_0_AV_PAIR* pAvPair;
pAvPair = (MSV1_0_AV_PAIR*) pAvList;
if (!pAvPair)
{
return NULL;
}
pAvPair->AvId = MsvAvEOL;
pAvPair->AvLen = 0;
return pAvPair;
}
MSV1_0_AV_PAIR*
SspAvlAdd(
IN MSV1_0_AV_PAIR* pAvList,
IN MSV1_0_AVID AvId,
IN UNICODE_STRING* pString,
IN ULONG cAvList
)
/*++
Routine Description:
add an AV pair to a list, ssumes buffer is long enough!
Arguments:
pAvList - first pair of AV pair list
AvId - AV pair to add
pString - value of pair
cAvList - max size of AV list
Return Value:
av pair added, NULL on failure
--*/
{
MSV1_0_AV_PAIR* pCurPair;
//
// find the EOL
//
pCurPair = SspAvlGet(pAvList, MsvAvEOL, cAvList);
if (pCurPair == NULL)
{
return NULL;
}
//
// check for enough space in the av list buffer, then append the new AvPair
// (assume the buffer is long enough!)
//
if ( (((UCHAR*) pCurPair) - ((UCHAR*)pAvList)) + sizeof(MSV1_0_AV_PAIR) * 2 + pString->Length > cAvList)
{
return NULL;
}
pCurPair->AvId = (USHORT) AvId;
pCurPair->AvLen = (USHORT) pString->Length;
_fmemcpy(pCurPair + 1, pString->Buffer, pCurPair->AvLen);
//
// top it off with a new EOL
//
pCurPair = (MSV1_0_AV_PAIR*) ((UCHAR*) pCurPair + sizeof(MSV1_0_AV_PAIR) + pCurPair->AvLen);
pCurPair->AvId = MsvAvEOL;
pCurPair->AvLen = 0;
return pCurPair;
}
MSV1_0_AV_PAIR*
SspAvlGet(
IN MSV1_0_AV_PAIR* pAvList,
IN MSV1_0_AVID AvId,
IN ULONG cAvList
)
/*++
Routine Description:
Find a particular AV pair by ID
Arguments:
pAvList - first pair of AV pair list
AvId - AV pair to find
cAvList - size of AV list
Return Value:
av pair found, NULL if not found
--*/
{
MSV1_0_AV_PAIR* pAvPair;
pAvPair = pAvList;
while (TRUE)
{
if (pAvPair->AvId == AvId)
{
return pAvPair;
}
if (pAvPair->AvId == MsvAvEOL)
{
return NULL;
}
cAvList -= (pAvPair->AvLen + sizeof(MSV1_0_AV_PAIR));
if (cAvList <= 0)
{
return NULL;
}
pAvPair = (MSV1_0_AV_PAIR*) ((UCHAR*) pAvPair + pAvPair->AvLen + sizeof(MSV1_0_AV_PAIR));
}
}
ULONG
SspAvlLen(
IN MSV1_0_AV_PAIR* pAvList,
IN ULONG cAvList
)
/*++
Routine Description:
Find length of a AV list
Arguments:
pAvList - first pair of AV pair list
cAvList - target info output
Return Value:
Length of av list
--*/
{
MSV1_0_AV_PAIR* pCurPair;
//
// find the EOL
//
pCurPair = SspAvlGet(pAvList, MsvAvEOL, cAvList);
if (pCurPair == NULL)
{
return 0;
}
//
// compute length (not forgetting the EOL pair)
//
return (ULONG)(((UCHAR*) pCurPair - (UCHAR*) pAvList) + sizeof(MSV1_0_AV_PAIR));
}
NTSTATUS
SspCreateTargetInfo(
IN UNICODE_STRING* pTargetName,
OUT STRING* pTargetInfo
)
/*++
Routine Description:
Create a target info from target name
Arguments:
pTargetName - name of the target, this can be a domain name followed by a
server name
pTargetInfo - target info output
Return Value:
NTSTATUS
--*/
{
UNICODE_STRING DomainName = {0};
UNICODE_STRING ServerName = {0};
ULONG i = 0;
MSV1_0_AV_PAIR* pAV;
//
// check length of name to make sure it fits in my buffer
//
if (pTargetName->Length > (DNS_MAX_NAME_LENGTH + CNLEN + 2) * sizeof(WCHAR))
{
return STATUS_INVALID_PARAMETER;
}
//
// init AV list in temp buffer
//
pAV = SspAvlInit(pTargetInfo->Buffer);
if (!pAV)
{
return STATUS_INVALID_PARAMETER;
}
//
// see if there's a NULL in the middle of the server name that indicates
// that it's really a domain name followed by a server name
//
DomainName = *pTargetName;
for (i = 0; i < (DomainName.Length / sizeof(WCHAR)); i++)
{
if (DomainName.Buffer[i] == L'\0')
{
//
// take length of domain name without the NULL
//
DomainName.Length = (USHORT) i * sizeof(WCHAR);
//
// adjust server name and length to point after the domain name
//
ServerName.Length = (USHORT) (pTargetName->Length - (i + 1) * sizeof(WCHAR));
ServerName.Buffer = pTargetName->Buffer + (i + 1);
break;
}
}
//
// strip off possible trailing null after the server name
//
for (i = 0; i < (ServerName.Length / sizeof(WCHAR)); i++)
{
if (ServerName.Buffer[i] == L'\0')
{
ServerName.Length = (USHORT) i * sizeof(WCHAR);
break;
}
}
//
// put both names in the AV list (if both exist)
//
if (!SspAvlAdd(pAV, MsvAvNbDomainName, &DomainName, pTargetInfo->MaximumLength))
{
return STATUS_INVALID_PARAMETER;
}
if ((ServerName.Length > 0) && !SspAvlAdd(pAV, MsvAvNbComputerName, &ServerName, pTargetInfo->MaximumLength))
{
return STATUS_INVALID_PARAMETER;
}
//
// make the request point at AV list instead of names.
//
pTargetInfo->Length = (USHORT) SspAvlLen(pAV, pTargetInfo->MaximumLength);
pTargetInfo->Buffer = (CHAR*) pAV;
return STATUS_SUCCESS;
}
NTSTATUS
SsprHandleNtlmv2ChallengeMessage(
IN SSP_CREDENTIAL* pCredential,
IN ULONG cbChallengeMessage,
IN CHALLENGE_MESSAGE* pChallengeMessage,
IN OUT ULONG* pNegotiateFlags,
IN OUT ULONG* pcbAuthenticateMessage,
OUT AUTHENTICATE_MESSAGE* pAuthenticateMessage,
OUT USER_SESSION_KEY* pContextSessionKey
)
/*++
Routine Description:
Handle challenge message and generate authentication message and context
session key
Arguments:
pCredential - client credentials
cbChallengeMessage - challenge message size
pChallengeMessage - challenge message
pNegotiateFlags - negotiate flags
pcbAuthenticateMessage - size of authentication message
pAuthenticateMessage - authentication message
pContextSessionKey - context session key
Return Value:
NTSTATUS
--*/
{
NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
ULONG cbAuthenticateMessage = 0;
UCHAR* pWhere = NULL;
BOOLEAN DoUnicode = TRUE;
//
// use a scratch buffer to avoid memory allocation in bootssp
//
CHAR ScrtachBuff[sizeof(MSV1_0_NTLMV2_RESPONSE) + sizeof(DWORD) + NTLMV2_RESPONSE_LENGTH] = {0};
STRING LmChallengeResponse = {0};
STRING NtChallengeResponse = {0};
STRING DatagramSessionKey = {0};
USHORT Ntlmv2ResponseSize = 0;
MSV1_0_NTLMV2_RESPONSE* pNtlmv2Response = NULL;
LM_SESSION_KEY LanmanSessionKey = {0};
STRING TargetInfo = {0};
UCHAR DatagramKey[sizeof(USER_SESSION_KEY)] ={0};
USER_SESSION_KEY NtUserSessionKey = {0};
//
// use pre-allocated buffers to avoid memory allocation in bootssp
//
// to be consistent with LSA/SSPI, allow DNS names in szDomainName and
// szWorkstation
//
CHAR szUserName[(UNLEN + 4) * sizeof(WCHAR)] = {0};
CHAR szDomainName[(DNSLEN + 4) * sizeof(WCHAR)] = {0};
CHAR szWorkstation[(DNSLEN + 4) * sizeof(WCHAR)] = {0};
STRING UserName = {0, sizeof(szUserName), szUserName};
STRING DomainName = {0, sizeof(szDomainName), szDomainName};
STRING Workstation = {0, sizeof(szWorkstation), szWorkstation};
//
// responses to return to the caller
//
LM_RESPONSE LmResponse = {0};
NT_RESPONSE NtResponse = {0};
USER_SESSION_KEY ContextSessionKey = {0};
ULONG NegotiateFlags = 0;
if (!pCredential || !pChallengeMessage || !pNegotiateFlags || !pcbAuthenticateMessage || !pContextSessionKey)
{
return STATUS_INVALID_PARAMETER;
}
SspPrint((SSP_NTLMV2, "Entering SsprHandleNtlmv2ChallengeMessage: NegotiateFlags %#x\n", *pNegotiateFlags));
NegotiateFlags = *pNegotiateFlags;
NtStatus = SspInitUnicodeStringNoAlloc(pCredential->Username, (UNICODE_STRING *) &UserName);
if (NT_SUCCESS(NtStatus))
{
NtStatus = SspInitUnicodeStringNoAlloc(pCredential->Domain, (UNICODE_STRING *) &DomainName);
}
if (NT_SUCCESS(NtStatus))
{
NtStatus = SspInitUnicodeStringNoAlloc(pCredential->Workstation, (UNICODE_STRING *) &Workstation);
}
if (NT_SUCCESS(NtStatus))
{
NtStatus = !_fstrcmp(NTLMSSP_SIGNATURE, (char *) pChallengeMessage->Signature) && pChallengeMessage->MessageType == NtLmChallenge ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER;
}
if (NT_SUCCESS(NtStatus))
{
if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_UNICODE)
{
NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;
NegotiateFlags &= ~NTLMSSP_NEGOTIATE_OEM;
DoUnicode = TRUE;
}
else if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_OEM)
{
NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM;
NegotiateFlags &= ~NTLMSSP_NEGOTIATE_UNICODE;
DoUnicode = FALSE;
}
else
{
NtStatus = STATUS_INVALID_PARAMETER;
}
}
if (NT_SUCCESS(NtStatus))
{
if (!DoUnicode)
{
SspUpcaseUnicodeString((UNICODE_STRING *) &UserName);
SspUpcaseUnicodeString((UNICODE_STRING *) &DomainName);
SspUpcaseUnicodeString((UNICODE_STRING *) &Workstation);
}
if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2)
{
NegotiateFlags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
}
else // (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2))
{
NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_NTLM2);
}
if (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM))
{
NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_NTLM);
}
if (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH))
{
NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_KEY_EXCH);
}
if (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY))
{
NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_LM_KEY);
}
if ((NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) &&
(NegotiateFlags & (NTLMSSP_NEGOTIATE_SIGN |NTLMSSP_NEGOTIATE_SEAL)))
{
NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
}
if (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_56))
{
NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_56);
}
if ((pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_128) == 0)
{
NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_128);
}
if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)
{
NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
}
else
{
NegotiateFlags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
}
if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO)
{
NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO;
SspPrint((SSP_NTLMV2, "SsprHandleNtlmv2ChallengeMessage: NTLMSSP_NEGOTIATE_TARGET_INFO negotiated\n"));
NtStatus = SspConvertRelativeToAbsolute(
pChallengeMessage,
cbChallengeMessage,
&pChallengeMessage->TargetInfo,
DoUnicode,
TRUE, // NULL target info OK
&TargetInfo
);
}
else
{
UNICODE_STRING TargetName = {0};
UCHAR TargetInfoBuffer[3 * sizeof(MSV1_0_AV_PAIR) + (DNS_MAX_NAME_LENGTH + CNLEN + 2) * sizeof(WCHAR)];
SspPrint((SSP_NTLMV2, "SsprHandleNtlmv2ChallengeMessage: NTLMSSP_NEGOTIATE_TARGET_INFO NOT negotiated\n"));
NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_TARGET_INFO);
TargetInfo.Length = 0;
TargetInfo.MaximumLength = sizeof(TargetInfoBuffer);
TargetInfo.Buffer = TargetInfoBuffer;
NtStatus = SspConvertRelativeToAbsolute(
pChallengeMessage,
cbChallengeMessage,
&pChallengeMessage->TargetName,
DoUnicode,
TRUE, // NULL TargetName ok
(STRING*) &TargetName
);
if (NT_SUCCESS(NtStatus))
{
NtStatus = SspCreateTargetInfo(&TargetName, &TargetInfo);
}
}
}
if (NT_SUCCESS(NtStatus))
{
Ntlmv2ResponseSize = sizeof(MSV1_0_NTLMV2_RESPONSE) + TargetInfo.Length;
NtStatus = Ntlmv2ResponseSize <= sizeof(ScrtachBuff) ? STATUS_SUCCESS : STATUS_INSUFFICIENT_RESOURCES;
}
if (NT_SUCCESS(NtStatus))
{
// C_ASSERT(sizeof(MSV1_0_NTLMV2_RESPONSE) == sizeof(LM_RESPONSE));
pNtlmv2Response = (MSV1_0_NTLMV2_RESPONSE *) ScrtachBuff;
NtStatus = SspLm20GetNtlmv2ChallengeResponse(
pCredential->NtPassword,
(UNICODE_STRING *) &UserName,
(UNICODE_STRING *) &DomainName,
&TargetInfo,
pChallengeMessage->Challenge,
pNtlmv2Response,
(MSV1_0_LMV2_RESPONSE *) &LmResponse,
&NtUserSessionKey,
&LanmanSessionKey
);
}
if (NT_SUCCESS(NtStatus))
{
NtChallengeResponse.Buffer = (CHAR *) pNtlmv2Response;
NtChallengeResponse.Length = Ntlmv2ResponseSize;
LmChallengeResponse.Buffer = (CHAR *) &LmResponse;
LmChallengeResponse.Length = sizeof(LmResponse);
//
// prepare to send encrypted randomly generated session key
//
DatagramSessionKey.Buffer = (CHAR *) DatagramKey;
DatagramSessionKey.Length = DatagramSessionKey.MaximumLength = 0;
//
// Generate the session key, or encrypt the previosly generated random
// one, from various bits of info. Fill in session key if needed.
//
NtStatus = SspMakeSessionKeys(
NegotiateFlags,
&LmChallengeResponse,
&NtUserSessionKey,
&LanmanSessionKey,
&DatagramSessionKey,
&ContextSessionKey
);
}
if (NT_SUCCESS(NtStatus) && !DoUnicode)
{
NtStatus = SspUpcaseUnicodeStringToOemString((UNICODE_STRING *) &DomainName, &DomainName);
if (NT_SUCCESS(NtStatus))
{
NtStatus = SspUpcaseUnicodeStringToOemString((UNICODE_STRING *) &UserName, &UserName);
}
if (NT_SUCCESS(NtStatus))
{
NtStatus = SspUpcaseUnicodeStringToOemString((UNICODE_STRING *) &Workstation, &Workstation);
}
}
if (NT_SUCCESS(NtStatus))
{
cbAuthenticateMessage =
sizeof(*pAuthenticateMessage) +
LmChallengeResponse.Length +
NtChallengeResponse.Length +
DomainName.Length +
UserName.Length +
Workstation.Length +
DatagramSessionKey.Length;
NtStatus = cbAuthenticateMessage <= *pcbAuthenticateMessage ? STATUS_SUCCESS : STATUS_BUFFER_TOO_SMALL;
if (NtStatus == STATUS_BUFFER_TOO_SMALL)
{
*pcbAuthenticateMessage = cbAuthenticateMessage;
}
}
if (NT_SUCCESS(NtStatus))
{
_fmemset(pAuthenticateMessage, 0, cbAuthenticateMessage);
//
// Build the authenticate message
//
_fstrcpy((char *) pAuthenticateMessage->Signature, NTLMSSP_SIGNATURE);
pAuthenticateMessage->MessageType = NtLmAuthenticate;
pWhere = (UCHAR *) (pAuthenticateMessage + 1);
//
// Copy the strings needing 2 byte alignment.
//
SspCopyStringAsString32(
pAuthenticateMessage,
&DomainName,
&pWhere,
&pAuthenticateMessage->DomainName
);
SspCopyStringAsString32(
pAuthenticateMessage,
&UserName,
&pWhere,
&pAuthenticateMessage->UserName
);
SspCopyStringAsString32(
pAuthenticateMessage,
&Workstation,
&pWhere,
&pAuthenticateMessage->Workstation
);
//
// Copy the strings not needing special alignment.
//
SspCopyStringAsString32(
pAuthenticateMessage,
(STRING *) &LmChallengeResponse,
&pWhere,
&pAuthenticateMessage->LmChallengeResponse
);
SspCopyStringAsString32(
pAuthenticateMessage,
(STRING *) &NtChallengeResponse,
&pWhere,
&pAuthenticateMessage->NtChallengeResponse
);
SspCopyStringAsString32(
pAuthenticateMessage,
(STRING *) &DatagramSessionKey,
&pWhere,
&pAuthenticateMessage->SessionKey
);
pAuthenticateMessage->NegotiateFlags = NegotiateFlags;
*pcbAuthenticateMessage = cbAuthenticateMessage;
*pContextSessionKey = ContextSessionKey;
*pNegotiateFlags = NegotiateFlags;
}
SspPrint((SSP_NTLMV2, "Leaving SsprHandleNtlmv2ChallengeMessage %#x\n", NtStatus));
return NtStatus;
}
NTSTATUS
SspGenerateChallenge(
UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH]
)
/*++
Routine Description:
Generate a challenge.
Arguments:
ChallengeFromClient - challenge from client
Return Value:
NTSTATUS
--*/
{
NTSTATUS NtStatus;
MD5_CTX Md5Context;
FILETIME CurTime;
ULONG ulRandom;
C_ASSERT(sizeof(ULONG) * 2 == MSV1_0_CHALLENGE_LENGTH);
C_ASSERT(MD5DIGESTLEN >= MSV1_0_CHALLENGE_LENGTH);
SspPrint((SSP_NTLMV2, "SspGenerateChallenge\n"));
#ifdef USE_CONSTANT_CHALLENGE
_fmemset(ChallengeFromClient, 0, MSV1_0_CHALLENGE_LENGTH);
return STATUS_SUCCESS;
#endif
ulRandom = rand();
_fmemcpy(ChallengeFromClient, &ulRandom, sizeof(ULONG));
ulRandom = rand();
_fmemcpy(ChallengeFromClient + sizeof(ULONG), &ulRandom, sizeof(ULONG));
NtStatus = SspGetSystemTimeAsFileTime(&CurTime);
if (!NT_SUCCESS(NtStatus))
{
return NtStatus;
}
MD5Init(&Md5Context);
MD5Update(&Md5Context, ChallengeFromClient, MSV1_0_CHALLENGE_LENGTH);
MD5Update(&Md5Context, (UCHAR*)&CurTime, sizeof(CurTime));
MD5Final(&Md5Context);
//
// only take the first half of the MD5 hash
//
_fmemcpy(ChallengeFromClient, Md5Context.digest, MSV1_0_CHALLENGE_LENGTH);
return NtStatus;
}
NTSTATUS
SspConvertRelativeToAbsolute(
IN VOID* pMessageBase,
IN ULONG cbMessageSize,
IN STRING32* pStringToRelocate,
IN BOOLEAN AlignToWchar,
IN BOOLEAN AllowNullString,
OUT STRING* pOutputString
)
/*++
Routine Description:
Convert relative string to absolute string
Arguments:
pMessageBase - message base
cbMessageSize - mssage size
pStringToRelocate - relative string
AlignToWchar - align to wide char
AllowNullString - allow null string
pOutputString - output string
Return Value:
NTSTATUS
--*/
{
ULONG Offset;
//
// If the buffer is allowed to be null,
// check that special case.
//
if (AllowNullString && (pStringToRelocate->Length == 0))
{
pOutputString->MaximumLength = pOutputString->Length = pStringToRelocate->Length;
pOutputString->Buffer = NULL;
return STATUS_SUCCESS;
}
//
// Ensure the string in entirely within the message.
//
Offset = (ULONG)pStringToRelocate->Buffer;
if (Offset >= cbMessageSize || Offset + pStringToRelocate->Length > cbMessageSize)
{
return STATUS_INVALID_PARAMETER;
}
//
// Ensure the buffer is properly aligned.
//
if (AlignToWchar && (!COUNT_IS_ALIGNED(Offset, ALIGN_WCHAR) ||
!COUNT_IS_ALIGNED(pStringToRelocate->Length, ALIGN_WCHAR)))
{
return STATUS_INVALID_PARAMETER;
}
//
// Finally make the pointer absolute.
//
pOutputString->Buffer = (CHAR*)(pMessageBase) + Offset;
pOutputString->MaximumLength = pOutputString->Length = pStringToRelocate->Length ;
return STATUS_SUCCESS;
}
NTSTATUS
SspUpcaseUnicodeStringToOemString(
IN UNICODE_STRING* pUnicodeString,
OUT STRING* pOemString
)
/*++
Routine Description:
Upcase unicode string and convert it to oem string.
Arguments:
pUnicodeString - uncide string
pOemString - OEM string
Return Value:
NTSTATUS
--*/
{
ULONG i;
//
// use a scratch buffer: the strings we encounter are among
// username/domainname/workstationname, hence the length are
// UNLEN maximum
//
CHAR Buffer[2 * (UNLEN + 4)] = {0};
STRING OemString = {0, sizeof(Buffer), Buffer};
if (OemString.MaximumLength < pUnicodeString->Length)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// upcase the unicode string and put it into OemString
//
OemString.Length = pUnicodeString->Length;
for (i = 0; i < pUnicodeString->Length / sizeof(WCHAR); i++)
{
((UNICODE_STRING*)(&OemString))->Buffer[i] = RtlUpcaseUnicodeChar(pUnicodeString->Buffer[i]);
}
return SspUnicodeStringToOemString((STRING*)(pUnicodeString), (UNICODE_STRING*)(&OemString), FALSE);
}
VOID
SspUpcaseUnicodeString(
IN OUT UNICODE_STRING* pUnicodeString
)
/*++
Routine Description:
Upcase unicode string, modifying string in place.
Arguments:
pUnicodeString - string
Return Value:
none
--*/
{
ULONG i;
for (i = 0; i < pUnicodeString->Length / sizeof(WCHAR); i++)
{
pUnicodeString->Buffer[i] = RtlUpcaseUnicodeChar(pUnicodeString->Buffer[i]);
}
}
NTSTATUS
SspGetSystemTimeAsFileTime(
OUT FILETIME* pSystemTimeAsFileTime
)
/*++
Routine Description:
Get system time as FILETIME
Arguments:
pSystemTimeAsFileTime system time as FILETIME
Return Value:
NTSTATUS
--*/
{
SspPrint((SSP_NTLMV2, "SspGetSystemTimeAsFileTime\n"));
#ifdef USE_CONSTANT_CHALLENGE
_fmemset(pSystemTimeAsFileTime, 0, sizeof(*pSystemTimeAsFileTime));
return STATUS_SUCCESS;
#else
return BlGetSystemTimeAsFileTime(pSystemTimeAsFileTime);
#endif
}
NTSTATUS
SspLm20GetNtlmv2ChallengeResponse(
IN NT_OWF_PASSWORD* pNtOwfPassword,
IN UNICODE_STRING* pUserName,
IN UNICODE_STRING* pLogonDomainName,
IN STRING* pTargetInfo,
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
OUT MSV1_0_NTLMV2_RESPONSE* pNtlmv2Response,
OUT MSV1_0_LMV2_RESPONSE* pLmv2Response,
OUT USER_SESSION_KEY* pNtUserSessionKey,
OUT LM_SESSION_KEY* pLmSessionKey
)
/*++
Routine Description:
Get NTLMv2 response and session keys. This route fills in time stamps and
challenge from client.
Arguments:
pNtOwfPassword - NT OWF
pUserName - user name
pLogonDomainName - logon domain name
pTargetInfo - target info
ChallengeToClient - challenge to client
pNtlmv2Response - NTLM v2 response
pLmv2Response - LM v2 response
pNtUserSessionKey - NT user session key
pLmSessionKey - LM session key
Return Value:
NTSTATUS
--*/
{
NTSTATUS NtStatus;
SspPrint((SSP_API, "Entering SspLm20GetNtlmv2ChallengeResponse\n"));
//
// fill in version numbers, timestamp, and client's challenge
//
pNtlmv2Response->RespType = 1;
pNtlmv2Response->HiRespType = 1;
pNtlmv2Response->Flags = 0;
pNtlmv2Response->MsgWord = 0;
NtStatus = SspGetSystemTimeAsFileTime((FILETIME*)(&pNtlmv2Response->TimeStamp));
if (NT_SUCCESS(NtStatus))
{
NtStatus = SspGenerateChallenge(pNtlmv2Response->ChallengeFromClient);
}
if (NT_SUCCESS(NtStatus))
{
_fmemcpy(pNtlmv2Response->Buffer, pTargetInfo->Buffer, pTargetInfo->Length);
//
// Calculate Ntlmv2 response, filling in response field
//
SspGetNtlmv2Response(
pNtOwfPassword,
pUserName,
pLogonDomainName,
pTargetInfo->Length,
ChallengeToClient,
pNtlmv2Response,
pNtUserSessionKey,
pLmSessionKey
);
//
// Use same challenge to compute the LMV2 response
//
_fmemcpy(pLmv2Response->ChallengeFromClient, pNtlmv2Response->ChallengeFromClient, MSV1_0_CHALLENGE_LENGTH);
//
// Calculate LMV2 response
//
SspGetLmv2Response(
pNtOwfPassword,
pUserName,
pLogonDomainName,
ChallengeToClient,
pLmv2Response->ChallengeFromClient,
pLmv2Response->Response
);
}
SspPrint((SSP_API, "Leaving SspLm20GetNtlmv2ChallengeResponse %#x\n", NtStatus));
return NtStatus;
}
VOID
SspGetNtlmv2Response(
IN NT_OWF_PASSWORD* pNtOwfPassword,
IN UNICODE_STRING* pUserName,
IN UNICODE_STRING* pLogonDomainName,
IN ULONG TargetInfoLength,
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
IN OUT MSV1_0_NTLMV2_RESPONSE* pNtlmv2Response,
OUT USER_SESSION_KEY* pNtUserSessionKey,
OUT LM_SESSION_KEY* pLmSessionKey
)
/*++
Routine Description:
Get NTLM v2 response.
Arguments:
pNtOwfPassword - NT OWF
pUserName - user name
pLogonDomainName - logon domain name
TargetInfoLength - target info length
ChallengeToClient - challenge to client
pNtlmv2Response - NTLM v2 response
response - response
pNtUserSessionKey - NT user session key
pLmSessionKey - LM session key
Return Value:
none
--*/
{
HMACMD5_CTX HMACMD5Context;
UCHAR Ntlmv2Owf[MSV1_0_NTLMV2_OWF_LENGTH];
C_ASSERT(MD5DIGESTLEN == MSV1_0_NTLMV2_RESPONSE_LENGTH);
C_ASSERT(MD5DIGESTLEN == sizeof(USER_SESSION_KEY));
C_ASSERT(sizeof(LM_SESSION_KEY) <= sizeof(USER_SESSION_KEY));
SspPrint((SSP_NTLMV2, "SspGetLmv2Response\n"));
//
// get Ntlmv2 OWF
//
SspCalculateNtlmv2Owf(
pNtOwfPassword,
pUserName,
pLogonDomainName,
Ntlmv2Owf
);
HMACMD5Init(
&HMACMD5Context,
Ntlmv2Owf,
MSV1_0_NTLMV2_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
&pNtlmv2Response->RespType,
(MSV1_0_NTLMV2_INPUT_LENGTH + TargetInfoLength)
);
HMACMD5Final(
&HMACMD5Context,
pNtlmv2Response->Response
);
//
// now compute the session keys
// HMAC(Kr, R)
//
HMACMD5Init(
&HMACMD5Context,
Ntlmv2Owf,
MSV1_0_NTLMV2_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
pNtlmv2Response->Response,
MSV1_0_NTLMV2_RESPONSE_LENGTH
);
HMACMD5Final(
&HMACMD5Context,
(UCHAR*)(pNtUserSessionKey)
);
_fmemcpy(pLmSessionKey, pNtUserSessionKey, sizeof(LM_SESSION_KEY));
}
VOID
SspCopyStringAsString32(
IN VOID* pMessageBuffer,
IN STRING* pInString,
IN OUT UCHAR** ppWhere,
OUT STRING32* pOutString32
)
/*++
Routine Description:
Copy string as STRING32
Arguments:
pMessageBuffer - STRING32 base
pInString - input STRING
ppWhere - next empty spot in pMessageBuffer
pOutString32 - output STRING32
Return Value:
none
--*/
{
//
// Copy the data to the Buffer
//
if (pInString->Buffer != NULL)
{
_fmemcpy(*ppWhere, pInString->Buffer, pInString->Length);
}
//
// Build a descriptor to the newly copied data
//
pOutString32->Length = pOutString32->MaximumLength = pInString->Length;
pOutString32->Buffer = (ULONG)(*ppWhere - (UCHAR*)(pMessageBuffer));
//
// Update Where to point past the copied data
//
*ppWhere += pInString->Length;
}
VOID
SspCalculateNtlmv2Owf(
IN NT_OWF_PASSWORD* pNtOwfPassword,
IN UNICODE_STRING* pUserName,
IN UNICODE_STRING* pLogonDomainName,
OUT UCHAR Ntlmv2Owf[MSV1_0_NTLMV2_OWF_LENGTH]
)
/*++
Routine Description:
Calculate Ntlm v2 OWF, salted with username and logon domain name
Arguments:
pNtOwfPassword - NT OWF
pUserName - user name
pLogonDomainName - logon domain name
Ntlmv2Owf - NTLM v2 OWF
Return Value:
none
--*/
{
HMACMD5_CTX HMACMD5Context;
//
// reserve a scratch buffer
//
WCHAR szUserName[(UNLEN + 4)] = {0};
UNICODE_STRING UserName = {0, sizeof(szUserName), szUserName};
SspPrint((SSP_NTLMV2, "SspGetLmv2Response\n"));
//
// first make a copy then upcase it
//
UserName.Length = min(UserName.MaximumLength, pUserName->Length);
ASSERT(UserName.Length == pUserName->Length);
_fmemcpy(UserName.Buffer, pUserName->Buffer, UserName.Length);
SspUpcaseUnicodeString(&UserName);
//
// Calculate Ntlmv2 OWF -- HMAC(MD4(P), (UserName, LogonDomainName))
//
HMACMD5Init(
&HMACMD5Context,
(UCHAR *) pNtOwfPassword,
sizeof(*pNtOwfPassword)
);
HMACMD5Update(
&HMACMD5Context,
(UCHAR *) UserName.Buffer,
UserName.Length
);
HMACMD5Update(
&HMACMD5Context,
(UCHAR *) pLogonDomainName->Buffer,
pLogonDomainName->Length
);
HMACMD5Final(
&HMACMD5Context,
Ntlmv2Owf
);
}
VOID
SspGetLmv2Response(
IN NT_OWF_PASSWORD* pNtOwfPassword,
IN UNICODE_STRING* pUserName,
IN UNICODE_STRING* pLogonDomainName,
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
IN UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH],
OUT UCHAR Response[MSV1_0_NTLMV2_RESPONSE_LENGTH]
)
/*++
Routine Description:
Get LMv2 response
Arguments:
pNtOwfPassword - NT OWF
pUserName - user name
pLogonDomainName - logon domain name
ChallengeToClient - challenge to client
pLmv2Response - Lm v2 response
Routine - response
Return Value:
NTSTATUS
--*/
{
HMACMD5_CTX HMACMD5Context;
UCHAR Ntlmv2Owf[MSV1_0_NTLMV2_OWF_LENGTH];
C_ASSERT(MD5DIGESTLEN == MSV1_0_NTLMV2_RESPONSE_LENGTH);
SspPrint((SSP_NTLMV2, "SspGetLmv2Response\n"));
//
// get Ntlmv2 OWF
//
SspCalculateNtlmv2Owf(
pNtOwfPassword,
pUserName,
pLogonDomainName,
Ntlmv2Owf
);
//
// Calculate Ntlmv2 Response
// HMAC(Ntlmv2Owf, (ChallengeToClient, ChallengeFromClient))
//
HMACMD5Init(
&HMACMD5Context,
Ntlmv2Owf,
MSV1_0_NTLMV2_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Final(
&HMACMD5Context,
Response
);
return;
}
NTSTATUS
SspMakeSessionKeys(
IN ULONG NegotiateFlags,
IN STRING* pLmChallengeResponse,
IN USER_SESSION_KEY* pNtUserSessionKey, // from the DC or GetChalResp
IN LM_SESSION_KEY* pLanmanSessionKey, // from the DC of GetChalResp
OUT STRING* pDatagramSessionKey, // this is the session key sent over wire
OUT USER_SESSION_KEY* pContextSessionKey // session key in context
)
/*++
Routine Description:
Make NTLMv2 context session key and DatagramSessionKey.
Arguments:
NegotiateFlags - negotiate flags
pLmChallengeResponse - LM challenge response
pNtUserSessionKey - NtUserSessionKey
pLanmanSessionKey - LanmanSessionKey
pDatagramSessionKey - DatagramSessionKey
pContextSessionKey - NTLMv2 conext session key
Return Value:
NTSTATUS
--*/
{
NTSTATUS NtStatus = STATUS_SUCCESS;
UCHAR pLocalSessionKey[sizeof(USER_SESSION_KEY)] = {0};
SspPrint((SSP_NTLMV2, "Entering SspMakeSessionKeys\n"));
if (!(NegotiateFlags & (NTLMSSP_NEGOTIATE_SIGN| NTLMSSP_NEGOTIATE_SEAL)))
{
_fmemcpy(pContextSessionKey, pNtUserSessionKey, sizeof(pLocalSessionKey));
return STATUS_SUCCESS;
}
if (NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2)
{
_fmemcpy(pLocalSessionKey, pNtUserSessionKey, sizeof(pLocalSessionKey));
}
else if(NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY)
{
LM_OWF_PASSWORD LmKey;
LM_RESPONSE LmResponseKey;
BYTE pTemporaryResponse[LM_RESPONSE_LENGTH] = {0};
if (pLmChallengeResponse->Length > LM_RESPONSE_LENGTH)
{
return STATUS_NOT_SUPPORTED;
}
_fmemcpy(pTemporaryResponse, pLmChallengeResponse->Buffer, pLmChallengeResponse->Length);
_fmemcpy(&LmKey, pLanmanSessionKey, sizeof(LM_SESSION_KEY));
_fmemset((UCHAR*)(&LmKey) + sizeof(LM_SESSION_KEY),
NTLMSSP_KEY_SALT,
LM_OWF_PASSWORD_LENGTH - sizeof(LM_SESSION_KEY)
);
NtStatus = CalculateLmResponse(
(LM_CHALLENGE *) pTemporaryResponse,
&LmKey,
&LmResponseKey
);
if (!NT_SUCCESS(NtStatus))
{
return NtStatus;
}
_fmemcpy(pLocalSessionKey, &LmResponseKey, sizeof(USER_SESSION_KEY));
}
else
{
_fmemcpy(pLocalSessionKey, pNtUserSessionKey, sizeof(USER_SESSION_KEY));
}
if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
{
struct RC4_KEYSTRUCT Rc4Key;
rc4_key(
&Rc4Key,
sizeof(USER_SESSION_KEY),
pLocalSessionKey
);
if (pDatagramSessionKey == NULL)
{
rc4(
&Rc4Key,
sizeof(USER_SESSION_KEY),
(UCHAR*) pContextSessionKey
);
}
else
{
pDatagramSessionKey->Length =
pDatagramSessionKey->MaximumLength =
sizeof(USER_SESSION_KEY);
_fmemcpy(pDatagramSessionKey->Buffer, pContextSessionKey, sizeof(USER_SESSION_KEY));
rc4(
&Rc4Key,
sizeof(USER_SESSION_KEY),
(UCHAR*)(pDatagramSessionKey->Buffer)
);
}
}
else
{
_fmemcpy(pContextSessionKey, pLocalSessionKey, sizeof(USER_SESSION_KEY));
}
SspPrint((SSP_NTLMV2, "Leaving SspMakeSessionKeys %#x\n", NtStatus));
return NtStatus;
}
VOID
SspMakeNtlmv2SKeys(
IN USER_SESSION_KEY* pUserSessionKey,
IN ULONG NegotiateFlags,
IN ULONG SendNonce,
IN ULONG RecvNonce,
OUT NTLMV2_DERIVED_SKEYS* pNtlmv2Keys
)
/*++
Routine Description:
Derive all NTLMv2 session keys
Arguments:
pUserSessionKey - NTLMv2 user session key
NegotiateFlags - negotiate flags
SendNonce - send message sequence number
RecvNonce - receive message sequence number
pNtlmv2Keys - derived NTLMv2 session keys
Return Value:
none
--*/
{
MD5_CTX Md5Context;
C_ASSERT(MD5DIGESTLEN == sizeof(USER_SESSION_KEY));
SspPrint((SSP_NTLMV2, "SspMakeSessionKeys\n"));
if (NegotiateFlags & NTLMSSP_NEGOTIATE_128)
{
pNtlmv2Keys->KeyLen = 16;
}
else if (NegotiateFlags & NTLMSSP_NEGOTIATE_56)
{
pNtlmv2Keys->KeyLen = 7;
}
else
{
pNtlmv2Keys->KeyLen = 5;
}
//
// make client to server encryption key
//
MD5Init(&Md5Context);
MD5Update(&Md5Context, (UCHAR*)(pUserSessionKey), pNtlmv2Keys->KeyLen);
MD5Update(&Md5Context, (UCHAR*)(CSSEALMAGIC), sizeof(CSSEALMAGIC));
MD5Final(&Md5Context);
_fmemcpy(&pNtlmv2Keys->SealSessionKey, Md5Context.digest, sizeof(USER_SESSION_KEY));
//
// make server to client encryption key
//
MD5Init(&Md5Context);
MD5Update(&Md5Context, (UCHAR*)(pUserSessionKey), pNtlmv2Keys->KeyLen);
MD5Update(&Md5Context, (UCHAR*)(SCSEALMAGIC), sizeof(SCSEALMAGIC));
MD5Final(&Md5Context);
_fmemcpy(&pNtlmv2Keys->UnsealSessionKey, Md5Context.digest, sizeof(USER_SESSION_KEY));
//
// make client to server signing key -- always 128 bits!
//
MD5Init(&Md5Context);
MD5Update(&Md5Context, (UCHAR*)(pUserSessionKey), sizeof(USER_SESSION_KEY));
MD5Update(&Md5Context, (UCHAR*)(CSSIGNMAGIC), sizeof(CSSIGNMAGIC));
MD5Final(&Md5Context);
_fmemcpy(&pNtlmv2Keys->SignSessionKey, Md5Context.digest, sizeof(USER_SESSION_KEY));
//
// make server to client signing key
//
MD5Init(&Md5Context);
MD5Update(&Md5Context, (UCHAR*)(pUserSessionKey), sizeof(USER_SESSION_KEY));
MD5Update(&Md5Context, (UCHAR*)(SCSIGNMAGIC), sizeof(SCSIGNMAGIC));
MD5Final(&Md5Context);
_fmemcpy(&pNtlmv2Keys->VerifySessionKey, Md5Context.digest, sizeof(USER_SESSION_KEY));
//
// set pointers to different key schedule and nonce for each direction
// key schedule will be filled in later...
//
pNtlmv2Keys->pSealRc4Sched = &pNtlmv2Keys->SealRc4Sched;
pNtlmv2Keys->pUnsealRc4Sched = &pNtlmv2Keys->UnsealRc4Sched;
pNtlmv2Keys->pSendNonce = &pNtlmv2Keys->SendNonce;
pNtlmv2Keys->pRecvNonce = &pNtlmv2Keys->RecvNonce;
pNtlmv2Keys->SendNonce = SendNonce;
pNtlmv2Keys->RecvNonce = RecvNonce;
rc4_key(&pNtlmv2Keys->SealRc4Sched, sizeof(USER_SESSION_KEY), (UCHAR*)(&pNtlmv2Keys->SealSessionKey));
rc4_key(&pNtlmv2Keys->UnsealRc4Sched, sizeof(USER_SESSION_KEY), (UCHAR*)(&pNtlmv2Keys->UnsealSessionKey));
}
NTSTATUS
SspSignSealHelper(
IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys,
IN ULONG NegotiateFlags,
IN eSignSealOp Op,
IN ULONG MessageSeqNo,
IN OUT SecBufferDesc* pMessage,
OUT NTLMSSP_MESSAGE_SIGNATURE* pSig,
OUT NTLMSSP_MESSAGE_SIGNATURE** ppSig
)
/*++
Routine Description:
Helper function for signing/sealing/unsealing/verifying.
Arguments:
pNtlmv2Keys - key materials
NegotiateFlags - negotiate Flags
Op - which operation to performance
MessageSeqNo - message sequence number
pMessage - message buffer descriptor
pSig - result signature
ppSig - address of the signature token in message
buffer descriptor pMessage
Return Value:
SECURITY_STATUS
--*/
{
NTSTATUS NtStatus = STATUS_SUCCESS;
HMACMD5_CTX HMACMD5Context;
UCHAR TempSig[MD5DIGESTLEN];
NTLMSSP_MESSAGE_SIGNATURE Sig;
int Signature;
ULONG i;
PUCHAR pKey; // ptr to key to use for encryption
PUCHAR pSignKey; // ptr to key to use for signing
PULONG pNonce; // ptr to nonce to use
struct RC4_KEYSTRUCT* pRc4Sched; // ptr to key schedule to use
NTLMSSP_MESSAGE_SIGNATURE AlignedSig; // aligned copy of input sig data
SspPrint((SSP_NTLMV2, "Entering SspSignSealHelper NegotiateFlags %#x, eSignSealOp %d\n", NegotiateFlags, Op));
//
// pre-initialize to null in case of failure.
//
*ppSig = NULL;
Signature = -1;
for (i = 0; i < pMessage->cBuffers; i++)
{
if ((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_TOKEN)
{
Signature = i;
break;
}
}
if (Signature == -1)
{
NtStatus = STATUS_INVALID_PARAMETER;
}
if (NT_SUCCESS(NtStatus))
{
if (pMessage->pBuffers[Signature].cbBuffer < sizeof(NTLMSSP_MESSAGE_SIGNATURE))
{
NtStatus = STATUS_INVALID_PARAMETER;
}
}
if (NT_SUCCESS(NtStatus))
{
*ppSig = (NTLMSSP_MESSAGE_SIGNATURE*)(pMessage->pBuffers[Signature].pvBuffer);
_fmemcpy(&AlignedSig, *ppSig, sizeof(AlignedSig));
//
// If sequence detect wasn't requested, put on an empty security token.
// Don't do the check if Seal/Unseal is called
//
if (!(NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) &&
(Op == eSign || Op == eVerify))
{
_fmemset(pSig, 0, sizeof(NTLMSSP_MESSAGE_SIGNATURE));
pSig->Version = NTLM_SIGN_VERSION;
NtStatus = STATUS_SUCCESS;
}
}
if (NT_SUCCESS(NtStatus))
{
switch (Op)
{
case eSeal:
pSignKey = pNtlmv2Keys->SignSessionKey; // if NTLM2
pKey = pNtlmv2Keys->SealSessionKey;
pRc4Sched = pNtlmv2Keys->pSealRc4Sched;
pNonce = pNtlmv2Keys->pSendNonce;
break;
case eUnseal:
pSignKey = pNtlmv2Keys->VerifySessionKey; // if NTLM2
pKey = pNtlmv2Keys->UnsealSessionKey;
pRc4Sched = pNtlmv2Keys->pUnsealRc4Sched;
pNonce = pNtlmv2Keys->pRecvNonce;
break;
case eSign:
pSignKey = pNtlmv2Keys->SignSessionKey; // if NTLM2
pKey = pNtlmv2Keys->SealSessionKey; // might be used to encrypt the signature
pRc4Sched = pNtlmv2Keys->pSealRc4Sched;
pNonce = pNtlmv2Keys->pSendNonce;
break;
case eVerify:
pSignKey = pNtlmv2Keys->VerifySessionKey; // if NTLM2
pKey = pNtlmv2Keys->UnsealSessionKey; // might be used to decrypt the signature
pRc4Sched = pNtlmv2Keys->pUnsealRc4Sched;
pNonce = pNtlmv2Keys->pRecvNonce;
break;
default:
NtStatus = (STATUS_INVALID_LEVEL);
break;
}
}
//
// Either we can supply the sequence number, or the application can supply
// the message sequence number.
//
if (NT_SUCCESS(NtStatus))
{
Sig.Version = NTLM_SIGN_VERSION;
if ((NegotiateFlags & NTLMSSP_APP_SEQ) == 0)
{
Sig.Nonce = *pNonce; // use our sequence number
(*pNonce) += 1;
}
else
{
if (Op == eSeal || Op == eSign || MessageSeqNo != 0)
{
Sig.Nonce = MessageSeqNo;
}
else
{
Sig.Nonce = AlignedSig.Nonce;
}
//
// if using RC4, must rekey for each packet RC4 is used for seal,
// unseal; and for encrypting the HMAC hash if key exchange was
// negotiated (we use just HMAC if no key exchange, so that a good
// signing option exists with no RC4 encryption needed)
//
if (Op == eSeal || Op == eUnseal || NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
{
MD5_CTX Md5ContextReKey;
C_ASSERT(MD5DIGESTLEN == sizeof(USER_SESSION_KEY));
MD5Init(&Md5ContextReKey);
MD5Update(&Md5ContextReKey, pKey, sizeof(USER_SESSION_KEY));
MD5Update(&Md5ContextReKey, (unsigned char*)&Sig.Nonce, sizeof(Sig.Nonce));
MD5Final(&Md5ContextReKey);
rc4_key(pRc4Sched, sizeof(USER_SESSION_KEY), Md5ContextReKey.digest);
}
}
//
// using HMAC hash, init it with the key
//
HMACMD5Init(&HMACMD5Context, pSignKey, sizeof(USER_SESSION_KEY));
//
// include the message sequence number
//
HMACMD5Update(&HMACMD5Context, (unsigned char*)&Sig.Nonce, sizeof(Sig.Nonce));
for (i = 0; i < pMessage->cBuffers; i++)
{
if (((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_DATA) &&
(pMessage->pBuffers[i].cbBuffer != 0))
{
//
// decrypt (before checksum...) if it's not READ_ONLY
//
if ((Op == eUnseal)
&& !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY))
{
rc4(
pRc4Sched,
pMessage->pBuffers[i].cbBuffer,
(UCHAR*)(pMessage->pBuffers[i].pvBuffer)
);
}
HMACMD5Update(
&HMACMD5Context,
(UCHAR*)(pMessage->pBuffers[i].pvBuffer),
pMessage->pBuffers[i].cbBuffer
);
//
// Encrypt if its not READ_ONLY
//
if ((Op == eSeal)
&& !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY))
{
rc4(
pRc4Sched,
pMessage->pBuffers[i].cbBuffer,
(UCHAR*)(pMessage->pBuffers[i].pvBuffer)
);
}
}
}
HMACMD5Final(&HMACMD5Context, TempSig);
//
// use RandomPad and Checksum fields for 8 bytes of MD5 hash
//
_fmemcpy(&Sig.RandomPad, TempSig, 8);
//
// if we're using crypto for KEY_EXCH, may as well use it for signing too
//
if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
{
rc4(
pRc4Sched,
8,
(UCHAR*)(&Sig.RandomPad)
);
}
_fmemcpy(pSig, &Sig, sizeof(NTLMSSP_MESSAGE_SIGNATURE));
}
SspPrint((SSP_NTLMV2, "Leaving SspSignSealHelper %#x\n", NtStatus));
return NtStatus;
}
SECURITY_STATUS
SspNtlmv2MakeSignature(
IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys,
IN ULONG NegotiateFlags,
IN ULONG fQOP,
IN ULONG MessageSeqNo,
IN OUT SecBufferDesc* pMessage
)
/*++
Routine Description:
Make signature of a message
Arguments:
pNtlmv2Keys - key materials
NegotiateFlags - negotiate Flags
fQOP - quality of protection
MessageSeqNo - message Sequence Number
pMessage - message buffer descriptor
Return Value:
SECURITY_STATUS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
NTLMSSP_MESSAGE_SIGNATURE Sig;
NTLMSSP_MESSAGE_SIGNATURE *pSig;
Status = SspSignSealHelper(
pNtlmv2Keys,
NegotiateFlags,
eSign,
MessageSeqNo,
pMessage,
&Sig,
&pSig
);
if (NT_SUCCESS(Status))
{
_fmemcpy(pSig, &Sig, sizeof(NTLMSSP_MESSAGE_SIGNATURE));
}
return SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR);
}
SECURITY_STATUS
SspNtlmv2VerifySignature(
IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys,
IN ULONG NegotiateFlags,
IN ULONG MessageSeqNo,
IN OUT SecBufferDesc* pMessage,
OUT ULONG* pfQOP
)
/*++
Routine Description:
Verify signature of a message
Arguments:
pNtlmv2Keys - key materials
NegotiateFlags - negotiate Flags
MessageSeqNo - message Sequence Number
pMessage - message buffer descriptor
pfQOP - quality of protection
Return Value:
SECURITY_STATUS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
NTLMSSP_MESSAGE_SIGNATURE Sig;
NTLMSSP_MESSAGE_SIGNATURE* pSig; // pointer to buffer with sig in it
NTLMSSP_MESSAGE_SIGNATURE AlignedSig; // Aligned sig buffer.
Status = SspSignSealHelper(
pNtlmv2Keys,
NegotiateFlags,
eVerify,
MessageSeqNo,
pMessage,
&Sig,
&pSig
);
if (NT_SUCCESS(Status))
{
_fmemcpy(&AlignedSig, pSig, sizeof(AlignedSig));
if (AlignedSig.Version != NTLM_SIGN_VERSION)
{
return SEC_E_INVALID_TOKEN;
}
//
// validate the signature...
//
if (AlignedSig.CheckSum != Sig.CheckSum)
{
return SEC_E_MESSAGE_ALTERED;
}
//
// with MD5 sig, this now matters!
//
if (AlignedSig.RandomPad != Sig.RandomPad)
{
return SEC_E_MESSAGE_ALTERED;
}
if (AlignedSig.Nonce != Sig.Nonce)
{
return SEC_E_OUT_OF_SEQUENCE;
}
}
return SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR);
}
SECURITY_STATUS
SspNtlmv2SealMessage(
IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys,
IN ULONG NegotiateFlags,
IN ULONG fQOP,
IN ULONG MessageSeqNo,
IN OUT SecBufferDesc* pMessage
)
/*++
Routine Description:
Seal a message
Arguments:
pNtlmv2Keys - key materials
NegotiateFlags - negotiate Flags
fQOP - quality of protection
MessageSeqNo - message Sequence Number
pMessage - message buffer descriptor
Return Value:
SECURITY_STATUS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
NTLMSSP_MESSAGE_SIGNATURE Sig;
NTLMSSP_MESSAGE_SIGNATURE* pSig; // pointer to buffer where sig goes
ULONG i;
Status = SspSignSealHelper(
pNtlmv2Keys,
NegotiateFlags,
eSeal,
MessageSeqNo,
pMessage,
&Sig,
&pSig
);
if (NT_SUCCESS(Status))
{
_fmemcpy(pSig, &Sig, sizeof(NTLMSSP_MESSAGE_SIGNATURE));
//
// for gss style sign/seal, strip the padding as RC4 requires none.
// (in fact, we rely on this to simplify the size computation in
// DecryptMessage). if we support some other block cipher, need to rev
// the NTLM_ token version to make blocksize
//
for (i = 0; i < pMessage->cBuffers; i++)
{
if ((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_PADDING)
{
//
// no padding required!
//
pMessage->pBuffers[i].cbBuffer = 0;
break;
}
}
}
return SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR);
}
SECURITY_STATUS
SspNtlmv2UnsealMessage(
IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys,
IN ULONG NegotiateFlags,
IN ULONG MessageSeqNo,
IN OUT SecBufferDesc* pMessage,
OUT ULONG* pfQOP
)
/*++
Routine Description:
Unseal a message
Arguments:
pNtlmv2Keys - key materials
NegotiateFlags - negotiate Flags
MessageSeqNo - message Sequence Number
pMessage - message buffer descriptor
pfQOP - quality of protection
Return Value:
SECURITY_STATUS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
NTLMSSP_MESSAGE_SIGNATURE Sig;
NTLMSSP_MESSAGE_SIGNATURE* pSig; // pointer to buffer where sig goes
NTLMSSP_MESSAGE_SIGNATURE AlignedSig; // aligned buffer.
SecBufferDesc* pMessageBuffers = pMessage;
ULONG Index;
SecBuffer* pSignatureBuffer = NULL;
SecBuffer* pStreamBuffer = NULL;
SecBuffer* pDataBuffer = NULL;
SecBufferDesc ProcessBuffers;
SecBuffer wrap_bufs[2];
//
// Find the body and signature SecBuffers from pMessage
//
for (Index = 0; Index < pMessageBuffers->cBuffers; Index++)
{
if ((pMessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_TOKEN)
{
pSignatureBuffer = &pMessageBuffers->pBuffers[Index];
}
else if ((pMessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_STREAM)
{
pStreamBuffer = &pMessageBuffers->pBuffers[Index];
}
else if ((pMessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_DATA)
{
pDataBuffer = &pMessageBuffers->pBuffers[Index];
}
}
if (pStreamBuffer != NULL)
{
if (pSignatureBuffer != NULL)
{
return SEC_E_INVALID_TOKEN;
}
//
// for version 1 NTLM blobs, padding is never present, since RC4 is
// stream cipher
//
wrap_bufs[0].cbBuffer = sizeof(NTLMSSP_MESSAGE_SIGNATURE);
wrap_bufs[1].cbBuffer = pStreamBuffer->cbBuffer - sizeof(NTLMSSP_MESSAGE_SIGNATURE);
if (pStreamBuffer->cbBuffer < wrap_bufs[0].cbBuffer)
{
return SEC_E_INVALID_TOKEN;
}
wrap_bufs[0].BufferType = SECBUFFER_TOKEN;
wrap_bufs[0].pvBuffer = pStreamBuffer->pvBuffer;
wrap_bufs[1].BufferType = SECBUFFER_DATA;
wrap_bufs[1].pvBuffer = (PBYTE)wrap_bufs[0].pvBuffer + wrap_bufs[0].cbBuffer;
if (pDataBuffer == NULL)
{
return SEC_E_INVALID_TOKEN;
}
pDataBuffer->cbBuffer = wrap_bufs[1].cbBuffer;
pDataBuffer->pvBuffer = wrap_bufs[1].pvBuffer;
ProcessBuffers.cBuffers = 2;
ProcessBuffers.pBuffers = wrap_bufs;
ProcessBuffers.ulVersion = SECBUFFER_VERSION;
}
else
{
ProcessBuffers = *pMessageBuffers;
}
Status = SspSignSealHelper(
pNtlmv2Keys,
NegotiateFlags,
eUnseal,
MessageSeqNo,
&ProcessBuffers,
&Sig,
&pSig
);
if (NT_SUCCESS(Status))
{
_fmemcpy(&AlignedSig, pSig, sizeof(AlignedSig));
if (AlignedSig.Version != NTLM_SIGN_VERSION)
{
return SEC_E_INVALID_TOKEN;
}
//
// validate the signature...
//
if (AlignedSig.CheckSum != Sig.CheckSum)
{
return SEC_E_MESSAGE_ALTERED;
}
if (AlignedSig.RandomPad != Sig.RandomPad)
{
return SEC_E_MESSAGE_ALTERED;
}
if (AlignedSig.Nonce != Sig.Nonce)
{
return SEC_E_OUT_OF_SEQUENCE;
}
}
return SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR);
}