8cc6582044
* Unified enum/struct definitions, fixed include issues * Fixed mac compilation issues * Added missing include * Fixed windows server build warnings * Fixed VS2010 build issue * Removed unnecessary library linking * Fixed ThreadPool WinXP compatibility * Fixed pr review remarks
1177 lines
40 KiB
C
1177 lines
40 KiB
C
/**
|
|
* WinPR: Windows Portable Runtime
|
|
* NTLM Security Package (Message)
|
|
*
|
|
* Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "ntlm.h"
|
|
#include "../sspi.h"
|
|
|
|
#include <winpr/crt.h>
|
|
#include <winpr/print.h>
|
|
#include <winpr/stream.h>
|
|
#include <winpr/sysinfo.h>
|
|
|
|
#include "ntlm_compute.h"
|
|
|
|
#include "ntlm_message.h"
|
|
|
|
#include "../../log.h"
|
|
#define TAG WINPR_TAG("sspi.NTLM")
|
|
|
|
static const char NTLM_SIGNATURE[8] = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' };
|
|
|
|
#ifdef WITH_DEBUG_NTLM
|
|
static const char* const NTLM_NEGOTIATE_STRINGS[] = { "NTLMSSP_NEGOTIATE_56",
|
|
"NTLMSSP_NEGOTIATE_KEY_EXCH",
|
|
"NTLMSSP_NEGOTIATE_128",
|
|
"NTLMSSP_RESERVED1",
|
|
"NTLMSSP_RESERVED2",
|
|
"NTLMSSP_RESERVED3",
|
|
"NTLMSSP_NEGOTIATE_VERSION",
|
|
"NTLMSSP_RESERVED4",
|
|
"NTLMSSP_NEGOTIATE_TARGET_INFO",
|
|
"NTLMSSP_REQUEST_NON_NT_SESSION_KEY",
|
|
"NTLMSSP_RESERVED5",
|
|
"NTLMSSP_NEGOTIATE_IDENTIFY",
|
|
"NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY",
|
|
"NTLMSSP_RESERVED6",
|
|
"NTLMSSP_TARGET_TYPE_SERVER",
|
|
"NTLMSSP_TARGET_TYPE_DOMAIN",
|
|
"NTLMSSP_NEGOTIATE_ALWAYS_SIGN",
|
|
"NTLMSSP_RESERVED7",
|
|
"NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED",
|
|
"NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED",
|
|
"NTLMSSP_NEGOTIATE_ANONYMOUS",
|
|
"NTLMSSP_RESERVED8",
|
|
"NTLMSSP_NEGOTIATE_NTLM",
|
|
"NTLMSSP_RESERVED9",
|
|
"NTLMSSP_NEGOTIATE_LM_KEY",
|
|
"NTLMSSP_NEGOTIATE_DATAGRAM",
|
|
"NTLMSSP_NEGOTIATE_SEAL",
|
|
"NTLMSSP_NEGOTIATE_SIGN",
|
|
"NTLMSSP_RESERVED10",
|
|
"NTLMSSP_REQUEST_TARGET",
|
|
"NTLMSSP_NEGOTIATE_OEM",
|
|
"NTLMSSP_NEGOTIATE_UNICODE" };
|
|
|
|
static void ntlm_print_negotiate_flags(UINT32 flags)
|
|
{
|
|
int i;
|
|
const char* str;
|
|
WLog_VRB(TAG, "negotiateFlags \"0x%08" PRIX32 "\"", flags);
|
|
|
|
for (i = 31; i >= 0; i--)
|
|
{
|
|
if ((flags >> i) & 1)
|
|
{
|
|
str = NTLM_NEGOTIATE_STRINGS[(31 - i)];
|
|
WLog_VRB(TAG, "\t%s (%d),", str, (31 - i));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int ntlm_read_message_header(wStream* s, NTLM_MESSAGE_HEADER* header)
|
|
{
|
|
if (Stream_GetRemainingLength(s) < 12)
|
|
return -1;
|
|
|
|
Stream_Read(s, header->Signature, 8);
|
|
Stream_Read_UINT32(s, header->MessageType);
|
|
|
|
if (strncmp((char*)header->Signature, NTLM_SIGNATURE, 8) != 0)
|
|
return -1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void ntlm_write_message_header(wStream* s, NTLM_MESSAGE_HEADER* header)
|
|
{
|
|
Stream_Write(s, header->Signature, sizeof(NTLM_SIGNATURE));
|
|
Stream_Write_UINT32(s, header->MessageType);
|
|
}
|
|
|
|
static void ntlm_populate_message_header(NTLM_MESSAGE_HEADER* header, UINT32 MessageType)
|
|
{
|
|
CopyMemory(header->Signature, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
|
|
header->MessageType = MessageType;
|
|
}
|
|
|
|
static int ntlm_read_message_fields(wStream* s, NTLM_MESSAGE_FIELDS* fields)
|
|
{
|
|
if (Stream_GetRemainingLength(s) < 8)
|
|
return -1;
|
|
|
|
Stream_Read_UINT16(s, fields->Len); /* Len (2 bytes) */
|
|
Stream_Read_UINT16(s, fields->MaxLen); /* MaxLen (2 bytes) */
|
|
Stream_Read_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */
|
|
return 1;
|
|
}
|
|
|
|
static void ntlm_write_message_fields(wStream* s, NTLM_MESSAGE_FIELDS* fields)
|
|
{
|
|
if (fields->MaxLen < 1)
|
|
fields->MaxLen = fields->Len;
|
|
|
|
Stream_Write_UINT16(s, fields->Len); /* Len (2 bytes) */
|
|
Stream_Write_UINT16(s, fields->MaxLen); /* MaxLen (2 bytes) */
|
|
Stream_Write_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */
|
|
}
|
|
|
|
static int ntlm_read_message_fields_buffer(wStream* s, NTLM_MESSAGE_FIELDS* fields)
|
|
{
|
|
if (fields->Len > 0)
|
|
{
|
|
const UINT32 offset = fields->BufferOffset + fields->Len;
|
|
|
|
if (fields->BufferOffset > UINT32_MAX - fields->Len)
|
|
return -1;
|
|
|
|
if (offset > Stream_Length(s))
|
|
return -1;
|
|
|
|
fields->Buffer = (PBYTE)malloc(fields->Len);
|
|
|
|
if (!fields->Buffer)
|
|
return -1;
|
|
|
|
Stream_SetPosition(s, fields->BufferOffset);
|
|
Stream_Read(s, fields->Buffer, fields->Len);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void ntlm_write_message_fields_buffer(wStream* s, NTLM_MESSAGE_FIELDS* fields)
|
|
{
|
|
if (fields->Len > 0)
|
|
{
|
|
Stream_SetPosition(s, fields->BufferOffset);
|
|
Stream_Write(s, fields->Buffer, fields->Len);
|
|
}
|
|
}
|
|
|
|
static void ntlm_free_message_fields_buffer(NTLM_MESSAGE_FIELDS* fields)
|
|
{
|
|
if (fields)
|
|
{
|
|
if (fields->Buffer)
|
|
{
|
|
free(fields->Buffer);
|
|
fields->Len = 0;
|
|
fields->MaxLen = 0;
|
|
fields->Buffer = NULL;
|
|
fields->BufferOffset = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_DEBUG_NTLM
|
|
static void ntlm_print_message_fields(NTLM_MESSAGE_FIELDS* fields, const char* name)
|
|
{
|
|
WLog_VRB(TAG, "%s (Len: %" PRIu16 " MaxLen: %" PRIu16 " BufferOffset: %" PRIu32 ")", name,
|
|
fields->Len, fields->MaxLen, fields->BufferOffset);
|
|
|
|
if (fields->Len > 0)
|
|
winpr_HexDump(TAG, WLOG_TRACE, fields->Buffer, fields->Len);
|
|
}
|
|
#endif
|
|
|
|
SECURITY_STATUS ntlm_read_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
|
|
{
|
|
wStream* s;
|
|
size_t length;
|
|
NTLM_NEGOTIATE_MESSAGE* message;
|
|
message = &context->NEGOTIATE_MESSAGE;
|
|
ZeroMemory(message, sizeof(NTLM_NEGOTIATE_MESSAGE));
|
|
s = Stream_New((BYTE*)buffer->pvBuffer, buffer->cbBuffer);
|
|
|
|
if (!s)
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
if (ntlm_read_message_header(s, (NTLM_MESSAGE_HEADER*)message) < 0)
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INVALID_TOKEN;
|
|
}
|
|
|
|
if (message->MessageType != MESSAGE_TYPE_NEGOTIATE)
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INVALID_TOKEN;
|
|
}
|
|
|
|
if (Stream_GetRemainingLength(s) < 4)
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INVALID_TOKEN;
|
|
}
|
|
Stream_Read_UINT32(s, message->NegotiateFlags); /* NegotiateFlags (4 bytes) */
|
|
|
|
if (!((message->NegotiateFlags & NTLMSSP_REQUEST_TARGET) &&
|
|
(message->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM) &&
|
|
(message->NegotiateFlags & NTLMSSP_NEGOTIATE_UNICODE)))
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INVALID_TOKEN;
|
|
}
|
|
|
|
context->NegotiateFlags = message->NegotiateFlags;
|
|
|
|
/* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */
|
|
|
|
if (ntlm_read_message_fields(s, &(message->DomainName)) < 0) /* DomainNameFields (8 bytes) */
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INVALID_TOKEN;
|
|
}
|
|
|
|
/* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */
|
|
|
|
if (ntlm_read_message_fields(s, &(message->Workstation)) < 0) /* WorkstationFields (8 bytes) */
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INVALID_TOKEN;
|
|
}
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
{
|
|
if (ntlm_read_version_info(s, &(message->Version)) < 0) /* Version (8 bytes) */
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INVALID_TOKEN;
|
|
}
|
|
}
|
|
|
|
length = Stream_GetPosition(s);
|
|
WINPR_ASSERT(length <= ULONG_MAX);
|
|
buffer->cbBuffer = (ULONG)length;
|
|
|
|
if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length))
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer);
|
|
context->NegotiateMessage.BufferType = buffer->BufferType;
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "NEGOTIATE_MESSAGE (length = %" PRIu32 ")", context->NegotiateMessage.cbBuffer);
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->NegotiateMessage.pvBuffer,
|
|
context->NegotiateMessage.cbBuffer);
|
|
ntlm_print_negotiate_flags(message->NegotiateFlags);
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_print_version_info(&(message->Version));
|
|
|
|
#endif
|
|
context->state = NTLM_STATE_CHALLENGE;
|
|
Stream_Free(s, FALSE);
|
|
return SEC_I_CONTINUE_NEEDED;
|
|
}
|
|
|
|
SECURITY_STATUS ntlm_write_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
|
|
{
|
|
wStream* s;
|
|
size_t length;
|
|
NTLM_NEGOTIATE_MESSAGE* message;
|
|
message = &context->NEGOTIATE_MESSAGE;
|
|
ZeroMemory(message, sizeof(NTLM_NEGOTIATE_MESSAGE));
|
|
s = Stream_New((BYTE*)buffer->pvBuffer, buffer->cbBuffer);
|
|
|
|
if (!s)
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
ntlm_populate_message_header((NTLM_MESSAGE_HEADER*)message, MESSAGE_TYPE_NEGOTIATE);
|
|
|
|
if (context->NTLMv2)
|
|
{
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_56;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_LM_KEY;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM;
|
|
}
|
|
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_128;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN;
|
|
message->NegotiateFlags |= NTLMSSP_REQUEST_TARGET;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;
|
|
|
|
if (context->confidentiality)
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;
|
|
|
|
if (context->SendVersionInfo)
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_get_version_info(&(message->Version));
|
|
|
|
context->NegotiateFlags = message->NegotiateFlags;
|
|
/* Message Header (12 bytes) */
|
|
ntlm_write_message_header(s, (NTLM_MESSAGE_HEADER*)message);
|
|
Stream_Write_UINT32(s, message->NegotiateFlags); /* NegotiateFlags (4 bytes) */
|
|
/* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */
|
|
/* DomainNameFields (8 bytes) */
|
|
ntlm_write_message_fields(s, &(message->DomainName));
|
|
/* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */
|
|
/* WorkstationFields (8 bytes) */
|
|
ntlm_write_message_fields(s, &(message->Workstation));
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_write_version_info(s, &(message->Version));
|
|
|
|
length = Stream_GetPosition(s);
|
|
WINPR_ASSERT(length <= ULONG_MAX);
|
|
buffer->cbBuffer = (ULONG)length;
|
|
|
|
if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length))
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer);
|
|
context->NegotiateMessage.BufferType = buffer->BufferType;
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "NEGOTIATE_MESSAGE (length = %d)", length);
|
|
winpr_HexDump(TAG, WLOG_TRACE, Stream_Buffer(s), length);
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_print_version_info(&(message->Version));
|
|
|
|
#endif
|
|
context->state = NTLM_STATE_CHALLENGE;
|
|
Stream_Free(s, FALSE);
|
|
return SEC_I_CONTINUE_NEEDED;
|
|
}
|
|
|
|
SECURITY_STATUS ntlm_read_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
|
|
{
|
|
SECURITY_STATUS status = SEC_E_INVALID_TOKEN;
|
|
wStream* s;
|
|
size_t length;
|
|
size_t StartOffset;
|
|
size_t PayloadOffset;
|
|
NTLM_AV_PAIR* AvTimestamp;
|
|
NTLM_CHALLENGE_MESSAGE* message;
|
|
if (!context || !buffer)
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
ntlm_generate_client_challenge(context);
|
|
message = &context->CHALLENGE_MESSAGE;
|
|
ZeroMemory(message, sizeof(NTLM_CHALLENGE_MESSAGE));
|
|
s = Stream_New((BYTE*)buffer->pvBuffer, buffer->cbBuffer);
|
|
|
|
if (!s)
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
StartOffset = Stream_GetPosition(s);
|
|
|
|
if (ntlm_read_message_header(s, (NTLM_MESSAGE_HEADER*)message) < 0)
|
|
goto fail;
|
|
|
|
if (message->MessageType != MESSAGE_TYPE_CHALLENGE)
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields(s, &(message->TargetName)) < 0) /* TargetNameFields (8 bytes) */
|
|
goto fail;
|
|
|
|
if (Stream_GetRemainingLength(s) < 4)
|
|
goto fail;
|
|
|
|
Stream_Read_UINT32(s, message->NegotiateFlags); /* NegotiateFlags (4 bytes) */
|
|
context->NegotiateFlags = message->NegotiateFlags;
|
|
|
|
if (Stream_GetRemainingLength(s) < 8)
|
|
goto fail;
|
|
|
|
Stream_Read(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */
|
|
CopyMemory(context->ServerChallenge, message->ServerChallenge, 8);
|
|
|
|
if (Stream_GetRemainingLength(s) < 8)
|
|
goto fail;
|
|
|
|
Stream_Read(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */
|
|
|
|
if (ntlm_read_message_fields(s, &(message->TargetInfo)) < 0) /* TargetInfoFields (8 bytes) */
|
|
goto fail;
|
|
|
|
if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
{
|
|
if (ntlm_read_version_info(s, &(message->Version)) < 0) /* Version (8 bytes) */
|
|
goto fail;
|
|
}
|
|
|
|
/* Payload (variable) */
|
|
PayloadOffset = Stream_GetPosition(s);
|
|
|
|
status = SEC_E_INTERNAL_ERROR;
|
|
if (message->TargetName.Len > 0)
|
|
{
|
|
if (ntlm_read_message_fields_buffer(s, &(message->TargetName)) < 0)
|
|
goto fail;
|
|
}
|
|
|
|
if (message->TargetInfo.Len > 0)
|
|
{
|
|
size_t cbAvTimestamp;
|
|
|
|
if (ntlm_read_message_fields_buffer(s, &(message->TargetInfo)) < 0)
|
|
goto fail;
|
|
|
|
context->ChallengeTargetInfo.pvBuffer = message->TargetInfo.Buffer;
|
|
context->ChallengeTargetInfo.cbBuffer = message->TargetInfo.Len;
|
|
AvTimestamp = ntlm_av_pair_get((NTLM_AV_PAIR*)message->TargetInfo.Buffer,
|
|
message->TargetInfo.Len, MsvAvTimestamp, &cbAvTimestamp);
|
|
|
|
if (AvTimestamp)
|
|
{
|
|
PBYTE ptr = ntlm_av_pair_get_value_pointer(AvTimestamp);
|
|
|
|
if (!ptr)
|
|
goto fail;
|
|
|
|
if (context->NTLMv2)
|
|
context->UseMIC = TRUE;
|
|
|
|
CopyMemory(context->ChallengeTimestamp, ptr, 8);
|
|
}
|
|
}
|
|
|
|
length = (PayloadOffset - StartOffset) + message->TargetName.Len + message->TargetInfo.Len;
|
|
if (length > buffer->cbBuffer)
|
|
goto fail;
|
|
|
|
if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length))
|
|
goto fail;
|
|
|
|
if (context->ChallengeMessage.pvBuffer)
|
|
CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s) + StartOffset, length);
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "CHALLENGE_MESSAGE (length = %d)", length);
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ChallengeMessage.pvBuffer,
|
|
context->ChallengeMessage.cbBuffer);
|
|
ntlm_print_negotiate_flags(context->NegotiateFlags);
|
|
|
|
if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_print_version_info(&(message->Version));
|
|
|
|
ntlm_print_message_fields(&(message->TargetName), "TargetName");
|
|
ntlm_print_message_fields(&(message->TargetInfo), "TargetInfo");
|
|
|
|
if (context->ChallengeTargetInfo.cbBuffer > 0)
|
|
{
|
|
WLog_VRB(TAG, "ChallengeTargetInfo (%" PRIu32 "):", context->ChallengeTargetInfo.cbBuffer);
|
|
ntlm_print_av_pair_list(context->ChallengeTargetInfo.pvBuffer,
|
|
context->ChallengeTargetInfo.cbBuffer);
|
|
}
|
|
|
|
#endif
|
|
/* AV_PAIRs */
|
|
|
|
if (context->NTLMv2)
|
|
{
|
|
if (ntlm_construct_authenticate_target_info(context) < 0)
|
|
goto fail;
|
|
|
|
sspi_SecBufferFree(&context->ChallengeTargetInfo);
|
|
context->ChallengeTargetInfo.pvBuffer = context->AuthenticateTargetInfo.pvBuffer;
|
|
context->ChallengeTargetInfo.cbBuffer = context->AuthenticateTargetInfo.cbBuffer;
|
|
}
|
|
|
|
ntlm_generate_timestamp(context); /* Timestamp */
|
|
|
|
if (ntlm_compute_lm_v2_response(context) < 0) /* LmChallengeResponse */
|
|
goto fail;
|
|
|
|
if (ntlm_compute_ntlm_v2_response(context) < 0) /* NtChallengeResponse */
|
|
goto fail;
|
|
|
|
ntlm_generate_key_exchange_key(context); /* KeyExchangeKey */
|
|
ntlm_generate_random_session_key(context); /* RandomSessionKey */
|
|
ntlm_generate_exported_session_key(context); /* ExportedSessionKey */
|
|
ntlm_encrypt_random_session_key(context); /* EncryptedRandomSessionKey */
|
|
/* Generate signing keys */
|
|
ntlm_generate_client_signing_key(context);
|
|
ntlm_generate_server_signing_key(context);
|
|
/* Generate sealing keys */
|
|
ntlm_generate_client_sealing_key(context);
|
|
ntlm_generate_server_sealing_key(context);
|
|
/* Initialize RC4 seal state using client sealing key */
|
|
ntlm_init_rc4_seal_states(context);
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "ClientChallenge");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ClientChallenge, 8);
|
|
WLog_VRB(TAG, "ServerChallenge");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ServerChallenge, 8);
|
|
WLog_VRB(TAG, "SessionBaseKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->SessionBaseKey, 16);
|
|
WLog_VRB(TAG, "KeyExchangeKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->KeyExchangeKey, 16);
|
|
WLog_VRB(TAG, "ExportedSessionKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ExportedSessionKey, 16);
|
|
WLog_VRB(TAG, "RandomSessionKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->RandomSessionKey, 16);
|
|
WLog_VRB(TAG, "ClientSigningKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ClientSigningKey, 16);
|
|
WLog_VRB(TAG, "ClientSealingKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ClientSealingKey, 16);
|
|
WLog_VRB(TAG, "ServerSigningKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ServerSigningKey, 16);
|
|
WLog_VRB(TAG, "ServerSealingKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ServerSealingKey, 16);
|
|
WLog_VRB(TAG, "Timestamp");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->Timestamp, 8);
|
|
#endif
|
|
context->state = NTLM_STATE_AUTHENTICATE;
|
|
status = SEC_I_CONTINUE_NEEDED;
|
|
fail:
|
|
ntlm_free_message_fields_buffer(&(message->TargetName));
|
|
Stream_Free(s, FALSE);
|
|
return status;
|
|
}
|
|
|
|
SECURITY_STATUS ntlm_write_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
|
|
{
|
|
wStream* s;
|
|
size_t length;
|
|
UINT32 PayloadOffset;
|
|
NTLM_CHALLENGE_MESSAGE* message;
|
|
message = &context->CHALLENGE_MESSAGE;
|
|
ZeroMemory(message, sizeof(NTLM_CHALLENGE_MESSAGE));
|
|
s = Stream_New((BYTE*)buffer->pvBuffer, buffer->cbBuffer);
|
|
|
|
if (!s)
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
ntlm_get_version_info(&(message->Version)); /* Version */
|
|
ntlm_generate_server_challenge(context); /* Server Challenge */
|
|
ntlm_generate_timestamp(context); /* Timestamp */
|
|
|
|
if (ntlm_construct_challenge_target_info(context) < 0) /* TargetInfo */
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
CopyMemory(message->ServerChallenge, context->ServerChallenge, 8); /* ServerChallenge */
|
|
message->NegotiateFlags = context->NegotiateFlags;
|
|
ntlm_populate_message_header((NTLM_MESSAGE_HEADER*)message, MESSAGE_TYPE_CHALLENGE);
|
|
/* Message Header (12 bytes) */
|
|
ntlm_write_message_header(s, (NTLM_MESSAGE_HEADER*)message);
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET)
|
|
{
|
|
message->TargetName.Len = (UINT16)context->TargetName.cbBuffer;
|
|
message->TargetName.Buffer = (PBYTE)context->TargetName.pvBuffer;
|
|
}
|
|
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO;
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO)
|
|
{
|
|
message->TargetInfo.Len = (UINT16)context->ChallengeTargetInfo.cbBuffer;
|
|
message->TargetInfo.Buffer = (PBYTE)context->ChallengeTargetInfo.pvBuffer;
|
|
}
|
|
|
|
PayloadOffset = 48;
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
PayloadOffset += 8;
|
|
|
|
message->TargetName.BufferOffset = PayloadOffset;
|
|
message->TargetInfo.BufferOffset = message->TargetName.BufferOffset + message->TargetName.Len;
|
|
/* TargetNameFields (8 bytes) */
|
|
ntlm_write_message_fields(s, &(message->TargetName));
|
|
Stream_Write_UINT32(s, message->NegotiateFlags); /* NegotiateFlags (4 bytes) */
|
|
Stream_Write(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */
|
|
Stream_Write(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */
|
|
/* TargetInfoFields (8 bytes) */
|
|
ntlm_write_message_fields(s, &(message->TargetInfo));
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_write_version_info(s, &(message->Version)); /* Version (8 bytes) */
|
|
|
|
/* Payload (variable) */
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET)
|
|
ntlm_write_message_fields_buffer(s, &(message->TargetName));
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO)
|
|
ntlm_write_message_fields_buffer(s, &(message->TargetInfo));
|
|
|
|
length = Stream_GetPosition(s);
|
|
WINPR_ASSERT(length <= ULONG_MAX);
|
|
buffer->cbBuffer = (ULONG)length;
|
|
|
|
if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length))
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s), length);
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "CHALLENGE_MESSAGE (length = %d)", length);
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ChallengeMessage.pvBuffer,
|
|
context->ChallengeMessage.cbBuffer);
|
|
ntlm_print_negotiate_flags(message->NegotiateFlags);
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_print_version_info(&(message->Version));
|
|
|
|
ntlm_print_message_fields(&(message->TargetName), "TargetName");
|
|
ntlm_print_message_fields(&(message->TargetInfo), "TargetInfo");
|
|
#endif
|
|
context->state = NTLM_STATE_AUTHENTICATE;
|
|
Stream_Free(s, FALSE);
|
|
return SEC_I_CONTINUE_NEEDED;
|
|
}
|
|
|
|
SECURITY_STATUS ntlm_read_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
|
|
{
|
|
SECURITY_STATUS status = SEC_E_INVALID_TOKEN;
|
|
wStream* s;
|
|
size_t length;
|
|
UINT32 flags = 0;
|
|
NTLM_AV_PAIR* AvFlags = NULL;
|
|
size_t PayloadBufferOffset;
|
|
NTLM_AUTHENTICATE_MESSAGE* message;
|
|
SSPI_CREDENTIALS* credentials = context->credentials;
|
|
|
|
message = &context->AUTHENTICATE_MESSAGE;
|
|
ZeroMemory(message, sizeof(NTLM_AUTHENTICATE_MESSAGE));
|
|
s = Stream_New((BYTE*)buffer->pvBuffer, buffer->cbBuffer);
|
|
|
|
if (!s)
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
if (ntlm_read_message_header(s, (NTLM_MESSAGE_HEADER*)message) < 0)
|
|
goto fail;
|
|
|
|
if (message->MessageType != MESSAGE_TYPE_AUTHENTICATE)
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields(s, &(message->LmChallengeResponse)) <
|
|
0) /* LmChallengeResponseFields (8 bytes) */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields(s, &(message->NtChallengeResponse)) <
|
|
0) /* NtChallengeResponseFields (8 bytes) */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields(s, &(message->DomainName)) < 0) /* DomainNameFields (8 bytes) */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields(s, &(message->UserName)) < 0) /* UserNameFields (8 bytes) */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields(s, &(message->Workstation)) < 0) /* WorkstationFields (8 bytes) */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields(s, &(message->EncryptedRandomSessionKey)) <
|
|
0) /* EncryptedRandomSessionKeyFields (8 bytes) */
|
|
goto fail;
|
|
|
|
if (Stream_GetRemainingLength(s) < 4)
|
|
goto fail;
|
|
Stream_Read_UINT32(s, message->NegotiateFlags); /* NegotiateFlags (4 bytes) */
|
|
context->NegotiateKeyExchange =
|
|
(message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) ? TRUE : FALSE;
|
|
|
|
if ((context->NegotiateKeyExchange && !message->EncryptedRandomSessionKey.Len) ||
|
|
(!context->NegotiateKeyExchange && message->EncryptedRandomSessionKey.Len))
|
|
goto fail;
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
{
|
|
if (ntlm_read_version_info(s, &(message->Version)) < 0) /* Version (8 bytes) */
|
|
goto fail;
|
|
}
|
|
|
|
PayloadBufferOffset = Stream_GetPosition(s);
|
|
|
|
status = SEC_E_INTERNAL_ERROR;
|
|
if (ntlm_read_message_fields_buffer(s, &(message->DomainName)) < 0) /* DomainName */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields_buffer(s, &(message->UserName)) < 0) /* UserName */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields_buffer(s, &(message->Workstation)) < 0) /* Workstation */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields_buffer(s, &(message->LmChallengeResponse)) <
|
|
0) /* LmChallengeResponse */
|
|
goto fail;
|
|
|
|
if (ntlm_read_message_fields_buffer(s, &(message->NtChallengeResponse)) <
|
|
0) /* NtChallengeResponse */
|
|
goto fail;
|
|
|
|
if (message->NtChallengeResponse.Len > 0)
|
|
{
|
|
int rc;
|
|
size_t cbAvFlags;
|
|
wStream* snt =
|
|
Stream_New(message->NtChallengeResponse.Buffer, message->NtChallengeResponse.Len);
|
|
|
|
if (!snt)
|
|
goto fail;
|
|
|
|
status = SEC_E_INVALID_TOKEN;
|
|
rc = ntlm_read_ntlm_v2_response(snt, &(context->NTLMv2Response));
|
|
Stream_Free(snt, FALSE);
|
|
if (rc < 0)
|
|
goto fail;
|
|
status = SEC_E_INTERNAL_ERROR;
|
|
|
|
context->NtChallengeResponse.pvBuffer = message->NtChallengeResponse.Buffer;
|
|
context->NtChallengeResponse.cbBuffer = message->NtChallengeResponse.Len;
|
|
sspi_SecBufferFree(&(context->ChallengeTargetInfo));
|
|
context->ChallengeTargetInfo.pvBuffer = (void*)context->NTLMv2Response.Challenge.AvPairs;
|
|
context->ChallengeTargetInfo.cbBuffer = message->NtChallengeResponse.Len - (28 + 16);
|
|
CopyMemory(context->ClientChallenge, context->NTLMv2Response.Challenge.ClientChallenge, 8);
|
|
AvFlags =
|
|
ntlm_av_pair_get(context->NTLMv2Response.Challenge.AvPairs,
|
|
context->NTLMv2Response.Challenge.cbAvPairs, MsvAvFlags, &cbAvFlags);
|
|
|
|
if (AvFlags)
|
|
Data_Read_UINT32(ntlm_av_pair_get_value_pointer(AvFlags), flags);
|
|
}
|
|
|
|
if (ntlm_read_message_fields_buffer(s, &(message->EncryptedRandomSessionKey)) <
|
|
0) /* EncryptedRandomSessionKey */
|
|
goto fail;
|
|
|
|
if (message->EncryptedRandomSessionKey.Len > 0)
|
|
{
|
|
if (message->EncryptedRandomSessionKey.Len != 16)
|
|
goto fail;
|
|
|
|
CopyMemory(context->EncryptedRandomSessionKey, message->EncryptedRandomSessionKey.Buffer,
|
|
16);
|
|
}
|
|
|
|
length = Stream_GetPosition(s);
|
|
WINPR_ASSERT(length <= ULONG_MAX);
|
|
|
|
if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length))
|
|
goto fail;
|
|
|
|
CopyMemory(context->AuthenticateMessage.pvBuffer, Stream_Buffer(s), length);
|
|
buffer->cbBuffer = (ULONG)length;
|
|
Stream_SetPosition(s, PayloadBufferOffset);
|
|
|
|
if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
|
|
{
|
|
context->MessageIntegrityCheckOffset = (UINT32)Stream_GetPosition(s);
|
|
|
|
status = SEC_E_INVALID_TOKEN;
|
|
if (Stream_GetRemainingLength(s) < 16)
|
|
goto fail;
|
|
|
|
Stream_Read(s, message->MessageIntegrityCheck, 16);
|
|
}
|
|
|
|
status = SEC_E_INTERNAL_ERROR;
|
|
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "AUTHENTICATE_MESSAGE (length = %" PRIu32 ")",
|
|
context->AuthenticateMessage.cbBuffer);
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->AuthenticateMessage.pvBuffer,
|
|
context->AuthenticateMessage.cbBuffer);
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_print_version_info(&(message->Version));
|
|
|
|
ntlm_print_message_fields(&(message->DomainName), "DomainName");
|
|
ntlm_print_message_fields(&(message->UserName), "UserName");
|
|
ntlm_print_message_fields(&(message->Workstation), "Workstation");
|
|
ntlm_print_message_fields(&(message->LmChallengeResponse), "LmChallengeResponse");
|
|
ntlm_print_message_fields(&(message->NtChallengeResponse), "NtChallengeResponse");
|
|
ntlm_print_message_fields(&(message->EncryptedRandomSessionKey), "EncryptedRandomSessionKey");
|
|
ntlm_print_av_pair_list(context->NTLMv2Response.Challenge.AvPairs,
|
|
context->NTLMv2Response.Challenge.cbAvPairs);
|
|
|
|
if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
|
|
{
|
|
WLog_VRB(TAG, "MessageIntegrityCheck:");
|
|
winpr_HexDump(TAG, WLOG_TRACE, message->MessageIntegrityCheck, 16);
|
|
}
|
|
|
|
#endif
|
|
|
|
if (message->UserName.Len > 0)
|
|
{
|
|
credentials->identity.User = (UINT16*)malloc(message->UserName.Len);
|
|
|
|
if (!credentials->identity.User)
|
|
goto fail;
|
|
|
|
CopyMemory(credentials->identity.User, message->UserName.Buffer, message->UserName.Len);
|
|
credentials->identity.UserLength = message->UserName.Len / 2;
|
|
}
|
|
|
|
if (message->DomainName.Len > 0)
|
|
{
|
|
credentials->identity.Domain = (UINT16*)malloc(message->DomainName.Len);
|
|
|
|
if (!credentials->identity.Domain)
|
|
goto fail;
|
|
|
|
CopyMemory(credentials->identity.Domain, message->DomainName.Buffer,
|
|
message->DomainName.Len);
|
|
credentials->identity.DomainLength = message->DomainName.Len / 2;
|
|
}
|
|
|
|
Stream_Free(s, FALSE);
|
|
/* Computations beyond this point require the NTLM hash of the password */
|
|
context->state = NTLM_STATE_COMPLETION;
|
|
return SEC_I_COMPLETE_NEEDED;
|
|
|
|
fail:
|
|
Stream_Free(s, FALSE);
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Send NTLMSSP AUTHENTICATE_MESSAGE.\n
|
|
* AUTHENTICATE_MESSAGE @msdn{cc236643}
|
|
* @param NTLM context
|
|
* @param buffer
|
|
*/
|
|
|
|
SECURITY_STATUS ntlm_write_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer)
|
|
{
|
|
wStream* s;
|
|
size_t length;
|
|
UINT32 PayloadBufferOffset;
|
|
NTLM_AUTHENTICATE_MESSAGE* message;
|
|
SSPI_CREDENTIALS* credentials = context->credentials;
|
|
message = &context->AUTHENTICATE_MESSAGE;
|
|
ZeroMemory(message, sizeof(NTLM_AUTHENTICATE_MESSAGE));
|
|
s = Stream_New((BYTE*)buffer->pvBuffer, buffer->cbBuffer);
|
|
|
|
if (!s)
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
if (context->NTLMv2)
|
|
{
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_56;
|
|
|
|
if (context->SendVersionInfo)
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION;
|
|
}
|
|
|
|
if (context->UseMIC)
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO;
|
|
|
|
if (context->SendWorkstationName)
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED;
|
|
|
|
if (context->confidentiality)
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;
|
|
|
|
if (context->CHALLENGE_MESSAGE.NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
|
|
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_128;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN;
|
|
message->NegotiateFlags |= NTLMSSP_REQUEST_TARGET;
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_get_version_info(&(message->Version));
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
|
|
{
|
|
message->Workstation.Len = context->Workstation.Length;
|
|
message->Workstation.Buffer = (BYTE*)context->Workstation.Buffer;
|
|
}
|
|
|
|
if (credentials->identity.DomainLength > 0)
|
|
{
|
|
message->NegotiateFlags |= NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED;
|
|
message->DomainName.Len = (UINT16)credentials->identity.DomainLength * 2;
|
|
message->DomainName.Buffer = (BYTE*)credentials->identity.Domain;
|
|
}
|
|
|
|
message->UserName.Len = (UINT16)credentials->identity.UserLength * 2;
|
|
message->UserName.Buffer = (BYTE*)credentials->identity.User;
|
|
message->LmChallengeResponse.Len = (UINT16)context->LmChallengeResponse.cbBuffer;
|
|
message->LmChallengeResponse.Buffer = (BYTE*)context->LmChallengeResponse.pvBuffer;
|
|
message->NtChallengeResponse.Len = (UINT16)context->NtChallengeResponse.cbBuffer;
|
|
message->NtChallengeResponse.Buffer = (BYTE*)context->NtChallengeResponse.pvBuffer;
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
|
|
{
|
|
message->EncryptedRandomSessionKey.Len = 16;
|
|
message->EncryptedRandomSessionKey.Buffer = context->EncryptedRandomSessionKey;
|
|
}
|
|
|
|
PayloadBufferOffset = 64;
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
PayloadBufferOffset += 8; /* Version (8 bytes) */
|
|
|
|
if (context->UseMIC)
|
|
PayloadBufferOffset += 16; /* Message Integrity Check (16 bytes) */
|
|
|
|
message->DomainName.BufferOffset = PayloadBufferOffset;
|
|
message->UserName.BufferOffset = message->DomainName.BufferOffset + message->DomainName.Len;
|
|
message->Workstation.BufferOffset = message->UserName.BufferOffset + message->UserName.Len;
|
|
message->LmChallengeResponse.BufferOffset =
|
|
message->Workstation.BufferOffset + message->Workstation.Len;
|
|
message->NtChallengeResponse.BufferOffset =
|
|
message->LmChallengeResponse.BufferOffset + message->LmChallengeResponse.Len;
|
|
message->EncryptedRandomSessionKey.BufferOffset =
|
|
message->NtChallengeResponse.BufferOffset + message->NtChallengeResponse.Len;
|
|
ntlm_populate_message_header((NTLM_MESSAGE_HEADER*)message, MESSAGE_TYPE_AUTHENTICATE);
|
|
ntlm_write_message_header(s, (NTLM_MESSAGE_HEADER*)message); /* Message Header (12 bytes) */
|
|
ntlm_write_message_fields(
|
|
s, &(message->LmChallengeResponse)); /* LmChallengeResponseFields (8 bytes) */
|
|
ntlm_write_message_fields(
|
|
s, &(message->NtChallengeResponse)); /* NtChallengeResponseFields (8 bytes) */
|
|
ntlm_write_message_fields(s, &(message->DomainName)); /* DomainNameFields (8 bytes) */
|
|
ntlm_write_message_fields(s, &(message->UserName)); /* UserNameFields (8 bytes) */
|
|
ntlm_write_message_fields(s, &(message->Workstation)); /* WorkstationFields (8 bytes) */
|
|
ntlm_write_message_fields(
|
|
s, &(message->EncryptedRandomSessionKey)); /* EncryptedRandomSessionKeyFields (8 bytes) */
|
|
Stream_Write_UINT32(s, message->NegotiateFlags); /* NegotiateFlags (4 bytes) */
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_write_version_info(s, &(message->Version)); /* Version (8 bytes) */
|
|
|
|
if (context->UseMIC)
|
|
{
|
|
context->MessageIntegrityCheckOffset = (UINT32)Stream_GetPosition(s);
|
|
Stream_Zero(s, 16); /* Message Integrity Check (16 bytes) */
|
|
}
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED)
|
|
ntlm_write_message_fields_buffer(s, &(message->DomainName)); /* DomainName */
|
|
|
|
ntlm_write_message_fields_buffer(s, &(message->UserName)); /* UserName */
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
|
|
ntlm_write_message_fields_buffer(s, &(message->Workstation)); /* Workstation */
|
|
|
|
ntlm_write_message_fields_buffer(s, &(message->LmChallengeResponse)); /* LmChallengeResponse */
|
|
ntlm_write_message_fields_buffer(s, &(message->NtChallengeResponse)); /* NtChallengeResponse */
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
|
|
ntlm_write_message_fields_buffer(
|
|
s, &(message->EncryptedRandomSessionKey)); /* EncryptedRandomSessionKey */
|
|
|
|
length = Stream_GetPosition(s);
|
|
WINPR_ASSERT(length <= ULONG_MAX);
|
|
|
|
if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length))
|
|
{
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_INTERNAL_ERROR;
|
|
}
|
|
|
|
CopyMemory(context->AuthenticateMessage.pvBuffer, Stream_Buffer(s), length);
|
|
buffer->cbBuffer = (ULONG)length;
|
|
|
|
if (context->UseMIC)
|
|
{
|
|
/* Message Integrity Check */
|
|
ntlm_compute_message_integrity_check(context, message->MessageIntegrityCheck, 16);
|
|
Stream_SetPosition(s, context->MessageIntegrityCheckOffset);
|
|
Stream_Write(s, message->MessageIntegrityCheck, 16);
|
|
Stream_SetPosition(s, length);
|
|
}
|
|
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "AUTHENTICATE_MESSAGE (length = %d)", length);
|
|
winpr_HexDump(TAG, WLOG_TRACE, Stream_Buffer(s), length);
|
|
ntlm_print_negotiate_flags(message->NegotiateFlags);
|
|
|
|
if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION)
|
|
ntlm_print_version_info(&(message->Version));
|
|
|
|
if (context->AuthenticateTargetInfo.cbBuffer > 0)
|
|
{
|
|
WLog_VRB(TAG,
|
|
"AuthenticateTargetInfo (%" PRIu32 "):", context->AuthenticateTargetInfo.cbBuffer);
|
|
ntlm_print_av_pair_list(context->AuthenticateTargetInfo.pvBuffer,
|
|
context->AuthenticateTargetInfo.cbBuffer);
|
|
}
|
|
|
|
ntlm_print_message_fields(&(message->DomainName), "DomainName");
|
|
ntlm_print_message_fields(&(message->UserName), "UserName");
|
|
ntlm_print_message_fields(&(message->Workstation), "Workstation");
|
|
ntlm_print_message_fields(&(message->LmChallengeResponse), "LmChallengeResponse");
|
|
ntlm_print_message_fields(&(message->NtChallengeResponse), "NtChallengeResponse");
|
|
ntlm_print_message_fields(&(message->EncryptedRandomSessionKey), "EncryptedRandomSessionKey");
|
|
|
|
if (context->UseMIC)
|
|
{
|
|
WLog_VRB(TAG, "MessageIntegrityCheck (length = 16)");
|
|
winpr_HexDump(TAG, WLOG_TRACE, message->MessageIntegrityCheck, 16);
|
|
}
|
|
|
|
#endif
|
|
context->state = NTLM_STATE_FINAL;
|
|
Stream_Free(s, FALSE);
|
|
return SEC_E_OK;
|
|
}
|
|
|
|
SECURITY_STATUS ntlm_server_AuthenticateComplete(NTLM_CONTEXT* context)
|
|
{
|
|
UINT32 flags = 0;
|
|
size_t cbAvFlags;
|
|
NTLM_AV_PAIR* AvFlags = NULL;
|
|
NTLM_AUTHENTICATE_MESSAGE* message;
|
|
BYTE messageIntegrityCheck[16];
|
|
|
|
if (!context)
|
|
return SEC_E_INVALID_PARAMETER;
|
|
|
|
if (context->state != NTLM_STATE_COMPLETION)
|
|
return SEC_E_OUT_OF_SEQUENCE;
|
|
|
|
message = &context->AUTHENTICATE_MESSAGE;
|
|
AvFlags = ntlm_av_pair_get(context->NTLMv2Response.Challenge.AvPairs,
|
|
context->NTLMv2Response.Challenge.cbAvPairs, MsvAvFlags, &cbAvFlags);
|
|
|
|
if (AvFlags)
|
|
Data_Read_UINT32(ntlm_av_pair_get_value_pointer(AvFlags), flags);
|
|
|
|
if (ntlm_compute_lm_v2_response(context) < 0) /* LmChallengeResponse */
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
if (ntlm_compute_ntlm_v2_response(context) < 0) /* NtChallengeResponse */
|
|
return SEC_E_INTERNAL_ERROR;
|
|
|
|
/* KeyExchangeKey */
|
|
ntlm_generate_key_exchange_key(context);
|
|
/* EncryptedRandomSessionKey */
|
|
ntlm_decrypt_random_session_key(context);
|
|
/* ExportedSessionKey */
|
|
ntlm_generate_exported_session_key(context);
|
|
|
|
if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK)
|
|
{
|
|
ZeroMemory(
|
|
&((PBYTE)context->AuthenticateMessage.pvBuffer)[context->MessageIntegrityCheckOffset],
|
|
16);
|
|
ntlm_compute_message_integrity_check(context, messageIntegrityCheck,
|
|
sizeof(messageIntegrityCheck));
|
|
CopyMemory(
|
|
&((PBYTE)context->AuthenticateMessage.pvBuffer)[context->MessageIntegrityCheckOffset],
|
|
message->MessageIntegrityCheck, 16);
|
|
|
|
if (memcmp(messageIntegrityCheck, message->MessageIntegrityCheck, 16) != 0)
|
|
{
|
|
WLog_ERR(TAG, "Message Integrity Check (MIC) verification failed!");
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_ERR(TAG, "Expected MIC:");
|
|
winpr_HexDump(TAG, WLOG_ERROR, messageIntegrityCheck, sizeof(messageIntegrityCheck));
|
|
WLog_ERR(TAG, "Actual MIC:");
|
|
winpr_HexDump(TAG, WLOG_ERROR, message->MessageIntegrityCheck,
|
|
sizeof(message->MessageIntegrityCheck));
|
|
#endif
|
|
return SEC_E_MESSAGE_ALTERED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* no mic message was present
|
|
|
|
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/f9e6fbc4-a953-4f24-b229-ccdcc213b9ec
|
|
the mic is optional, as not supported in Windows NT, Windows 2000, Windows XP, and
|
|
Windows Server 2003 and, as it seems, in the NTLMv2 implementation of Qt5.
|
|
|
|
now check the NtProofString, to detect if the entered client password matches the
|
|
expected password.
|
|
*/
|
|
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "No MIC present, using NtProofString for verification.");
|
|
#endif
|
|
|
|
if (memcmp(context->NTLMv2Response.Response, context->NtProofString, 16) != 0)
|
|
{
|
|
WLog_ERR(TAG, "NtProofString verification failed!");
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_ERR(TAG, "Expected NtProofString:");
|
|
winpr_HexDump(TAG, WLOG_ERROR, context->NtProofString, sizeof(context->NtProofString));
|
|
WLog_ERR(TAG, "Actual NtProofString:");
|
|
winpr_HexDump(TAG, WLOG_ERROR, context->NTLMv2Response.Response,
|
|
sizeof(context->NTLMv2Response));
|
|
#endif
|
|
return SEC_E_LOGON_DENIED;
|
|
}
|
|
}
|
|
|
|
/* Generate signing keys */
|
|
ntlm_generate_client_signing_key(context);
|
|
ntlm_generate_server_signing_key(context);
|
|
/* Generate sealing keys */
|
|
ntlm_generate_client_sealing_key(context);
|
|
ntlm_generate_server_sealing_key(context);
|
|
/* Initialize RC4 seal state */
|
|
ntlm_init_rc4_seal_states(context);
|
|
#ifdef WITH_DEBUG_NTLM
|
|
WLog_VRB(TAG, "ClientChallenge");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ClientChallenge, 8);
|
|
WLog_VRB(TAG, "ServerChallenge");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ServerChallenge, 8);
|
|
WLog_VRB(TAG, "SessionBaseKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->SessionBaseKey, 16);
|
|
WLog_VRB(TAG, "KeyExchangeKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->KeyExchangeKey, 16);
|
|
WLog_VRB(TAG, "ExportedSessionKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ExportedSessionKey, 16);
|
|
WLog_VRB(TAG, "RandomSessionKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->RandomSessionKey, 16);
|
|
WLog_VRB(TAG, "ClientSigningKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ClientSigningKey, 16);
|
|
WLog_VRB(TAG, "ClientSealingKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ClientSealingKey, 16);
|
|
WLog_VRB(TAG, "ServerSigningKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ServerSigningKey, 16);
|
|
WLog_VRB(TAG, "ServerSealingKey");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->ServerSealingKey, 16);
|
|
WLog_VRB(TAG, "Timestamp");
|
|
winpr_HexDump(TAG, WLOG_TRACE, context->Timestamp, 8);
|
|
#endif
|
|
context->state = NTLM_STATE_FINAL;
|
|
ntlm_free_message_fields_buffer(&(message->DomainName));
|
|
ntlm_free_message_fields_buffer(&(message->UserName));
|
|
ntlm_free_message_fields_buffer(&(message->Workstation));
|
|
ntlm_free_message_fields_buffer(&(message->LmChallengeResponse));
|
|
ntlm_free_message_fields_buffer(&(message->NtChallengeResponse));
|
|
ntlm_free_message_fields_buffer(&(message->EncryptedRandomSessionKey));
|
|
return SEC_E_OK;
|
|
}
|