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.
2494 lines
60 KiB
2494 lines
60 KiB
/*++
|
|
|
|
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);
|
|
}
|