diff options
Diffstat (limited to 'winpr/libwinpr/sspi/NTLM')
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm.c | 1526 | ||||
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm.h | 301 | ||||
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c | 775 | ||||
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h | 42 | ||||
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm_compute.c | 887 | ||||
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm_compute.h | 64 | ||||
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm_export.h | 40 | ||||
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm_message.c | 1397 | ||||
-rw-r--r-- | winpr/libwinpr/sspi/NTLM/ntlm_message.h | 36 |
9 files changed, 5068 insertions, 0 deletions
diff --git a/winpr/libwinpr/sspi/NTLM/ntlm.c b/winpr/libwinpr/sspi/NTLM/ntlm.c new file mode 100644 index 0000000..6a2ee6a --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm.c @@ -0,0 +1,1526 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package + * + * 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. + */ + +#include <winpr/config.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/sspi.h> +#include <winpr/print.h> +#include <winpr/string.h> +#include <winpr/tchar.h> +#include <winpr/sysinfo.h> +#include <winpr/registry.h> +#include <winpr/endian.h> +#include <winpr/build-config.h> + +#include "ntlm.h" +#include "ntlm_export.h" +#include "../sspi.h" + +#include "ntlm_message.h" + +#include "../../log.h" +#define TAG WINPR_TAG("sspi.NTLM") + +#define WINPR_KEY "Software\\" WINPR_VENDOR_STRING "\\" WINPR_PRODUCT_STRING "\\WinPR\\NTLM" + +static char* NTLM_PACKAGE_NAME = "NTLM"; + +#define check_context(ctx) check_context_((ctx), __FILE__, __func__, __LINE__) +static BOOL check_context_(NTLM_CONTEXT* context, const char* file, const char* fkt, size_t line) +{ + BOOL rc = TRUE; + wLog* log = WLog_Get(TAG); + const DWORD log_level = WLOG_ERROR; + + if (!context) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context"); + + return FALSE; + } + + if (!context->RecvRc4Seal) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->RecvRc4Seal"); + rc = FALSE; + } + if (!context->SendRc4Seal) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->SendRc4Seal"); + rc = FALSE; + } + + if (!context->SendSigningKey) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->SendSigningKey"); + rc = FALSE; + } + if (!context->RecvSigningKey) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->RecvSigningKey"); + rc = FALSE; + } + if (!context->SendSealingKey) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->SendSealingKey"); + rc = FALSE; + } + if (!context->RecvSealingKey) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->RecvSealingKey"); + rc = FALSE; + } + return rc; +} + +static int ntlm_SetContextWorkstation(NTLM_CONTEXT* context, char* Workstation) +{ + char* ws = Workstation; + DWORD nSize = 0; + CHAR* computerName = NULL; + + WINPR_ASSERT(context); + + if (!Workstation) + { + if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) || + GetLastError() != ERROR_MORE_DATA) + return -1; + + computerName = calloc(nSize, sizeof(CHAR)); + + if (!computerName) + return -1; + + if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize)) + { + free(computerName); + return -1; + } + + if (nSize > MAX_COMPUTERNAME_LENGTH) + computerName[MAX_COMPUTERNAME_LENGTH] = '\0'; + + ws = computerName; + + if (!ws) + return -1; + } + + size_t len = 0; + context->Workstation.Buffer = ConvertUtf8ToWCharAlloc(ws, &len); + + if (!Workstation) + free(ws); + + if (!context->Workstation.Buffer || (len > UINT16_MAX / sizeof(WCHAR))) + return -1; + + context->Workstation.Length = (USHORT)(len * sizeof(WCHAR)); + return 1; +} + +static int ntlm_SetContextServicePrincipalNameW(NTLM_CONTEXT* context, LPWSTR ServicePrincipalName) +{ + WINPR_ASSERT(context); + + if (!ServicePrincipalName) + { + context->ServicePrincipalName.Buffer = NULL; + context->ServicePrincipalName.Length = 0; + return 1; + } + + context->ServicePrincipalName.Length = (USHORT)(_wcslen(ServicePrincipalName) * 2); + context->ServicePrincipalName.Buffer = (PWSTR)malloc(context->ServicePrincipalName.Length + 2); + + if (!context->ServicePrincipalName.Buffer) + return -1; + + memcpy(context->ServicePrincipalName.Buffer, ServicePrincipalName, + context->ServicePrincipalName.Length + 2); + return 1; +} + +static int ntlm_SetContextTargetName(NTLM_CONTEXT* context, char* TargetName) +{ + char* name = TargetName; + DWORD nSize = 0; + CHAR* computerName = NULL; + + WINPR_ASSERT(context); + + if (!name) + { + if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) || + GetLastError() != ERROR_MORE_DATA) + return -1; + + computerName = calloc(nSize, sizeof(CHAR)); + + if (!computerName) + return -1; + + if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize)) + { + free(computerName); + return -1; + } + + if (nSize > MAX_COMPUTERNAME_LENGTH) + computerName[MAX_COMPUTERNAME_LENGTH] = '\0'; + + name = computerName; + + if (!name) + return -1; + + CharUpperA(name); + } + + size_t len = 0; + context->TargetName.pvBuffer = ConvertUtf8ToWCharAlloc(name, &len); + + if (!context->TargetName.pvBuffer || (len > UINT16_MAX / sizeof(WCHAR))) + { + free(context->TargetName.pvBuffer); + context->TargetName.pvBuffer = NULL; + + if (!TargetName) + free(name); + + return -1; + } + + context->TargetName.cbBuffer = (USHORT)(len * sizeof(WCHAR)); + + if (!TargetName) + free(name); + + return 1; +} + +static NTLM_CONTEXT* ntlm_ContextNew(void) +{ + HKEY hKey = 0; + LONG status = 0; + DWORD dwType = 0; + DWORD dwSize = 0; + DWORD dwValue = 0; + NTLM_CONTEXT* context = (NTLM_CONTEXT*)calloc(1, sizeof(NTLM_CONTEXT)); + + if (!context) + return NULL; + + context->NTLMv2 = TRUE; + context->UseMIC = FALSE; + context->SendVersionInfo = TRUE; + context->SendSingleHostData = FALSE; + context->SendWorkstationName = TRUE; + context->NegotiateKeyExchange = TRUE; + context->UseSamFileDatabase = TRUE; + status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, WINPR_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("NTLMv2"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + context->NTLMv2 = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("UseMIC"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + context->UseMIC = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendVersionInfo"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + context->SendVersionInfo = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendSingleHostData"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SendSingleHostData = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendWorkstationName"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SendWorkstationName = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("WorkstationName"), NULL, &dwType, NULL, &dwSize) == + ERROR_SUCCESS) + { + char* workstation = (char*)malloc(dwSize + 1); + + if (!workstation) + { + free(context); + return NULL; + } + + status = RegQueryValueExA(hKey, "WorkstationName", NULL, &dwType, (BYTE*)workstation, + &dwSize); + if (status != ERROR_SUCCESS) + WLog_WARN(TAG, "Key ''WorkstationName' not found"); + workstation[dwSize] = '\0'; + + if (ntlm_SetContextWorkstation(context, workstation) < 0) + { + free(workstation); + free(context); + return NULL; + } + + free(workstation); + } + + RegCloseKey(hKey); + } + + /* + * Extended Protection is enabled by default in Windows 7, + * but enabling it in WinPR breaks TS Gateway at this point + */ + context->SuppressExtendedProtection = FALSE; + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Control\\LSA"), 0, + KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("SuppressExtendedProtection"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SuppressExtendedProtection = dwValue ? 1 : 0; + + RegCloseKey(hKey); + } + + context->NegotiateFlags = 0; + context->LmCompatibilityLevel = 3; + ntlm_change_state(context, NTLM_STATE_INITIAL); + FillMemory(context->MachineID, sizeof(context->MachineID), 0xAA); + + if (context->NTLMv2) + context->UseMIC = TRUE; + + return context; +} + +static void ntlm_ContextFree(NTLM_CONTEXT* context) +{ + if (!context) + return; + + winpr_RC4_Free(context->SendRc4Seal); + winpr_RC4_Free(context->RecvRc4Seal); + sspi_SecBufferFree(&context->NegotiateMessage); + sspi_SecBufferFree(&context->ChallengeMessage); + sspi_SecBufferFree(&context->AuthenticateMessage); + sspi_SecBufferFree(&context->ChallengeTargetInfo); + sspi_SecBufferFree(&context->TargetName); + sspi_SecBufferFree(&context->NtChallengeResponse); + sspi_SecBufferFree(&context->LmChallengeResponse); + free(context->ServicePrincipalName.Buffer); + free(context->Workstation.Buffer); + free(context); +} + +static SECURITY_STATUS SEC_ENTRY ntlm_AcquireCredentialsHandleW( + SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SEC_WINPR_NTLM_SETTINGS* settings = NULL; + + if ((fCredentialUse != SECPKG_CRED_OUTBOUND) && (fCredentialUse != SECPKG_CRED_INBOUND) && + (fCredentialUse != SECPKG_CRED_BOTH)) + { + return SEC_E_INVALID_PARAMETER; + } + + SSPI_CREDENTIALS* credentials = sspi_CredentialsNew(); + + if (!credentials) + return SEC_E_INTERNAL_ERROR; + + credentials->fCredentialUse = fCredentialUse; + credentials->pGetKeyFn = pGetKeyFn; + credentials->pvGetKeyArgument = pvGetKeyArgument; + + if (pAuthData) + { + UINT32 identityFlags = sspi_GetAuthIdentityFlags(pAuthData); + + sspi_CopyAuthIdentity(&(credentials->identity), + (const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData); + + if (identityFlags & SEC_WINNT_AUTH_IDENTITY_EXTENDED) + settings = (((SEC_WINNT_AUTH_IDENTITY_WINPR*)pAuthData)->ntlmSettings); + } + + if (settings) + { + if (settings->samFile) + { + credentials->ntlmSettings.samFile = _strdup(settings->samFile); + if (!credentials->ntlmSettings.samFile) + { + sspi_CredentialsFree(credentials); + return SEC_E_INSUFFICIENT_MEMORY; + } + } + credentials->ntlmSettings.hashCallback = settings->hashCallback; + credentials->ntlmSettings.hashCallbackArg = settings->hashCallbackArg; + } + + sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials); + sspi_SecureHandleSetUpperPointer(phCredential, (void*)NTLM_PACKAGE_NAME); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_AcquireCredentialsHandleA( + SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = SEC_E_INSUFFICIENT_MEMORY; + SEC_WCHAR* principal = NULL; + SEC_WCHAR* package = NULL; + + if (pszPrincipal) + { + principal = ConvertUtf8ToWCharAlloc(pszPrincipal, NULL); + if (!principal) + goto fail; + } + if (pszPackage) + { + package = ConvertUtf8ToWCharAlloc(pszPackage, NULL); + if (!package) + goto fail; + } + + status = + ntlm_AcquireCredentialsHandleW(principal, package, fCredentialUse, pvLogonID, pAuthData, + pGetKeyFn, pvGetKeyArgument, phCredential, ptsExpiry); + +fail: + free(principal); + free(package); + + return status; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_FreeCredentialsHandle(PCredHandle phCredential) +{ + if (!phCredential) + return SEC_E_INVALID_HANDLE; + + SSPI_CREDENTIALS* credentials = + (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + + if (!credentials) + return SEC_E_INVALID_HANDLE; + + sspi_CredentialsFree(credentials); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_QueryCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer) +{ + if (ulAttribute == SECPKG_CRED_ATTR_NAMES) + { + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_QueryCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer) +{ + return ntlm_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer); +} + +/** + * @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa374707 + */ +static SECURITY_STATUS SEC_ENTRY +ntlm_AcceptSecurityContext(PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, + ULONG fContextReq, ULONG TargetDataRep, PCtxtHandle phNewContext, + PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsTimeStamp) +{ + SECURITY_STATUS status = 0; + SSPI_CREDENTIALS* credentials = NULL; + PSecBuffer input_buffer = NULL; + PSecBuffer output_buffer = NULL; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + { + context = ntlm_ContextNew(); + + if (!context) + return SEC_E_INSUFFICIENT_MEMORY; + + context->server = TRUE; + + if (fContextReq & ASC_REQ_CONFIDENTIALITY) + context->confidentiality = TRUE; + + credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + context->credentials = credentials; + context->SamFile = credentials->ntlmSettings.samFile; + context->HashCallback = credentials->ntlmSettings.hashCallback; + context->HashCallbackArg = credentials->ntlmSettings.hashCallbackArg; + + ntlm_SetContextTargetName(context, NULL); + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, (void*)NTLM_PACKAGE_NAME); + } + + switch (ntlm_get_state(context)) + { + case NTLM_STATE_INITIAL: + { + ntlm_change_state(context, NTLM_STATE_NEGOTIATE); + + if (!pInput) + return SEC_E_INVALID_TOKEN; + + if (pInput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + + if (!input_buffer) + return SEC_E_INVALID_TOKEN; + + if (input_buffer->cbBuffer < 1) + return SEC_E_INVALID_TOKEN; + + status = ntlm_read_NegotiateMessage(context, input_buffer); + if (status != SEC_I_CONTINUE_NEEDED) + return status; + + if (ntlm_get_state(context) == NTLM_STATE_CHALLENGE) + { + if (!pOutput) + return SEC_E_INVALID_TOKEN; + + if (pOutput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!output_buffer->BufferType) + return SEC_E_INVALID_TOKEN; + + if (output_buffer->cbBuffer < 1) + return SEC_E_INSUFFICIENT_MEMORY; + + return ntlm_write_ChallengeMessage(context, output_buffer); + } + + return SEC_E_OUT_OF_SEQUENCE; + } + + case NTLM_STATE_AUTHENTICATE: + { + if (!pInput) + return SEC_E_INVALID_TOKEN; + + if (pInput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + + if (!input_buffer) + return SEC_E_INVALID_TOKEN; + + if (input_buffer->cbBuffer < 1) + return SEC_E_INVALID_TOKEN; + + status = ntlm_read_AuthenticateMessage(context, input_buffer); + + if (pOutput) + { + for (ULONG i = 0; i < pOutput->cBuffers; i++) + { + pOutput->pBuffers[i].cbBuffer = 0; + pOutput->pBuffers[i].BufferType = SECBUFFER_TOKEN; + } + } + + return status; + } + + default: + return SEC_E_OUT_OF_SEQUENCE; + } +} + +static SECURITY_STATUS SEC_ENTRY ntlm_ImpersonateSecurityContext(PCtxtHandle phContext) +{ + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_InitializeSecurityContextW( + PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + SSPI_CREDENTIALS* credentials = NULL; + PSecBuffer input_buffer = NULL; + PSecBuffer output_buffer = NULL; + PSecBuffer channel_bindings = NULL; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (pInput) + { + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + channel_bindings = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS); + } + + if (!context) + { + context = ntlm_ContextNew(); + + if (!context) + return SEC_E_INSUFFICIENT_MEMORY; + + if (fContextReq & ISC_REQ_CONFIDENTIALITY) + context->confidentiality = TRUE; + + credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + context->credentials = credentials; + + if (context->Workstation.Length < 1) + { + if (ntlm_SetContextWorkstation(context, NULL) < 0) + { + ntlm_ContextFree(context); + return SEC_E_INTERNAL_ERROR; + } + } + + if (ntlm_SetContextServicePrincipalNameW(context, pszTargetName) < 0) + { + ntlm_ContextFree(context); + return SEC_E_INTERNAL_ERROR; + } + + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, NTLM_SSP_NAME); + } + + if ((!input_buffer) || (ntlm_get_state(context) == NTLM_STATE_AUTHENTICATE)) + { + if (!pOutput) + return SEC_E_INVALID_TOKEN; + + if (pOutput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!output_buffer) + return SEC_E_INVALID_TOKEN; + + if (output_buffer->cbBuffer < 1) + return SEC_E_INVALID_TOKEN; + + if (ntlm_get_state(context) == NTLM_STATE_INITIAL) + ntlm_change_state(context, NTLM_STATE_NEGOTIATE); + + if (ntlm_get_state(context) == NTLM_STATE_NEGOTIATE) + return ntlm_write_NegotiateMessage(context, output_buffer); + + return SEC_E_OUT_OF_SEQUENCE; + } + else + { + if (!input_buffer) + return SEC_E_INVALID_TOKEN; + + if (input_buffer->cbBuffer < 1) + return SEC_E_INVALID_TOKEN; + + channel_bindings = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS); + + if (channel_bindings) + { + context->Bindings.BindingsLength = channel_bindings->cbBuffer; + context->Bindings.Bindings = (SEC_CHANNEL_BINDINGS*)channel_bindings->pvBuffer; + } + + if (ntlm_get_state(context) == NTLM_STATE_CHALLENGE) + { + status = ntlm_read_ChallengeMessage(context, input_buffer); + + if (status != SEC_I_CONTINUE_NEEDED) + return status; + + if (!pOutput) + return SEC_E_INVALID_TOKEN; + + if (pOutput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!output_buffer) + return SEC_E_INVALID_TOKEN; + + if (output_buffer->cbBuffer < 1) + return SEC_E_INSUFFICIENT_MEMORY; + + if (ntlm_get_state(context) == NTLM_STATE_AUTHENTICATE) + return ntlm_write_AuthenticateMessage(context, output_buffer); + } + + return SEC_E_OUT_OF_SEQUENCE; + } + + return SEC_E_OUT_OF_SEQUENCE; +} + +/** + * @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa375512%28v=vs.85%29.aspx + */ +static SECURITY_STATUS SEC_ENTRY ntlm_InitializeSecurityContextA( + PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + SEC_WCHAR* pszTargetNameW = NULL; + + if (pszTargetName) + { + pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, NULL); + if (!pszTargetNameW) + return SEC_E_INTERNAL_ERROR; + } + + status = ntlm_InitializeSecurityContextW(phCredential, phContext, pszTargetNameW, fContextReq, + Reserved1, TargetDataRep, pInput, Reserved2, + phNewContext, pOutput, pfContextAttr, ptsExpiry); + free(pszTargetNameW); + return status; +} + +/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375354 */ + +static SECURITY_STATUS SEC_ENTRY ntlm_DeleteSecurityContext(PCtxtHandle phContext) +{ + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + ntlm_ContextFree(context); + return SEC_E_OK; +} + +SECURITY_STATUS ntlm_computeProofValue(NTLM_CONTEXT* ntlm, SecBuffer* ntproof) +{ + BYTE* blob = NULL; + SecBuffer* target = NULL; + + WINPR_ASSERT(ntlm); + WINPR_ASSERT(ntproof); + + target = &ntlm->ChallengeTargetInfo; + + if (!sspi_SecBufferAlloc(ntproof, 36 + target->cbBuffer)) + return SEC_E_INSUFFICIENT_MEMORY; + + blob = (BYTE*)ntproof->pvBuffer; + CopyMemory(blob, ntlm->ServerChallenge, 8); /* Server challenge. */ + blob[8] = 1; /* Response version. */ + blob[9] = 1; /* Highest response version understood by the client. */ + /* Reserved 6B. */ + CopyMemory(&blob[16], ntlm->Timestamp, 8); /* Time. */ + CopyMemory(&blob[24], ntlm->ClientChallenge, 8); /* Client challenge. */ + /* Reserved 4B. */ + /* Server name. */ + CopyMemory(&blob[36], target->pvBuffer, target->cbBuffer); + return SEC_E_OK; +} + +SECURITY_STATUS ntlm_computeMicValue(NTLM_CONTEXT* ntlm, SecBuffer* micvalue) +{ + BYTE* blob = NULL; + ULONG msgSize = 0; + + WINPR_ASSERT(ntlm); + WINPR_ASSERT(micvalue); + + msgSize = ntlm->NegotiateMessage.cbBuffer + ntlm->ChallengeMessage.cbBuffer + + ntlm->AuthenticateMessage.cbBuffer; + + if (!sspi_SecBufferAlloc(micvalue, msgSize)) + return SEC_E_INSUFFICIENT_MEMORY; + + blob = (BYTE*)micvalue->pvBuffer; + CopyMemory(blob, ntlm->NegotiateMessage.pvBuffer, ntlm->NegotiateMessage.cbBuffer); + blob += ntlm->NegotiateMessage.cbBuffer; + CopyMemory(blob, ntlm->ChallengeMessage.pvBuffer, ntlm->ChallengeMessage.cbBuffer); + blob += ntlm->ChallengeMessage.cbBuffer; + CopyMemory(blob, ntlm->AuthenticateMessage.pvBuffer, ntlm->AuthenticateMessage.cbBuffer); + blob += ntlm->MessageIntegrityCheckOffset; + ZeroMemory(blob, 16); + return SEC_E_OK; +} + +/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa379337/ */ + +static SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + if (!phContext) + return SEC_E_INVALID_HANDLE; + + if (!pBuffer) + return SEC_E_INSUFFICIENT_MEMORY; + + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + if (ulAttribute == SECPKG_ATTR_SIZES) + { + SecPkgContext_Sizes* ContextSizes = (SecPkgContext_Sizes*)pBuffer; + ContextSizes->cbMaxToken = 2010; + ContextSizes->cbMaxSignature = 16; /* the size of expected signature is 16 bytes */ + ContextSizes->cbBlockSize = 0; /* no padding */ + ContextSizes->cbSecurityTrailer = 16; /* no security trailer appended in NTLM + contrary to Kerberos */ + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_IDENTITY) + { + SSPI_CREDENTIALS* credentials = NULL; + const SecPkgContext_AuthIdentity empty = { 0 }; + SecPkgContext_AuthIdentity* AuthIdentity = (SecPkgContext_AuthIdentity*)pBuffer; + + WINPR_ASSERT(AuthIdentity); + *AuthIdentity = empty; + + context->UseSamFileDatabase = FALSE; + credentials = context->credentials; + + if (credentials->identity.UserLength > 0) + { + if (ConvertWCharNToUtf8(credentials->identity.User, credentials->identity.UserLength, + AuthIdentity->User, ARRAYSIZE(AuthIdentity->User)) <= 0) + return SEC_E_INTERNAL_ERROR; + } + + if (credentials->identity.DomainLength > 0) + { + if (ConvertWCharNToUtf8(credentials->identity.Domain, + credentials->identity.DomainLength, AuthIdentity->Domain, + ARRAYSIZE(AuthIdentity->Domain)) <= 0) + return SEC_E_INTERNAL_ERROR; + } + + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_NTPROOF_VALUE) + { + return ntlm_computeProofValue(context, (SecBuffer*)pBuffer); + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_RANDKEY) + { + SecBuffer* randkey = NULL; + randkey = (SecBuffer*)pBuffer; + + if (!sspi_SecBufferAlloc(randkey, 16)) + return (SEC_E_INSUFFICIENT_MEMORY); + + CopyMemory(randkey->pvBuffer, context->EncryptedRandomSessionKey, 16); + return (SEC_E_OK); + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MIC) + { + SecBuffer* mic = (SecBuffer*)pBuffer; + NTLM_AUTHENTICATE_MESSAGE* message = &context->AUTHENTICATE_MESSAGE; + + if (!sspi_SecBufferAlloc(mic, 16)) + return (SEC_E_INSUFFICIENT_MEMORY); + + CopyMemory(mic->pvBuffer, message->MessageIntegrityCheck, 16); + return (SEC_E_OK); + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MIC_VALUE) + { + return ntlm_computeMicValue(context, (SecBuffer*)pBuffer); + } + + WLog_ERR(TAG, "TODO: Implement ulAttribute=0x%08" PRIx32, ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + return ntlm_QueryContextAttributesW(phContext, ulAttribute, pBuffer); +} + +static SECURITY_STATUS SEC_ENTRY ntlm_SetContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + if (!phContext) + return SEC_E_INVALID_HANDLE; + + if (!pBuffer) + return SEC_E_INVALID_PARAMETER; + + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + if (!context) + return SEC_E_INVALID_HANDLE; + + if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_HASH) + { + SecPkgContext_AuthNtlmHash* AuthNtlmHash = (SecPkgContext_AuthNtlmHash*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmHash)) + return SEC_E_INVALID_PARAMETER; + + if (AuthNtlmHash->Version == 1) + CopyMemory(context->NtlmHash, AuthNtlmHash->NtlmHash, 16); + else if (AuthNtlmHash->Version == 2) + CopyMemory(context->NtlmV2Hash, AuthNtlmHash->NtlmHash, 16); + + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MESSAGE) + { + SecPkgContext_AuthNtlmMessage* AuthNtlmMessage = (SecPkgContext_AuthNtlmMessage*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmMessage)) + return SEC_E_INVALID_PARAMETER; + + if (AuthNtlmMessage->type == 1) + { + sspi_SecBufferFree(&context->NegotiateMessage); + + if (!sspi_SecBufferAlloc(&context->NegotiateMessage, AuthNtlmMessage->length)) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(context->NegotiateMessage.pvBuffer, AuthNtlmMessage->buffer, + AuthNtlmMessage->length); + } + else if (AuthNtlmMessage->type == 2) + { + sspi_SecBufferFree(&context->ChallengeMessage); + + if (!sspi_SecBufferAlloc(&context->ChallengeMessage, AuthNtlmMessage->length)) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(context->ChallengeMessage.pvBuffer, AuthNtlmMessage->buffer, + AuthNtlmMessage->length); + } + else if (AuthNtlmMessage->type == 3) + { + sspi_SecBufferFree(&context->AuthenticateMessage); + + if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, AuthNtlmMessage->length)) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(context->AuthenticateMessage.pvBuffer, AuthNtlmMessage->buffer, + AuthNtlmMessage->length); + } + + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_TIMESTAMP) + { + SecPkgContext_AuthNtlmTimestamp* AuthNtlmTimestamp = + (SecPkgContext_AuthNtlmTimestamp*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmTimestamp)) + return SEC_E_INVALID_PARAMETER; + + if (AuthNtlmTimestamp->ChallengeOrResponse) + CopyMemory(context->ChallengeTimestamp, AuthNtlmTimestamp->Timestamp, 8); + else + CopyMemory(context->Timestamp, AuthNtlmTimestamp->Timestamp, 8); + + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_CLIENT_CHALLENGE) + { + SecPkgContext_AuthNtlmClientChallenge* AuthNtlmClientChallenge = + (SecPkgContext_AuthNtlmClientChallenge*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmClientChallenge)) + return SEC_E_INVALID_PARAMETER; + + CopyMemory(context->ClientChallenge, AuthNtlmClientChallenge->ClientChallenge, 8); + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_SERVER_CHALLENGE) + { + SecPkgContext_AuthNtlmServerChallenge* AuthNtlmServerChallenge = + (SecPkgContext_AuthNtlmServerChallenge*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmServerChallenge)) + return SEC_E_INVALID_PARAMETER; + + CopyMemory(context->ServerChallenge, AuthNtlmServerChallenge->ServerChallenge, 8); + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_SetContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + return ntlm_SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer); +} + +static SECURITY_STATUS SEC_ENTRY ntlm_SetCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_SetCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_RevertSecurityContext(PCtxtHandle phContext) +{ + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_EncryptMessage(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + const UINT32 SeqNo = MessageSeqNo; + UINT32 value = 0; + BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + BYTE checksum[8] = { 0 }; + ULONG version = 1; + PSecBuffer data_buffer = NULL; + PSecBuffer signature_buffer = NULL; + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + for (ULONG index = 0; index < pMessage->cBuffers; index++) + { + SecBuffer* cur = &pMessage->pBuffers[index]; + + if (cur->BufferType & SECBUFFER_DATA) + data_buffer = cur; + else if (cur->BufferType & SECBUFFER_TOKEN) + signature_buffer = cur; + } + + if (!data_buffer) + return SEC_E_INVALID_TOKEN; + + if (!signature_buffer) + return SEC_E_INVALID_TOKEN; + + /* Copy original data buffer */ + ULONG length = data_buffer->cbBuffer; + void* data = malloc(length); + + if (!data) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(data, data_buffer->pvBuffer, length); + /* Compute the HMAC-MD5 hash of ConcatenationOf(seq_num,data) using the client signing key */ + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + if (hmac && + winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->SendSigningKey, WINPR_MD5_DIGEST_LENGTH)) + { + Data_Write_UINT32(&value, SeqNo); + winpr_HMAC_Update(hmac, (void*)&value, 4); + winpr_HMAC_Update(hmac, (void*)data, length); + winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH); + winpr_HMAC_Free(hmac); + } + else + { + winpr_HMAC_Free(hmac); + free(data); + return SEC_E_INSUFFICIENT_MEMORY; + } + + /* Encrypt message using with RC4, result overwrites original buffer */ + if ((data_buffer->BufferType & SECBUFFER_READONLY) == 0) + { + if (context->confidentiality) + winpr_RC4_Update(context->SendRc4Seal, length, (BYTE*)data, + (BYTE*)data_buffer->pvBuffer); + else + CopyMemory(data_buffer->pvBuffer, data, length); + } + +#ifdef WITH_DEBUG_NTLM + WLog_DBG(TAG, "Data Buffer (length = %" PRIuz ")", length); + winpr_HexDump(TAG, WLOG_DEBUG, data, length); + WLog_DBG(TAG, "Encrypted Data Buffer (length = %" PRIu32 ")", data_buffer->cbBuffer); + winpr_HexDump(TAG, WLOG_DEBUG, data_buffer->pvBuffer, data_buffer->cbBuffer); +#endif + free(data); + /* RC4-encrypt first 8 bytes of digest */ + winpr_RC4_Update(context->SendRc4Seal, 8, digest, checksum); + if ((signature_buffer->BufferType & SECBUFFER_READONLY) == 0) + { + BYTE* signature = signature_buffer->pvBuffer; + /* Concatenate version, ciphertext and sequence number to build signature */ + Data_Write_UINT32(signature, version); + CopyMemory(&signature[4], (void*)checksum, 8); + Data_Write_UINT32(&signature[12], SeqNo); + } + context->SendSeqNum++; +#ifdef WITH_DEBUG_NTLM + WLog_DBG(TAG, "Signature (length = %" PRIu32 ")", signature_buffer->cbBuffer); + winpr_HexDump(TAG, WLOG_DEBUG, signature_buffer->pvBuffer, signature_buffer->cbBuffer); +#endif + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_DecryptMessage(PCtxtHandle phContext, PSecBufferDesc pMessage, + ULONG MessageSeqNo, PULONG pfQOP) +{ + const UINT32 SeqNo = (UINT32)MessageSeqNo; + UINT32 value = 0; + BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + BYTE checksum[8] = { 0 }; + UINT32 version = 1; + BYTE expected_signature[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + PSecBuffer data_buffer = NULL; + PSecBuffer signature_buffer = NULL; + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + for (ULONG index = 0; index < pMessage->cBuffers; index++) + { + if (pMessage->pBuffers[index].BufferType == SECBUFFER_DATA) + data_buffer = &pMessage->pBuffers[index]; + else if (pMessage->pBuffers[index].BufferType == SECBUFFER_TOKEN) + signature_buffer = &pMessage->pBuffers[index]; + } + + if (!data_buffer) + return SEC_E_INVALID_TOKEN; + + if (!signature_buffer) + return SEC_E_INVALID_TOKEN; + + /* Copy original data buffer */ + const ULONG length = data_buffer->cbBuffer; + void* data = malloc(length); + + if (!data) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(data, data_buffer->pvBuffer, length); + + /* Decrypt message using with RC4, result overwrites original buffer */ + + if (context->confidentiality) + winpr_RC4_Update(context->RecvRc4Seal, length, (BYTE*)data, (BYTE*)data_buffer->pvBuffer); + else + CopyMemory(data_buffer->pvBuffer, data, length); + + /* Compute the HMAC-MD5 hash of ConcatenationOf(seq_num,data) using the client signing key */ + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + if (hmac && + winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->RecvSigningKey, WINPR_MD5_DIGEST_LENGTH)) + { + Data_Write_UINT32(&value, SeqNo); + winpr_HMAC_Update(hmac, (void*)&value, 4); + winpr_HMAC_Update(hmac, (void*)data_buffer->pvBuffer, data_buffer->cbBuffer); + winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH); + winpr_HMAC_Free(hmac); + } + else + { + winpr_HMAC_Free(hmac); + free(data); + return SEC_E_INSUFFICIENT_MEMORY; + } + +#ifdef WITH_DEBUG_NTLM + WLog_DBG(TAG, "Encrypted Data Buffer (length = %" PRIuz ")", length); + winpr_HexDump(TAG, WLOG_DEBUG, data, length); + WLog_DBG(TAG, "Data Buffer (length = %" PRIu32 ")", data_buffer->cbBuffer); + winpr_HexDump(TAG, WLOG_DEBUG, data_buffer->pvBuffer, data_buffer->cbBuffer); +#endif + free(data); + /* RC4-encrypt first 8 bytes of digest */ + winpr_RC4_Update(context->RecvRc4Seal, 8, digest, checksum); + /* Concatenate version, ciphertext and sequence number to build signature */ + Data_Write_UINT32(expected_signature, version); + CopyMemory(&expected_signature[4], (void*)checksum, 8); + Data_Write_UINT32(&expected_signature[12], SeqNo); + context->RecvSeqNum++; + + if (memcmp(signature_buffer->pvBuffer, expected_signature, 16) != 0) + { + /* signature verification failed! */ + WLog_ERR(TAG, "signature verification failed, something nasty is going on!"); +#ifdef WITH_DEBUG_NTLM + WLog_ERR(TAG, "Expected Signature:"); + winpr_HexDump(TAG, WLOG_ERROR, expected_signature, 16); + WLog_ERR(TAG, "Actual Signature:"); + winpr_HexDump(TAG, WLOG_ERROR, (BYTE*)signature_buffer->pvBuffer, 16); +#endif + return SEC_E_MESSAGE_ALTERED; + } + + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_MakeSignature(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + PSecBuffer data_buffer = NULL; + PSecBuffer sig_buffer = NULL; + UINT32 seq_no = 0; + BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + BYTE checksum[8] = { 0 }; + + NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + for (ULONG i = 0; i < pMessage->cBuffers; i++) + { + if (pMessage->pBuffers[i].BufferType == SECBUFFER_DATA) + data_buffer = &pMessage->pBuffers[i]; + else if (pMessage->pBuffers[i].BufferType == SECBUFFER_TOKEN) + sig_buffer = &pMessage->pBuffers[i]; + } + + if (!data_buffer || !sig_buffer) + return SEC_E_INVALID_TOKEN; + + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + if (!winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->SendSigningKey, WINPR_MD5_DIGEST_LENGTH)) + return SEC_E_INTERNAL_ERROR; + + Data_Write_UINT32(&seq_no, MessageSeqNo); + winpr_HMAC_Update(hmac, (BYTE*)&seq_no, 4); + winpr_HMAC_Update(hmac, data_buffer->pvBuffer, data_buffer->cbBuffer); + winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH); + winpr_HMAC_Free(hmac); + + winpr_RC4_Update(context->SendRc4Seal, 8, digest, checksum); + + BYTE* signature = sig_buffer->pvBuffer; + Data_Write_UINT32(signature, 1L); + CopyMemory(&signature[4], checksum, 8); + Data_Write_UINT32(&signature[12], seq_no); + sig_buffer->cbBuffer = 16; + + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_VerifySignature(PCtxtHandle phContext, + PSecBufferDesc pMessage, ULONG MessageSeqNo, + PULONG pfQOP) +{ + PSecBuffer data_buffer = NULL; + PSecBuffer sig_buffer = NULL; + UINT32 seq_no = 0; + BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + BYTE checksum[8] = { 0 }; + BYTE signature[16] = { 0 }; + + NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + for (ULONG i = 0; i < pMessage->cBuffers; i++) + { + if (pMessage->pBuffers[i].BufferType == SECBUFFER_DATA) + data_buffer = &pMessage->pBuffers[i]; + else if (pMessage->pBuffers[i].BufferType == SECBUFFER_TOKEN) + sig_buffer = &pMessage->pBuffers[i]; + } + + if (!data_buffer || !sig_buffer) + return SEC_E_INVALID_TOKEN; + + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + if (!winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->RecvSigningKey, WINPR_MD5_DIGEST_LENGTH)) + return SEC_E_INTERNAL_ERROR; + + Data_Write_UINT32(&seq_no, MessageSeqNo); + winpr_HMAC_Update(hmac, (BYTE*)&seq_no, 4); + winpr_HMAC_Update(hmac, data_buffer->pvBuffer, data_buffer->cbBuffer); + winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH); + winpr_HMAC_Free(hmac); + + winpr_RC4_Update(context->RecvRc4Seal, 8, digest, checksum); + + Data_Write_UINT32(signature, 1L); + CopyMemory(&signature[4], checksum, 8); + Data_Write_UINT32(&signature[12], seq_no); + + if (memcmp(sig_buffer->pvBuffer, signature, 16) != 0) + return SEC_E_MESSAGE_ALTERED; + + return SEC_E_OK; +} + +const SecurityFunctionTableA NTLM_SecurityFunctionTableA = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + ntlm_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */ + ntlm_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */ + ntlm_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + ntlm_InitializeSecurityContextA, /* InitializeSecurityContext */ + ntlm_AcceptSecurityContext, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + ntlm_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + ntlm_QueryContextAttributesA, /* QueryContextAttributes */ + ntlm_ImpersonateSecurityContext, /* ImpersonateSecurityContext */ + ntlm_RevertSecurityContext, /* RevertSecurityContext */ + ntlm_MakeSignature, /* MakeSignature */ + ntlm_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + ntlm_EncryptMessage, /* EncryptMessage */ + ntlm_DecryptMessage, /* DecryptMessage */ + ntlm_SetContextAttributesA, /* SetContextAttributes */ + ntlm_SetCredentialsAttributesA, /* SetCredentialsAttributes */ +}; + +const SecurityFunctionTableW NTLM_SecurityFunctionTableW = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + ntlm_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */ + ntlm_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */ + ntlm_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + ntlm_InitializeSecurityContextW, /* InitializeSecurityContext */ + ntlm_AcceptSecurityContext, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + ntlm_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + ntlm_QueryContextAttributesW, /* QueryContextAttributes */ + ntlm_ImpersonateSecurityContext, /* ImpersonateSecurityContext */ + ntlm_RevertSecurityContext, /* RevertSecurityContext */ + ntlm_MakeSignature, /* MakeSignature */ + ntlm_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + ntlm_EncryptMessage, /* EncryptMessage */ + ntlm_DecryptMessage, /* DecryptMessage */ + ntlm_SetContextAttributesW, /* SetContextAttributes */ + ntlm_SetCredentialsAttributesW, /* SetCredentialsAttributes */ +}; + +const SecPkgInfoA NTLM_SecPkgInfoA = { + 0x00082B37, /* fCapabilities */ + 1, /* wVersion */ + 0x000A, /* wRPCID */ + 0x00000B48, /* cbMaxToken */ + "NTLM", /* Name */ + "NTLM Security Package" /* Comment */ +}; + +static WCHAR NTLM_SecPkgInfoW_NameBuffer[32] = { 0 }; +static WCHAR NTLM_SecPkgInfoW_CommentBuffer[32] = { 0 }; + +const SecPkgInfoW NTLM_SecPkgInfoW = { + 0x00082B37, /* fCapabilities */ + 1, /* wVersion */ + 0x000A, /* wRPCID */ + 0x00000B48, /* cbMaxToken */ + NTLM_SecPkgInfoW_NameBuffer, /* Name */ + NTLM_SecPkgInfoW_CommentBuffer /* Comment */ +}; + +char* ntlm_negotiate_flags_string(char* buffer, size_t size, UINT32 flags) +{ + if (!buffer || (size == 0)) + return buffer; + + _snprintf(buffer, size, "[0x%08" PRIx32 "] ", flags); + + for (int x = 0; x < 31; x++) + { + const UINT32 mask = 1 << x; + size_t len = strnlen(buffer, size); + if (flags & mask) + { + const char* str = ntlm_get_negotiate_string(mask); + const size_t flen = strlen(str); + + if ((len > 0) && (buffer[len - 1] != ' ')) + { + if (size - len < 1) + break; + winpr_str_append("|", buffer, size, NULL); + len++; + } + + if (size - len < flen) + break; + winpr_str_append(str, buffer, size, NULL); + } + } + + return buffer; +} + +const char* ntlm_message_type_string(UINT32 messageType) +{ + switch (messageType) + { + case MESSAGE_TYPE_NEGOTIATE: + return "MESSAGE_TYPE_NEGOTIATE"; + case MESSAGE_TYPE_CHALLENGE: + return "MESSAGE_TYPE_CHALLENGE"; + case MESSAGE_TYPE_AUTHENTICATE: + return "MESSAGE_TYPE_AUTHENTICATE"; + default: + return "MESSAGE_TYPE_UNKNOWN"; + } +} + +const char* ntlm_state_string(NTLM_STATE state) +{ + switch (state) + { + case NTLM_STATE_INITIAL: + return "NTLM_STATE_INITIAL"; + case NTLM_STATE_NEGOTIATE: + return "NTLM_STATE_NEGOTIATE"; + case NTLM_STATE_CHALLENGE: + return "NTLM_STATE_CHALLENGE"; + case NTLM_STATE_AUTHENTICATE: + return "NTLM_STATE_AUTHENTICATE"; + case NTLM_STATE_FINAL: + return "NTLM_STATE_FINAL"; + default: + return "NTLM_STATE_UNKNOWN"; + } +} +void ntlm_change_state(NTLM_CONTEXT* ntlm, NTLM_STATE state) +{ + WINPR_ASSERT(ntlm); + WLog_DBG(TAG, "change state from %s to %s", ntlm_state_string(ntlm->state), + ntlm_state_string(state)); + ntlm->state = state; +} + +NTLM_STATE ntlm_get_state(NTLM_CONTEXT* ntlm) +{ + WINPR_ASSERT(ntlm); + return ntlm->state; +} + +BOOL ntlm_reset_cipher_state(PSecHandle phContext) +{ + NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext); + + if (context) + { + check_context(context); + winpr_RC4_Free(context->SendRc4Seal); + winpr_RC4_Free(context->RecvRc4Seal); + context->SendRc4Seal = winpr_RC4_New(context->RecvSealingKey, 16); + context->RecvRc4Seal = winpr_RC4_New(context->SendSealingKey, 16); + + if (!context->SendRc4Seal) + { + WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal"); + return FALSE; + } + if (!context->RecvRc4Seal) + { + WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal"); + return FALSE; + } + } + + return TRUE; +} + +BOOL NTLM_init(void) +{ + InitializeConstWCharFromUtf8(NTLM_SecPkgInfoA.Name, NTLM_SecPkgInfoW_NameBuffer, + ARRAYSIZE(NTLM_SecPkgInfoW_NameBuffer)); + InitializeConstWCharFromUtf8(NTLM_SecPkgInfoA.Comment, NTLM_SecPkgInfoW_CommentBuffer, + ARRAYSIZE(NTLM_SecPkgInfoW_CommentBuffer)); + + return TRUE; +} diff --git a/winpr/libwinpr/sspi/NTLM/ntlm.h b/winpr/libwinpr/sspi/NTLM/ntlm.h new file mode 100644 index 0000000..4eac436 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm.h @@ -0,0 +1,301 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package + * + * 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. + */ + +#ifndef WINPR_SSPI_NTLM_PRIVATE_H +#define WINPR_SSPI_NTLM_PRIVATE_H + +#include <winpr/sspi.h> +#include <winpr/windows.h> + +#include <winpr/nt.h> +#include <winpr/crypto.h> + +#include "../sspi.h" + +#define MESSAGE_TYPE_NEGOTIATE 1 +#define MESSAGE_TYPE_CHALLENGE 2 +#define MESSAGE_TYPE_AUTHENTICATE 3 + +#define NTLMSSP_NEGOTIATE_56 0x80000000 /* W (0) */ +#define NTLMSSP_NEGOTIATE_KEY_EXCH 0x40000000 /* V (1) */ +#define NTLMSSP_NEGOTIATE_128 0x20000000 /* U (2) */ +#define NTLMSSP_RESERVED1 0x10000000 /* r1 (3) */ +#define NTLMSSP_RESERVED2 0x08000000 /* r2 (4) */ +#define NTLMSSP_RESERVED3 0x04000000 /* r3 (5) */ +#define NTLMSSP_NEGOTIATE_VERSION 0x02000000 /* T (6) */ +#define NTLMSSP_RESERVED4 0x01000000 /* r4 (7) */ +#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000 /* S (8) */ +#define NTLMSSP_REQUEST_NON_NT_SESSION_KEY 0x00400000 /* R (9) */ +#define NTLMSSP_RESERVED5 0x00200000 /* r5 (10) */ +#define NTLMSSP_NEGOTIATE_IDENTIFY 0x00100000 /* Q (11) */ +#define NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY 0x00080000 /* P (12) */ +#define NTLMSSP_RESERVED6 0x00040000 /* r6 (13) */ +#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000 /* O (14) */ +#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000 /* N (15) */ +#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000 /* M (16) */ +#define NTLMSSP_RESERVED7 0x00004000 /* r7 (17) */ +#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000 /* L (18) */ +#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000 /* K (19) */ +#define NTLMSSP_NEGOTIATE_ANONYMOUS 0x00000800 /* J (20) */ +#define NTLMSSP_RESERVED8 0x00000400 /* r8 (21) */ +#define NTLMSSP_NEGOTIATE_NTLM 0x00000200 /* H (22) */ +#define NTLMSSP_RESERVED9 0x00000100 /* r9 (23) */ +#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080 /* G (24) */ +#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040 /* F (25) */ +#define NTLMSSP_NEGOTIATE_SEAL 0x00000020 /* E (26) */ +#define NTLMSSP_NEGOTIATE_SIGN 0x00000010 /* D (27) */ +#define NTLMSSP_RESERVED10 0x00000008 /* r10 (28) */ +#define NTLMSSP_REQUEST_TARGET 0x00000004 /* C (29) */ +#define NTLMSSP_NEGOTIATE_OEM 0x00000002 /* B (30) */ +#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001 /* A (31) */ + +typedef enum +{ + NTLM_STATE_INITIAL, + NTLM_STATE_NEGOTIATE, + NTLM_STATE_CHALLENGE, + NTLM_STATE_AUTHENTICATE, + NTLM_STATE_FINAL +} NTLM_STATE; + +#ifdef __MINGW32__ +typedef MSV1_0_AVID NTLM_AV_ID; + +#if __MINGW64_VERSION_MAJOR < 9 +enum +{ + MsvAvTimestamp = MsvAvFlags + 1, + MsvAvRestrictions, + MsvAvTargetName, + MsvAvChannelBindings, + MsvAvSingleHost = MsvAvRestrictions +}; + +#else +#ifndef MsvAvSingleHost +#define MsvAvSingleHost MsvAvRestrictions +#endif +#endif +#else +typedef enum +{ + MsvAvEOL, + MsvAvNbComputerName, + MsvAvNbDomainName, + MsvAvDnsComputerName, + MsvAvDnsDomainName, + MsvAvDnsTreeName, + MsvAvFlags, + MsvAvTimestamp, + MsvAvSingleHost, + MsvAvTargetName, + MsvAvChannelBindings +} NTLM_AV_ID; +#endif /* __MINGW32__ */ + +typedef struct +{ + UINT16 AvId; + UINT16 AvLen; +} NTLM_AV_PAIR; + +#define MSV_AV_FLAGS_AUTHENTICATION_CONSTRAINED 0x00000001 +#define MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK 0x00000002 +#define MSV_AV_FLAGS_TARGET_SPN_UNTRUSTED_SOURCE 0x00000004 + +#define WINDOWS_MAJOR_VERSION_5 0x05 +#define WINDOWS_MAJOR_VERSION_6 0x06 +#define WINDOWS_MINOR_VERSION_0 0x00 +#define WINDOWS_MINOR_VERSION_1 0x01 +#define WINDOWS_MINOR_VERSION_2 0x02 +#define NTLMSSP_REVISION_W2K3 0x0F + +typedef struct +{ + UINT8 ProductMajorVersion; + UINT8 ProductMinorVersion; + UINT16 ProductBuild; + BYTE Reserved[3]; + UINT8 NTLMRevisionCurrent; +} NTLM_VERSION_INFO; + +typedef struct +{ + UINT32 Size; + UINT32 Z4; + UINT32 DataPresent; + UINT32 CustomData; + BYTE MachineID[32]; +} NTLM_SINGLE_HOST_DATA; + +typedef struct +{ + BYTE Response[24]; +} NTLM_RESPONSE; + +typedef struct +{ + UINT8 RespType; + UINT8 HiRespType; + UINT16 Reserved1; + UINT32 Reserved2; + BYTE Timestamp[8]; + BYTE ClientChallenge[8]; + UINT32 Reserved3; + NTLM_AV_PAIR* AvPairs; + UINT32 cbAvPairs; +} NTLMv2_CLIENT_CHALLENGE; + +typedef struct +{ + BYTE Response[16]; + NTLMv2_CLIENT_CHALLENGE Challenge; +} NTLMv2_RESPONSE; + +typedef struct +{ + UINT16 Len; + UINT16 MaxLen; + PBYTE Buffer; + UINT32 BufferOffset; +} NTLM_MESSAGE_FIELDS; + +typedef struct +{ + BYTE Signature[8]; + UINT32 MessageType; +} NTLM_MESSAGE_HEADER; + +typedef struct +{ + NTLM_MESSAGE_HEADER header; + UINT32 NegotiateFlags; + NTLM_VERSION_INFO Version; + NTLM_MESSAGE_FIELDS DomainName; + NTLM_MESSAGE_FIELDS Workstation; +} NTLM_NEGOTIATE_MESSAGE; + +typedef struct +{ + NTLM_MESSAGE_HEADER header; + UINT32 NegotiateFlags; + BYTE ServerChallenge[8]; + BYTE Reserved[8]; + NTLM_VERSION_INFO Version; + NTLM_MESSAGE_FIELDS TargetName; + NTLM_MESSAGE_FIELDS TargetInfo; +} NTLM_CHALLENGE_MESSAGE; + +typedef struct +{ + NTLM_MESSAGE_HEADER header; + UINT32 NegotiateFlags; + NTLM_VERSION_INFO Version; + NTLM_MESSAGE_FIELDS DomainName; + NTLM_MESSAGE_FIELDS UserName; + NTLM_MESSAGE_FIELDS Workstation; + NTLM_MESSAGE_FIELDS LmChallengeResponse; + NTLM_MESSAGE_FIELDS NtChallengeResponse; + NTLM_MESSAGE_FIELDS EncryptedRandomSessionKey; + BYTE MessageIntegrityCheck[16]; +} NTLM_AUTHENTICATE_MESSAGE; + +typedef struct +{ + BOOL server; + BOOL NTLMv2; + BOOL UseMIC; + NTLM_STATE state; + int SendSeqNum; + int RecvSeqNum; + char* SamFile; + BYTE NtlmHash[16]; + BYTE NtlmV2Hash[16]; + BYTE MachineID[32]; + BOOL SendVersionInfo; + BOOL confidentiality; + WINPR_RC4_CTX* SendRc4Seal; + WINPR_RC4_CTX* RecvRc4Seal; + BYTE* SendSigningKey; + BYTE* RecvSigningKey; + BYTE* SendSealingKey; + BYTE* RecvSealingKey; + UINT32 NegotiateFlags; + BOOL UseSamFileDatabase; + int LmCompatibilityLevel; + int SuppressExtendedProtection; + BOOL SendWorkstationName; + UNICODE_STRING Workstation; + UNICODE_STRING ServicePrincipalName; + SSPI_CREDENTIALS* credentials; + BYTE* ChannelBindingToken; + BYTE ChannelBindingsHash[16]; + SecPkgContext_Bindings Bindings; + BOOL SendSingleHostData; + BOOL NegotiateKeyExchange; + NTLM_SINGLE_HOST_DATA SingleHostData; + NTLM_NEGOTIATE_MESSAGE NEGOTIATE_MESSAGE; + NTLM_CHALLENGE_MESSAGE CHALLENGE_MESSAGE; + NTLM_AUTHENTICATE_MESSAGE AUTHENTICATE_MESSAGE; + size_t MessageIntegrityCheckOffset; + SecBuffer NegotiateMessage; + SecBuffer ChallengeMessage; + SecBuffer AuthenticateMessage; + SecBuffer ChallengeTargetInfo; + SecBuffer AuthenticateTargetInfo; + SecBuffer TargetName; + SecBuffer NtChallengeResponse; + SecBuffer LmChallengeResponse; + NTLMv2_RESPONSE NTLMv2Response; + BYTE NtProofString[16]; + BYTE Timestamp[8]; + BYTE ChallengeTimestamp[8]; + BYTE ServerChallenge[8]; + BYTE ClientChallenge[8]; + BYTE SessionBaseKey[16]; + BYTE KeyExchangeKey[16]; + BYTE RandomSessionKey[16]; + BYTE ExportedSessionKey[16]; + BYTE EncryptedRandomSessionKey[16]; + BYTE ClientSigningKey[16]; + BYTE ClientSealingKey[16]; + BYTE ServerSigningKey[16]; + BYTE ServerSealingKey[16]; + psSspiNtlmHashCallback HashCallback; + void* HashCallbackArg; +} NTLM_CONTEXT; + +char* ntlm_negotiate_flags_string(char* buffer, size_t size, UINT32 flags); +const char* ntlm_message_type_string(UINT32 messageType); + +const char* ntlm_state_string(NTLM_STATE state); +void ntlm_change_state(NTLM_CONTEXT* ntlm, NTLM_STATE state); +NTLM_STATE ntlm_get_state(NTLM_CONTEXT* ntlm); +BOOL ntlm_reset_cipher_state(PSecHandle phContext); + +SECURITY_STATUS ntlm_computeProofValue(NTLM_CONTEXT* ntlm, SecBuffer* ntproof); +SECURITY_STATUS ntlm_computeMicValue(NTLM_CONTEXT* ntlm, SecBuffer* micvalue); + +#ifdef WITH_DEBUG_NLA +#define WITH_DEBUG_NTLM +#endif + +BOOL NTLM_init(void); + +#endif /* WINPR_SSPI_NTLM_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c new file mode 100644 index 0000000..881a743 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c @@ -0,0 +1,775 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (AV_PAIRs) + * + * 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. + */ + +#include <winpr/config.h> + +#include <winpr/assert.h> + +#include "ntlm.h" +#include "../sspi.h" + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/sysinfo.h> +#include <winpr/tchar.h> +#include <winpr/crypto.h> + +#include "ntlm_compute.h" + +#include "ntlm_av_pairs.h" + +#include "../../log.h" +#define TAG WINPR_TAG("sspi.NTLM") + +static BOOL ntlm_av_pair_get_next_offset(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pOffset); + +static BOOL ntlm_av_pair_check_data(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair, size_t size) +{ + size_t offset = 0; + if (!pAvPair || cbAvPair < sizeof(NTLM_AV_PAIR) + size) + return FALSE; + if (!ntlm_av_pair_get_next_offset(pAvPair, cbAvPair, &offset)) + return FALSE; + return cbAvPair >= offset; +} + +static const char* get_av_pair_string(UINT16 pair) +{ + switch (pair) + { + case MsvAvEOL: + return "MsvAvEOL"; + case MsvAvNbComputerName: + return "MsvAvNbComputerName"; + case MsvAvNbDomainName: + return "MsvAvNbDomainName"; + case MsvAvDnsComputerName: + return "MsvAvDnsComputerName"; + case MsvAvDnsDomainName: + return "MsvAvDnsDomainName"; + case MsvAvDnsTreeName: + return "MsvAvDnsTreeName"; + case MsvAvFlags: + return "MsvAvFlags"; + case MsvAvTimestamp: + return "MsvAvTimestamp"; + case MsvAvSingleHost: + return "MsvAvSingleHost"; + case MsvAvTargetName: + return "MsvAvTargetName"; + case MsvAvChannelBindings: + return "MsvAvChannelBindings"; + default: + return "UNKNOWN"; + } +} + +static BOOL ntlm_av_pair_check(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair); +static NTLM_AV_PAIR* ntlm_av_pair_next(NTLM_AV_PAIR* pAvPairList, size_t* pcbAvPairList); + +static INLINE void ntlm_av_pair_set_id(NTLM_AV_PAIR* pAvPair, UINT16 id) +{ + WINPR_ASSERT(pAvPair); + Data_Write_UINT16(&pAvPair->AvId, id); +} + +static INLINE void ntlm_av_pair_set_len(NTLM_AV_PAIR* pAvPair, UINT16 len) +{ + WINPR_ASSERT(pAvPair); + Data_Write_UINT16(&pAvPair->AvLen, len); +} + +static BOOL ntlm_av_pair_list_init(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList) +{ + NTLM_AV_PAIR* pAvPair = pAvPairList; + + if (!pAvPair || (cbAvPairList < sizeof(NTLM_AV_PAIR))) + return FALSE; + + ntlm_av_pair_set_id(pAvPair, MsvAvEOL); + ntlm_av_pair_set_len(pAvPair, 0); + return TRUE; +} + +static INLINE BOOL ntlm_av_pair_get_id(const NTLM_AV_PAIR* pAvPair, size_t size, UINT16* pair) +{ + UINT16 AvId = 0; + if (!pAvPair || !pair) + return FALSE; + + if (size < sizeof(NTLM_AV_PAIR)) + return FALSE; + + Data_Read_UINT16(&pAvPair->AvId, AvId); + + *pair = AvId; + return TRUE; +} + +ULONG ntlm_av_pair_list_length(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList) +{ + size_t size = 0; + size_t cbAvPair = 0; + NTLM_AV_PAIR* pAvPair = NULL; + + pAvPair = ntlm_av_pair_get(pAvPairList, cbAvPairList, MsvAvEOL, &cbAvPair); + if (!pAvPair) + return 0; + + size = ((PBYTE)pAvPair - (PBYTE)pAvPairList) + sizeof(NTLM_AV_PAIR); + WINPR_ASSERT(size <= ULONG_MAX); + return (ULONG)size; +} + +static INLINE BOOL ntlm_av_pair_get_len(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pAvLen) +{ + UINT16 AvLen = 0; + if (!pAvPair) + return FALSE; + + if (size < sizeof(NTLM_AV_PAIR)) + return FALSE; + + Data_Read_UINT16(&pAvPair->AvLen, AvLen); + + *pAvLen = AvLen; + return TRUE; +} + +#ifdef WITH_DEBUG_NTLM +void ntlm_print_av_pair_list(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList) +{ + UINT16 pair = 0; + size_t cbAvPair = cbAvPairList; + NTLM_AV_PAIR* pAvPair = pAvPairList; + + if (!ntlm_av_pair_check(pAvPair, cbAvPair)) + return; + + WLog_VRB(TAG, "AV_PAIRs ="); + + while (pAvPair && ntlm_av_pair_get_id(pAvPair, cbAvPair, &pair) && (pair != MsvAvEOL)) + { + size_t cbLen = 0; + ntlm_av_pair_get_len(pAvPair, cbAvPair, &cbLen); + + WLog_VRB(TAG, "\t%s AvId: %" PRIu16 " AvLen: %" PRIu16 "", get_av_pair_string(pair), pair); + winpr_HexDump(TAG, WLOG_TRACE, ntlm_av_pair_get_value_pointer(pAvPair), cbLen); + + pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair); + } +} +#endif + +static ULONG ntlm_av_pair_list_size(ULONG AvPairsCount, ULONG AvPairsValueLength) +{ + /* size of headers + value lengths + terminating MsvAvEOL AV_PAIR */ + return ((AvPairsCount + 1) * 4) + AvPairsValueLength; +} + +PBYTE ntlm_av_pair_get_value_pointer(NTLM_AV_PAIR* pAvPair) +{ + WINPR_ASSERT(pAvPair); + return (PBYTE)pAvPair + sizeof(NTLM_AV_PAIR); +} + +static BOOL ntlm_av_pair_get_next_offset(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pOffset) +{ + size_t avLen = 0; + if (!pOffset) + return FALSE; + + if (!ntlm_av_pair_get_len(pAvPair, size, &avLen)) + return FALSE; + *pOffset = avLen + sizeof(NTLM_AV_PAIR); + return TRUE; +} + +static BOOL ntlm_av_pair_check(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair) +{ + return ntlm_av_pair_check_data(pAvPair, cbAvPair, 0); +} + +static NTLM_AV_PAIR* ntlm_av_pair_next(NTLM_AV_PAIR* pAvPair, size_t* pcbAvPair) +{ + size_t offset = 0; + + if (!pcbAvPair) + return NULL; + if (!ntlm_av_pair_check(pAvPair, *pcbAvPair)) + return NULL; + + if (!ntlm_av_pair_get_next_offset(pAvPair, *pcbAvPair, &offset)) + return NULL; + + *pcbAvPair -= offset; + return (NTLM_AV_PAIR*)((PBYTE)pAvPair + offset); +} + +NTLM_AV_PAIR* ntlm_av_pair_get(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId, + size_t* pcbAvPairListRemaining) +{ + UINT16 id = 0; + size_t cbAvPair = cbAvPairList; + NTLM_AV_PAIR* pAvPair = pAvPairList; + + if (!ntlm_av_pair_check(pAvPair, cbAvPair)) + pAvPair = NULL; + + while (pAvPair && ntlm_av_pair_get_id(pAvPair, cbAvPair, &id)) + { + if (id == AvId) + break; + if (id == MsvAvEOL) + { + pAvPair = NULL; + break; + } + + pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair); + } + + if (!pAvPair) + cbAvPair = 0; + if (pcbAvPairListRemaining) + *pcbAvPairListRemaining = cbAvPair; + + return pAvPair; +} + +static BOOL ntlm_av_pair_add(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId, + PBYTE Value, UINT16 AvLen) +{ + size_t cbAvPair = 0; + NTLM_AV_PAIR* pAvPair = NULL; + + pAvPair = ntlm_av_pair_get(pAvPairList, cbAvPairList, MsvAvEOL, &cbAvPair); + + /* size of header + value length + terminating MsvAvEOL AV_PAIR */ + if (!pAvPair || cbAvPair < 2 * sizeof(NTLM_AV_PAIR) + AvLen) + return FALSE; + + ntlm_av_pair_set_id(pAvPair, (UINT16)AvId); + ntlm_av_pair_set_len(pAvPair, AvLen); + if (AvLen) + { + WINPR_ASSERT(Value != NULL); + CopyMemory(ntlm_av_pair_get_value_pointer(pAvPair), Value, AvLen); + } + + pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair); + return ntlm_av_pair_list_init(pAvPair, cbAvPair); +} + +static BOOL ntlm_av_pair_add_copy(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, + NTLM_AV_PAIR* pAvPair, size_t cbAvPair) +{ + UINT16 pair = 0; + size_t avLen = 0; + + if (!ntlm_av_pair_check(pAvPair, cbAvPair)) + return FALSE; + + if (!ntlm_av_pair_get_id(pAvPair, cbAvPair, &pair)) + return FALSE; + + if (!ntlm_av_pair_get_len(pAvPair, cbAvPair, &avLen)) + return FALSE; + + WINPR_ASSERT(avLen <= UINT16_MAX); + return ntlm_av_pair_add(pAvPairList, cbAvPairList, pair, + ntlm_av_pair_get_value_pointer(pAvPair), (UINT16)avLen); +} + +static int ntlm_get_target_computer_name(PUNICODE_STRING pName, COMPUTER_NAME_FORMAT type) +{ + char* name = NULL; + int status = -1; + DWORD nSize = 0; + CHAR* computerName = NULL; + + WINPR_ASSERT(pName); + + if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) || GetLastError() != ERROR_MORE_DATA) + return -1; + + computerName = calloc(nSize, sizeof(CHAR)); + + if (!computerName) + return -1; + + if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize)) + { + free(computerName); + return -1; + } + + if (nSize > MAX_COMPUTERNAME_LENGTH) + computerName[MAX_COMPUTERNAME_LENGTH] = '\0'; + + name = computerName; + + if (!name) + return -1; + + if (type == ComputerNameNetBIOS) + CharUpperA(name); + + size_t len = 0; + pName->Buffer = ConvertUtf8ToWCharAlloc(name, &len); + + if (!pName->Buffer || (len == 0) || (len > UINT16_MAX / sizeof(WCHAR))) + { + free(pName->Buffer); + pName->Buffer = NULL; + free(name); + return status; + } + + pName->Length = (USHORT)((len) * sizeof(WCHAR)); + pName->MaximumLength = pName->Length; + free(name); + return 1; +} + +static void ntlm_free_unicode_string(PUNICODE_STRING string) +{ + if (string) + { + if (string->Length > 0) + { + free(string->Buffer); + string->Buffer = NULL; + string->Length = 0; + string->MaximumLength = 0; + } + } +} + +/** + * From http://www.ietf.org/proceedings/72/slides/sasl-2.pdf: + * + * tls-server-end-point: + * + * The hash of the TLS server's end entity certificate as it appears, octet for octet, + * in the server's Certificate message (note that the Certificate message contains a + * certificate_list, the first element of which is the server's end entity certificate.) + * The hash function to be selected is as follows: if the certificate's signature hash + * algorithm is either MD5 or SHA-1, then use SHA-256, otherwise use the certificate's + * signature hash algorithm. + */ + +/** + * Channel Bindings sample usage: + * https://raw.github.com/mozilla/mozilla-central/master/extensions/auth/nsAuthSSPI.cpp + */ + +/* +typedef struct gss_channel_bindings_struct { + OM_uint32 initiator_addrtype; + gss_buffer_desc initiator_address; + OM_uint32 acceptor_addrtype; + gss_buffer_desc acceptor_address; + gss_buffer_desc application_data; +} *gss_channel_bindings_t; + */ + +static BOOL ntlm_md5_update_uint32_be(WINPR_DIGEST_CTX* md5, UINT32 num) +{ + BYTE be32[4]; + be32[0] = (num >> 0) & 0xFF; + be32[1] = (num >> 8) & 0xFF; + be32[2] = (num >> 16) & 0xFF; + be32[3] = (num >> 24) & 0xFF; + return winpr_Digest_Update(md5, be32, 4); +} + +static void ntlm_compute_channel_bindings(NTLM_CONTEXT* context) +{ + WINPR_DIGEST_CTX* md5 = NULL; + BYTE* ChannelBindingToken = NULL; + UINT32 ChannelBindingTokenLength = 0; + SEC_CHANNEL_BINDINGS* ChannelBindings = NULL; + + WINPR_ASSERT(context); + + ZeroMemory(context->ChannelBindingsHash, WINPR_MD5_DIGEST_LENGTH); + ChannelBindings = context->Bindings.Bindings; + + if (!ChannelBindings) + return; + + if (!(md5 = winpr_Digest_New())) + return; + + if (!winpr_Digest_Init(md5, WINPR_MD_MD5)) + goto out; + + ChannelBindingTokenLength = context->Bindings.BindingsLength - sizeof(SEC_CHANNEL_BINDINGS); + ChannelBindingToken = &((BYTE*)ChannelBindings)[ChannelBindings->dwApplicationDataOffset]; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->dwInitiatorAddrType)) + goto out; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbInitiatorLength)) + goto out; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->dwAcceptorAddrType)) + goto out; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbAcceptorLength)) + goto out; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbApplicationDataLength)) + goto out; + + if (!winpr_Digest_Update(md5, (void*)ChannelBindingToken, ChannelBindingTokenLength)) + goto out; + + if (!winpr_Digest_Final(md5, context->ChannelBindingsHash, WINPR_MD5_DIGEST_LENGTH)) + goto out; + +out: + winpr_Digest_Free(md5); +} + +static void ntlm_compute_single_host_data(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + /** + * The Single_Host_Data structure allows a client to send machine-specific information + * within an authentication exchange to services on the same machine. The client can + * produce additional information to be processed in an implementation-specific way when + * the client and server are on the same host. If the server and client platforms are + * different or if they are on different hosts, then the information MUST be ignored. + * Any fields after the MachineID field MUST be ignored on receipt. + */ + Data_Write_UINT32(&context->SingleHostData.Size, 48); + Data_Write_UINT32(&context->SingleHostData.Z4, 0); + Data_Write_UINT32(&context->SingleHostData.DataPresent, 1); + Data_Write_UINT32(&context->SingleHostData.CustomData, SECURITY_MANDATORY_MEDIUM_RID); + FillMemory(context->SingleHostData.MachineID, 32, 0xAA); +} + +BOOL ntlm_construct_challenge_target_info(NTLM_CONTEXT* context) +{ + BOOL rc = FALSE; + ULONG length = 0; + ULONG AvPairsCount = 0; + ULONG AvPairsLength = 0; + NTLM_AV_PAIR* pAvPairList = NULL; + size_t cbAvPairList = 0; + UNICODE_STRING NbDomainName = { 0 }; + UNICODE_STRING NbComputerName = { 0 }; + UNICODE_STRING DnsDomainName = { 0 }; + UNICODE_STRING DnsComputerName = { 0 }; + + WINPR_ASSERT(context); + + if (ntlm_get_target_computer_name(&NbDomainName, ComputerNameNetBIOS) < 0) + goto fail; + + NbComputerName.Buffer = NULL; + + if (ntlm_get_target_computer_name(&NbComputerName, ComputerNameNetBIOS) < 0) + goto fail; + + DnsDomainName.Buffer = NULL; + + if (ntlm_get_target_computer_name(&DnsDomainName, ComputerNameDnsDomain) < 0) + goto fail; + + DnsComputerName.Buffer = NULL; + + if (ntlm_get_target_computer_name(&DnsComputerName, ComputerNameDnsHostname) < 0) + goto fail; + + AvPairsCount = 5; + AvPairsLength = NbDomainName.Length + NbComputerName.Length + DnsDomainName.Length + + DnsComputerName.Length + 8; + length = ntlm_av_pair_list_size(AvPairsCount, AvPairsLength); + + if (!sspi_SecBufferAlloc(&context->ChallengeTargetInfo, length)) + goto fail; + + pAvPairList = (NTLM_AV_PAIR*)context->ChallengeTargetInfo.pvBuffer; + cbAvPairList = context->ChallengeTargetInfo.cbBuffer; + + if (!ntlm_av_pair_list_init(pAvPairList, cbAvPairList)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvNbDomainName, (PBYTE)NbDomainName.Buffer, + NbDomainName.Length)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvNbComputerName, + (PBYTE)NbComputerName.Buffer, NbComputerName.Length)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvDnsDomainName, + (PBYTE)DnsDomainName.Buffer, DnsDomainName.Length)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvDnsComputerName, + (PBYTE)DnsComputerName.Buffer, DnsComputerName.Length)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvTimestamp, context->Timestamp, + sizeof(context->Timestamp))) + goto fail; + + rc = TRUE; +fail: + ntlm_free_unicode_string(&NbDomainName); + ntlm_free_unicode_string(&NbComputerName); + ntlm_free_unicode_string(&DnsDomainName); + ntlm_free_unicode_string(&DnsComputerName); + return rc; +} + +BOOL ntlm_construct_authenticate_target_info(NTLM_CONTEXT* context) +{ + ULONG size = 0; + ULONG AvPairsCount = 0; + ULONG AvPairsValueLength = 0; + NTLM_AV_PAIR* AvTimestamp = NULL; + NTLM_AV_PAIR* AvNbDomainName = NULL; + NTLM_AV_PAIR* AvNbComputerName = NULL; + NTLM_AV_PAIR* AvDnsDomainName = NULL; + NTLM_AV_PAIR* AvDnsComputerName = NULL; + NTLM_AV_PAIR* AvDnsTreeName = NULL; + NTLM_AV_PAIR* ChallengeTargetInfo = NULL; + NTLM_AV_PAIR* AuthenticateTargetInfo = NULL; + size_t cbAvTimestamp = 0; + size_t cbAvNbDomainName = 0; + size_t cbAvNbComputerName = 0; + size_t cbAvDnsDomainName = 0; + size_t cbAvDnsComputerName = 0; + size_t cbAvDnsTreeName = 0; + size_t cbChallengeTargetInfo = 0; + size_t cbAuthenticateTargetInfo = 0; + + WINPR_ASSERT(context); + + AvPairsCount = 1; + AvPairsValueLength = 0; + ChallengeTargetInfo = (NTLM_AV_PAIR*)context->ChallengeTargetInfo.pvBuffer; + cbChallengeTargetInfo = context->ChallengeTargetInfo.cbBuffer; + AvNbDomainName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvNbDomainName, + &cbAvNbDomainName); + AvNbComputerName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, + MsvAvNbComputerName, &cbAvNbComputerName); + AvDnsDomainName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, + MsvAvDnsDomainName, &cbAvDnsDomainName); + AvDnsComputerName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, + MsvAvDnsComputerName, &cbAvDnsComputerName); + AvDnsTreeName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvDnsTreeName, + &cbAvDnsTreeName); + AvTimestamp = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvTimestamp, + &cbAvTimestamp); + + if (AvNbDomainName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvNbDomainName, cbAvNbDomainName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvNbDomainName */ + AvPairsValueLength += avLen; + } + + if (AvNbComputerName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvNbComputerName, cbAvNbComputerName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvNbComputerName */ + AvPairsValueLength += avLen; + } + + if (AvDnsDomainName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvDnsDomainName, cbAvDnsDomainName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvDnsDomainName */ + AvPairsValueLength += avLen; + } + + if (AvDnsComputerName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvDnsComputerName, cbAvDnsComputerName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvDnsComputerName */ + AvPairsValueLength += avLen; + } + + if (AvDnsTreeName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvDnsTreeName, cbAvDnsTreeName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvDnsTreeName */ + AvPairsValueLength += avLen; + } + + AvPairsCount++; /* MsvAvTimestamp */ + AvPairsValueLength += 8; + + if (context->UseMIC) + { + AvPairsCount++; /* MsvAvFlags */ + AvPairsValueLength += 4; + } + + if (context->SendSingleHostData) + { + AvPairsCount++; /* MsvAvSingleHost */ + ntlm_compute_single_host_data(context); + AvPairsValueLength += context->SingleHostData.Size; + } + + /** + * Extended Protection for Authentication: + * http://blogs.technet.com/b/srd/archive/2009/12/08/extended-protection-for-authentication.aspx + */ + + if (!context->SuppressExtendedProtection) + { + /** + * SEC_CHANNEL_BINDINGS structure + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd919963/ + */ + AvPairsCount++; /* MsvAvChannelBindings */ + AvPairsValueLength += 16; + ntlm_compute_channel_bindings(context); + + if (context->ServicePrincipalName.Length > 0) + { + AvPairsCount++; /* MsvAvTargetName */ + AvPairsValueLength += context->ServicePrincipalName.Length; + } + } + + size = ntlm_av_pair_list_size(AvPairsCount, AvPairsValueLength); + + if (context->NTLMv2) + size += 8; /* unknown 8-byte padding */ + + if (!sspi_SecBufferAlloc(&context->AuthenticateTargetInfo, size)) + goto fail; + + AuthenticateTargetInfo = (NTLM_AV_PAIR*)context->AuthenticateTargetInfo.pvBuffer; + cbAuthenticateTargetInfo = context->AuthenticateTargetInfo.cbBuffer; + + if (!ntlm_av_pair_list_init(AuthenticateTargetInfo, cbAuthenticateTargetInfo)) + goto fail; + + if (AvNbDomainName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvNbDomainName, + cbAvNbDomainName)) + goto fail; + } + + if (AvNbComputerName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, + AvNbComputerName, cbAvNbComputerName)) + goto fail; + } + + if (AvDnsDomainName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, + AvDnsDomainName, cbAvDnsDomainName)) + goto fail; + } + + if (AvDnsComputerName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, + AvDnsComputerName, cbAvDnsComputerName)) + goto fail; + } + + if (AvDnsTreeName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvDnsTreeName, + cbAvDnsTreeName)) + goto fail; + } + + if (AvTimestamp) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvTimestamp, + cbAvTimestamp)) + goto fail; + } + + if (context->UseMIC) + { + UINT32 flags = 0; + Data_Write_UINT32(&flags, MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK); + + if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvFlags, + (PBYTE)&flags, 4)) + goto fail; + } + + if (context->SendSingleHostData) + { + WINPR_ASSERT(context->SingleHostData.Size <= UINT16_MAX); + if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvSingleHost, + (PBYTE)&context->SingleHostData, + (UINT16)context->SingleHostData.Size)) + goto fail; + } + + if (!context->SuppressExtendedProtection) + { + if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, + MsvAvChannelBindings, context->ChannelBindingsHash, 16)) + goto fail; + + if (context->ServicePrincipalName.Length > 0) + { + if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvTargetName, + (PBYTE)context->ServicePrincipalName.Buffer, + context->ServicePrincipalName.Length)) + goto fail; + } + } + + if (context->NTLMv2) + { + NTLM_AV_PAIR* AvEOL = NULL; + AvEOL = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvEOL, NULL); + + if (!AvEOL) + goto fail; + + ZeroMemory(AvEOL, sizeof(NTLM_AV_PAIR)); + } + + return TRUE; +fail: + sspi_SecBufferFree(&context->AuthenticateTargetInfo); + return FALSE; +} diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h new file mode 100644 index 0000000..ab9da43 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h @@ -0,0 +1,42 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (AV_PAIRs) + * + * Copyright 2011-2012 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. + */ + +#ifndef WINPR_SSPI_NTLM_AV_PAIRS_H +#define WINPR_SSPI_NTLM_AV_PAIRS_H + +#include <winpr/config.h> + +#include "ntlm.h" + +#include <winpr/stream.h> + +ULONG ntlm_av_pair_list_length(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList); + +#ifdef WITH_DEBUG_NTLM +void ntlm_print_av_pair_list(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList); +#endif + +PBYTE ntlm_av_pair_get_value_pointer(NTLM_AV_PAIR* pAvPair); +NTLM_AV_PAIR* ntlm_av_pair_get(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId, + size_t* pcbAvPairListRemaining); + +BOOL ntlm_construct_challenge_target_info(NTLM_CONTEXT* context); +BOOL ntlm_construct_authenticate_target_info(NTLM_CONTEXT* context); + +#endif /* WINPR_SSPI_NTLM_AV_PAIRS_H */ diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_compute.c b/winpr/libwinpr/sspi/NTLM/ntlm_compute.c new file mode 100644 index 0000000..9c6e818 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_compute.c @@ -0,0 +1,887 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (Compute) + * + * 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. + */ + +#include <winpr/config.h> + +#include <winpr/assert.h> + +#include "ntlm.h" +#include "../sspi.h" + +#include <winpr/crt.h> +#include <winpr/sam.h> +#include <winpr/ntlm.h> +#include <winpr/print.h> +#include <winpr/crypto.h> +#include <winpr/sysinfo.h> + +#include "ntlm_compute.h" + +#include "../../log.h" +#define TAG WINPR_TAG("sspi.NTLM") + +#define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what) \ + Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \ + __func__, __FILE__, (size_t)__LINE__) + +static char NTLM_CLIENT_SIGN_MAGIC[] = "session key to client-to-server signing key magic constant"; +static char NTLM_SERVER_SIGN_MAGIC[] = "session key to server-to-client signing key magic constant"; +static char NTLM_CLIENT_SEAL_MAGIC[] = "session key to client-to-server sealing key magic constant"; +static char NTLM_SERVER_SEAL_MAGIC[] = "session key to server-to-client sealing key magic constant"; + +static const BYTE NTLM_NULL_BUFFER[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/** + * Populate VERSION structure msdn{cc236654} + * @param versionInfo A pointer to the version struct + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_get_version_info(NTLM_VERSION_INFO* versionInfo) +{ + WINPR_ASSERT(versionInfo); + +#if defined(WITH_WINPR_DEPRECATED) + OSVERSIONINFOA osVersionInfo = { 0 }; + osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); + if (!GetVersionExA(&osVersionInfo)) + return FALSE; + versionInfo->ProductMajorVersion = (UINT8)osVersionInfo.dwMajorVersion; + versionInfo->ProductMinorVersion = (UINT8)osVersionInfo.dwMinorVersion; + versionInfo->ProductBuild = (UINT16)osVersionInfo.dwBuildNumber; +#else + /* Always return fixed version number. + * + * ProductVersion is fixed since windows 10 to Major 10, Minor 0 + * ProductBuild taken from https://en.wikipedia.org/wiki/Windows_11_version_history + * with most recent (pre) release build number + */ + versionInfo->ProductMajorVersion = 10; + versionInfo->ProductMinorVersion = 0; + versionInfo->ProductBuild = 22631; +#endif + ZeroMemory(versionInfo->Reserved, sizeof(versionInfo->Reserved)); + versionInfo->NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3; + return TRUE; +} + +/** + * Read VERSION structure. msdn{cc236654} + * @param s A pointer to a stream to read + * @param versionInfo A pointer to the struct to read data to + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_read_version_info(wStream* s, NTLM_VERSION_INFO* versionInfo) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(versionInfo); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return FALSE; + + Stream_Read_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */ + Stream_Read_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */ + Stream_Read_UINT16(s, versionInfo->ProductBuild); /* ProductBuild (2 bytes) */ + Stream_Read(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */ + Stream_Read_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */ + return TRUE; +} + +/** + * Write VERSION structure. msdn{cc236654} + * @param s A pointer to the stream to write to + * @param versionInfo A pointer to the buffer to read the data from + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_write_version_info(wStream* s, const NTLM_VERSION_INFO* versionInfo) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(versionInfo); + + if (!Stream_CheckAndLogRequiredCapacityEx( + TAG, WLOG_WARN, s, 5ull + sizeof(versionInfo->Reserved), 1ull, + "%s(%s:%" PRIuz ") NTLM_VERSION_INFO", __func__, __FILE__, (size_t)__LINE__)) + return FALSE; + + Stream_Write_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */ + Stream_Write_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */ + Stream_Write_UINT16(s, versionInfo->ProductBuild); /* ProductBuild (2 bytes) */ + Stream_Write(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */ + Stream_Write_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */ + return TRUE; +} + +/** + * Print VERSION structure. msdn{cc236654} + * @param versionInfo A pointer to the struct containing the data to print + */ +#ifdef WITH_DEBUG_NTLM +void ntlm_print_version_info(const NTLM_VERSION_INFO* versionInfo) +{ + WINPR_ASSERT(versionInfo); + + WLog_VRB(TAG, "VERSION ={"); + WLog_VRB(TAG, "\tProductMajorVersion: %" PRIu8 "", versionInfo->ProductMajorVersion); + WLog_VRB(TAG, "\tProductMinorVersion: %" PRIu8 "", versionInfo->ProductMinorVersion); + WLog_VRB(TAG, "\tProductBuild: %" PRIu16 "", versionInfo->ProductBuild); + WLog_VRB(TAG, "\tReserved: 0x%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "", versionInfo->Reserved[0], + versionInfo->Reserved[1], versionInfo->Reserved[2]); + WLog_VRB(TAG, "\tNTLMRevisionCurrent: 0x%02" PRIX8 "", versionInfo->NTLMRevisionCurrent); +} +#endif + +static BOOL ntlm_read_ntlm_v2_client_challenge(wStream* s, NTLMv2_CLIENT_CHALLENGE* challenge) +{ + size_t size = 0; + WINPR_ASSERT(s); + WINPR_ASSERT(challenge); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 28)) + return FALSE; + + Stream_Read_UINT8(s, challenge->RespType); + Stream_Read_UINT8(s, challenge->HiRespType); + Stream_Read_UINT16(s, challenge->Reserved1); + Stream_Read_UINT32(s, challenge->Reserved2); + Stream_Read(s, challenge->Timestamp, 8); + Stream_Read(s, challenge->ClientChallenge, 8); + Stream_Read_UINT32(s, challenge->Reserved3); + size = Stream_Length(s) - Stream_GetPosition(s); + + if (size > UINT32_MAX) + { + WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::cbAvPairs too large, got %" PRIuz "bytes", size); + return FALSE; + } + + challenge->cbAvPairs = (UINT32)size; + challenge->AvPairs = (NTLM_AV_PAIR*)malloc(challenge->cbAvPairs); + + if (!challenge->AvPairs) + { + WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::AvPairs failed to allocate %" PRIu32 "bytes", + challenge->cbAvPairs); + return FALSE; + } + + Stream_Read(s, challenge->AvPairs, size); + return TRUE; +} + +static BOOL ntlm_write_ntlm_v2_client_challenge(wStream* s, + const NTLMv2_CLIENT_CHALLENGE* challenge) +{ + ULONG length = 0; + + WINPR_ASSERT(s); + WINPR_ASSERT(challenge); + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 28, "NTLMv2_CLIENT_CHALLENGE")) + return FALSE; + + Stream_Write_UINT8(s, challenge->RespType); + Stream_Write_UINT8(s, challenge->HiRespType); + Stream_Write_UINT16(s, challenge->Reserved1); + Stream_Write_UINT32(s, challenge->Reserved2); + Stream_Write(s, challenge->Timestamp, 8); + Stream_Write(s, challenge->ClientChallenge, 8); + Stream_Write_UINT32(s, challenge->Reserved3); + length = ntlm_av_pair_list_length(challenge->AvPairs, challenge->cbAvPairs); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, length)) + return FALSE; + + Stream_Write(s, challenge->AvPairs, length); + return TRUE; +} + +BOOL ntlm_read_ntlm_v2_response(wStream* s, NTLMv2_RESPONSE* response) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(response); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return FALSE; + + Stream_Read(s, response->Response, 16); + return ntlm_read_ntlm_v2_client_challenge(s, &(response->Challenge)); +} + +BOOL ntlm_write_ntlm_v2_response(wStream* s, const NTLMv2_RESPONSE* response) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(response); + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16ull, "NTLMv2_RESPONSE")) + return FALSE; + + Stream_Write(s, response->Response, 16); + return ntlm_write_ntlm_v2_client_challenge(s, &(response->Challenge)); +} + +/** + * Get current time, in tenths of microseconds since midnight of January 1, 1601. + * @param[out] timestamp 64-bit little-endian timestamp + */ + +void ntlm_current_time(BYTE* timestamp) +{ + FILETIME filetime = { 0 }; + ULARGE_INTEGER time64 = { 0 }; + + WINPR_ASSERT(timestamp); + + GetSystemTimeAsFileTime(&filetime); + time64.u.LowPart = filetime.dwLowDateTime; + time64.u.HighPart = filetime.dwHighDateTime; + CopyMemory(timestamp, &(time64.QuadPart), 8); +} + +/** + * Generate timestamp for AUTHENTICATE_MESSAGE. + * + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_timestamp(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + if (memcmp(context->ChallengeTimestamp, NTLM_NULL_BUFFER, 8) != 0) + CopyMemory(context->Timestamp, context->ChallengeTimestamp, 8); + else + ntlm_current_time(context->Timestamp); +} + +static BOOL ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash) +{ + BOOL rc = FALSE; + WINPR_SAM* sam = NULL; + WINPR_SAM_ENTRY* entry = NULL; + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(hash); + + credentials = context->credentials; + sam = SamOpen(context->SamFile, TRUE); + + if (!sam) + goto fail; + + entry = SamLookupUserW( + sam, (LPWSTR)credentials->identity.User, credentials->identity.UserLength * sizeof(WCHAR), + (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * sizeof(WCHAR)); + + if (!entry) + { + entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User, + credentials->identity.UserLength * sizeof(WCHAR), NULL, 0); + } + + if (!entry) + goto fail; + +#ifdef WITH_DEBUG_NTLM + WLog_VRB(TAG, "NTLM Hash:"); + winpr_HexDump(TAG, WLOG_DEBUG, entry->NtHash, 16); +#endif + NTOWFv2FromHashW(entry->NtHash, (LPWSTR)credentials->identity.User, + credentials->identity.UserLength * sizeof(WCHAR), + (LPWSTR)credentials->identity.Domain, + credentials->identity.DomainLength * sizeof(WCHAR), (BYTE*)hash); + + rc = TRUE; + +fail: + SamFreeEntry(sam, entry); + SamClose(sam); + if (!rc) + WLog_ERR(TAG, "Error: Could not find user in SAM database"); + + return rc; +} + +static int ntlm_convert_password_hash(NTLM_CONTEXT* context, BYTE* hash) +{ + char PasswordHash[32] = { 0 }; + INT64 PasswordHashLength = 0; + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(hash); + + credentials = context->credentials; + /* Password contains a password hash of length (PasswordLength - + * SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) */ + PasswordHashLength = credentials->identity.PasswordLength - SSPI_CREDENTIALS_HASH_LENGTH_OFFSET; + + WINPR_ASSERT(PasswordHashLength >= 0); + WINPR_ASSERT((size_t)PasswordHashLength < ARRAYSIZE(PasswordHash)); + if (ConvertWCharNToUtf8(credentials->identity.Password, PasswordHashLength, PasswordHash, + ARRAYSIZE(PasswordHash)) <= 0) + return -1; + + CharUpperBuffA(PasswordHash, (DWORD)PasswordHashLength); + + for (size_t i = 0; i < ARRAYSIZE(PasswordHash); i += 2) + { + BYTE hn = + (BYTE)(PasswordHash[i] > '9' ? PasswordHash[i] - 'A' + 10 : PasswordHash[i] - '0'); + BYTE ln = (BYTE)(PasswordHash[i + 1] > '9' ? PasswordHash[i + 1] - 'A' + 10 + : PasswordHash[i + 1] - '0'); + hash[i / 2] = (BYTE)((hn << 4) | ln); + } + + return 1; +} + +static BOOL ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash) +{ + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(hash); + + credentials = context->credentials; +#ifdef WITH_DEBUG_NTLM + + if (credentials) + { + WLog_VRB(TAG, "Password (length = %" PRIu32 ")", credentials->identity.PasswordLength * 2); + winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Password, + credentials->identity.PasswordLength * 2); + WLog_VRB(TAG, "Username (length = %" PRIu32 ")", credentials->identity.UserLength * 2); + winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.User, + credentials->identity.UserLength * 2); + WLog_VRB(TAG, "Domain (length = %" PRIu32 ")", credentials->identity.DomainLength * 2); + winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Domain, + credentials->identity.DomainLength * 2); + } + else + WLog_VRB(TAG, "Strange, NTLM_CONTEXT is missing valid credentials..."); + + WLog_VRB(TAG, "Workstation (length = %" PRIu16 ")", context->Workstation.Length); + winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)context->Workstation.Buffer, context->Workstation.Length); + WLog_VRB(TAG, "NTOWFv2, NTLMv2 Hash"); + winpr_HexDump(TAG, WLOG_TRACE, context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH); +#endif + + if (memcmp(context->NtlmV2Hash, NTLM_NULL_BUFFER, 16) != 0) + return TRUE; + + if (!credentials) + return FALSE; + else if (memcmp(context->NtlmHash, NTLM_NULL_BUFFER, 16) != 0) + { + NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User, + credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain, + credentials->identity.DomainLength * 2, (BYTE*)hash); + } + else if (credentials->identity.PasswordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) + { + /* Special case for WinPR: password hash */ + if (ntlm_convert_password_hash(context, context->NtlmHash) < 0) + return FALSE; + + NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User, + credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain, + credentials->identity.DomainLength * 2, (BYTE*)hash); + } + else if (credentials->identity.Password) + { + NTOWFv2W((LPWSTR)credentials->identity.Password, credentials->identity.PasswordLength * 2, + (LPWSTR)credentials->identity.User, credentials->identity.UserLength * 2, + (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * 2, + (BYTE*)hash); + } + else if (context->HashCallback) + { + int ret = 0; + SecBuffer proofValue; + SecBuffer micValue; + + if (ntlm_computeProofValue(context, &proofValue) != SEC_E_OK) + return FALSE; + + if (ntlm_computeMicValue(context, &micValue) != SEC_E_OK) + { + sspi_SecBufferFree(&proofValue); + return FALSE; + } + + ret = context->HashCallback(context->HashCallbackArg, &credentials->identity, &proofValue, + context->EncryptedRandomSessionKey, + context->AUTHENTICATE_MESSAGE.MessageIntegrityCheck, &micValue, + hash); + sspi_SecBufferFree(&proofValue); + sspi_SecBufferFree(&micValue); + return ret ? TRUE : FALSE; + } + else if (context->UseSamFileDatabase) + { + return ntlm_fetch_ntlm_v2_hash(context, hash); + } + + return TRUE; +} + +BOOL ntlm_compute_lm_v2_response(NTLM_CONTEXT* context) +{ + BYTE* response = NULL; + BYTE value[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + + WINPR_ASSERT(context); + + if (context->LmCompatibilityLevel < 2) + { + if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24)) + return FALSE; + + ZeroMemory(context->LmChallengeResponse.pvBuffer, 24); + return TRUE; + } + + /* Compute the NTLMv2 hash */ + + if (!ntlm_compute_ntlm_v2_hash(context, context->NtlmV2Hash)) + return FALSE; + + /* Concatenate the server and client challenges */ + CopyMemory(value, context->ServerChallenge, 8); + CopyMemory(&value[8], context->ClientChallenge, 8); + + if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24)) + return FALSE; + + response = (BYTE*)context->LmChallengeResponse.pvBuffer; + /* Compute the HMAC-MD5 hash of the resulting value using the NTLMv2 hash as the key */ + winpr_HMAC(WINPR_MD_MD5, (void*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, (BYTE*)value, + WINPR_MD5_DIGEST_LENGTH, (BYTE*)response, WINPR_MD5_DIGEST_LENGTH); + /* Concatenate the resulting HMAC-MD5 hash and the client challenge, giving us the LMv2 response + * (24 bytes) */ + CopyMemory(&response[16], context->ClientChallenge, 8); + return TRUE; +} + +/** + * Compute NTLMv2 Response. + * + * NTLMv2_RESPONSE msdn{cc236653} + * NTLMv2 Authentication msdn{cc236700} + * + * @param context A pointer to the NTLM context + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context) +{ + BYTE* blob = NULL; + SecBuffer ntlm_v2_temp = { 0 }; + SecBuffer ntlm_v2_temp_chal = { 0 }; + PSecBuffer TargetInfo = NULL; + + WINPR_ASSERT(context); + + TargetInfo = &context->ChallengeTargetInfo; + BOOL ret = FALSE; + + if (!sspi_SecBufferAlloc(&ntlm_v2_temp, TargetInfo->cbBuffer + 28)) + goto exit; + + ZeroMemory(ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer); + blob = (BYTE*)ntlm_v2_temp.pvBuffer; + + /* Compute the NTLMv2 hash */ + if (!ntlm_compute_ntlm_v2_hash(context, (BYTE*)context->NtlmV2Hash)) + goto exit; + + /* Construct temp */ + blob[0] = 1; /* RespType (1 byte) */ + blob[1] = 1; /* HighRespType (1 byte) */ + /* Reserved1 (2 bytes) */ + /* Reserved2 (4 bytes) */ + CopyMemory(&blob[8], context->Timestamp, 8); /* Timestamp (8 bytes) */ + CopyMemory(&blob[16], context->ClientChallenge, 8); /* ClientChallenge (8 bytes) */ + /* Reserved3 (4 bytes) */ + CopyMemory(&blob[28], TargetInfo->pvBuffer, TargetInfo->cbBuffer); +#ifdef WITH_DEBUG_NTLM + WLog_VRB(TAG, "NTLMv2 Response Temp Blob"); + winpr_HexDump(TAG, WLOG_TRACE, ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer); +#endif + + /* Concatenate server challenge with temp */ + + if (!sspi_SecBufferAlloc(&ntlm_v2_temp_chal, ntlm_v2_temp.cbBuffer + 8)) + goto exit; + + blob = (BYTE*)ntlm_v2_temp_chal.pvBuffer; + CopyMemory(blob, context->ServerChallenge, 8); + CopyMemory(&blob[8], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer); + winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, + (BYTE*)ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer, + context->NtProofString, WINPR_MD5_DIGEST_LENGTH); + + /* NtChallengeResponse, Concatenate NTProofStr with temp */ + + if (!sspi_SecBufferAlloc(&context->NtChallengeResponse, ntlm_v2_temp.cbBuffer + 16)) + goto exit; + + blob = (BYTE*)context->NtChallengeResponse.pvBuffer; + CopyMemory(blob, context->NtProofString, WINPR_MD5_DIGEST_LENGTH); + CopyMemory(&blob[16], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer); + /* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2 hash as the key */ + winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, + context->NtProofString, WINPR_MD5_DIGEST_LENGTH, context->SessionBaseKey, + WINPR_MD5_DIGEST_LENGTH); + ret = TRUE; +exit: + sspi_SecBufferFree(&ntlm_v2_temp); + sspi_SecBufferFree(&ntlm_v2_temp_chal); + return ret; +} + +/** + * Encrypt the given plain text using RC4 and the given key. + * @param key RC4 key + * @param length text length + * @param plaintext plain text + * @param ciphertext cipher text + */ + +void ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext) +{ + WINPR_RC4_CTX* rc4 = winpr_RC4_New(key, 16); + + if (rc4) + { + winpr_RC4_Update(rc4, length, plaintext, ciphertext); + winpr_RC4_Free(rc4); + } +} + +/** + * Generate client challenge (8-byte nonce). + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_client_challenge(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + /* ClientChallenge is used in computation of LMv2 and NTLMv2 responses */ + if (memcmp(context->ClientChallenge, NTLM_NULL_BUFFER, sizeof(context->ClientChallenge)) == 0) + winpr_RAND(context->ClientChallenge, sizeof(context->ClientChallenge)); +} + +/** + * Generate server challenge (8-byte nonce). + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_server_challenge(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + if (memcmp(context->ServerChallenge, NTLM_NULL_BUFFER, sizeof(context->ServerChallenge)) == 0) + winpr_RAND(context->ServerChallenge, sizeof(context->ServerChallenge)); +} + +/** + * Generate KeyExchangeKey (the 128-bit SessionBaseKey). msdn{cc236710} + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_key_exchange_key(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(sizeof(context->KeyExchangeKey) == sizeof(context->SessionBaseKey)); + + /* In NTLMv2, KeyExchangeKey is the 128-bit SessionBaseKey */ + CopyMemory(context->KeyExchangeKey, context->SessionBaseKey, sizeof(context->KeyExchangeKey)); +} + +/** + * Generate RandomSessionKey (16-byte nonce). + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_random_session_key(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + winpr_RAND(context->RandomSessionKey, sizeof(context->RandomSessionKey)); +} + +/** + * Generate ExportedSessionKey (the RandomSessionKey, exported) + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_exported_session_key(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + CopyMemory(context->ExportedSessionKey, context->RandomSessionKey, + sizeof(context->ExportedSessionKey)); +} + +/** + * Encrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key). + * @param context A pointer to the NTLM context + */ + +void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context) +{ + /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the + * KeyExchangeKey */ + WINPR_ASSERT(context); + ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey, + context->EncryptedRandomSessionKey); +} + +/** + * Decrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key). + * @param context A pointer to the NTLM context + */ + +void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the + * KeyExchangeKey */ + + /** + * if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) + * Set RandomSessionKey to RC4K(KeyExchangeKey, + * AUTHENTICATE_MESSAGE.EncryptedRandomSessionKey) else Set RandomSessionKey to KeyExchangeKey + */ + if (context->NegotiateKeyExchange) + { + WINPR_ASSERT(sizeof(context->EncryptedRandomSessionKey) == + sizeof(context->RandomSessionKey)); + ntlm_rc4k(context->KeyExchangeKey, sizeof(context->EncryptedRandomSessionKey), + context->EncryptedRandomSessionKey, context->RandomSessionKey); + } + else + { + WINPR_ASSERT(sizeof(context->RandomSessionKey) == sizeof(context->KeyExchangeKey)); + CopyMemory(context->RandomSessionKey, context->KeyExchangeKey, + sizeof(context->RandomSessionKey)); + } +} + +/** + * Generate signing key msdn{cc236711} + * + * @param exported_session_key ExportedSessionKey + * @param sign_magic Sign magic string + * @param signing_key Destination signing key + * + * @return \b TRUE for success, \b FALSE for failure + */ + +static BOOL ntlm_generate_signing_key(BYTE* exported_session_key, const SecBuffer* sign_magic, + BYTE* signing_key) +{ + BOOL rc = FALSE; + size_t length = 0; + BYTE* value = NULL; + + WINPR_ASSERT(exported_session_key); + WINPR_ASSERT(sign_magic); + WINPR_ASSERT(signing_key); + + length = WINPR_MD5_DIGEST_LENGTH + sign_magic->cbBuffer; + value = (BYTE*)malloc(length); + + if (!value) + goto out; + + /* Concatenate ExportedSessionKey with sign magic */ + CopyMemory(value, exported_session_key, WINPR_MD5_DIGEST_LENGTH); + CopyMemory(&value[WINPR_MD5_DIGEST_LENGTH], sign_magic->pvBuffer, sign_magic->cbBuffer); + + rc = winpr_Digest(WINPR_MD_MD5, value, length, signing_key, WINPR_MD5_DIGEST_LENGTH); + +out: + free(value); + return rc; +} + +/** + * Generate client signing key (ClientSigningKey). msdn{cc236711} + * @param context A pointer to the NTLM context + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context) +{ + const SecBuffer signMagic = { sizeof(NTLM_CLIENT_SIGN_MAGIC), 0, NTLM_CLIENT_SIGN_MAGIC }; + + WINPR_ASSERT(context); + return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic, + context->ClientSigningKey); +} + +/** + * Generate server signing key (ServerSigningKey). msdn{cc236711} + * @param context A pointer to the NTLM context + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context) +{ + const SecBuffer signMagic = { sizeof(NTLM_SERVER_SIGN_MAGIC), 0, NTLM_SERVER_SIGN_MAGIC }; + + WINPR_ASSERT(context); + return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic, + context->ServerSigningKey); +} + +/** + * Generate client sealing key (ClientSealingKey). msdn{cc236712} + * @param context A pointer to the NTLM context + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context) +{ + const SecBuffer sealMagic = { sizeof(NTLM_CLIENT_SEAL_MAGIC), 0, NTLM_CLIENT_SEAL_MAGIC }; + + WINPR_ASSERT(context); + return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic, + context->ClientSealingKey); +} + +/** + * Generate server sealing key (ServerSealingKey). msdn{cc236712} + * @param context A pointer to the NTLM context + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context) +{ + const SecBuffer sealMagic = { sizeof(NTLM_SERVER_SEAL_MAGIC), 0, NTLM_SERVER_SEAL_MAGIC }; + + WINPR_ASSERT(context); + return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic, + context->ServerSealingKey); +} + +/** + * Initialize RC4 stream cipher states for sealing. + * @param context A pointer to the NTLM context + */ + +BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + if (context->server) + { + context->SendSigningKey = context->ServerSigningKey; + context->RecvSigningKey = context->ClientSigningKey; + context->SendSealingKey = context->ClientSealingKey; + context->RecvSealingKey = context->ServerSealingKey; + context->SendRc4Seal = + winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey)); + context->RecvRc4Seal = + winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey)); + } + else + { + context->SendSigningKey = context->ClientSigningKey; + context->RecvSigningKey = context->ServerSigningKey; + context->SendSealingKey = context->ServerSealingKey; + context->RecvSealingKey = context->ClientSealingKey; + context->SendRc4Seal = + winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey)); + context->RecvRc4Seal = + winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey)); + } + if (!context->SendRc4Seal) + { + WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal"); + return FALSE; + } + if (!context->RecvRc4Seal) + { + WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal"); + return FALSE; + } + return TRUE; +} + +BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size) +{ + BOOL rc = FALSE; + /* + * Compute the HMAC-MD5 hash of ConcatenationOf(NEGOTIATE_MESSAGE, + * CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE) using the ExportedSessionKey + */ + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + WINPR_ASSERT(context); + WINPR_ASSERT(mic); + WINPR_ASSERT(size >= WINPR_MD5_DIGEST_LENGTH); + + memset(mic, 0, size); + if (!hmac) + return FALSE; + + if (winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->ExportedSessionKey, WINPR_MD5_DIGEST_LENGTH)) + { + winpr_HMAC_Update(hmac, (BYTE*)context->NegotiateMessage.pvBuffer, + context->NegotiateMessage.cbBuffer); + winpr_HMAC_Update(hmac, (BYTE*)context->ChallengeMessage.pvBuffer, + context->ChallengeMessage.cbBuffer); + + if (context->MessageIntegrityCheckOffset > 0) + { + const BYTE* auth = (BYTE*)context->AuthenticateMessage.pvBuffer; + const BYTE data[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + const size_t rest = context->MessageIntegrityCheckOffset + sizeof(data); + + WINPR_ASSERT(rest <= context->AuthenticateMessage.cbBuffer); + winpr_HMAC_Update(hmac, &auth[0], context->MessageIntegrityCheckOffset); + winpr_HMAC_Update(hmac, data, sizeof(data)); + winpr_HMAC_Update(hmac, &auth[rest], context->AuthenticateMessage.cbBuffer - rest); + } + else + { + winpr_HMAC_Update(hmac, (BYTE*)context->AuthenticateMessage.pvBuffer, + context->AuthenticateMessage.cbBuffer); + } + winpr_HMAC_Final(hmac, mic, WINPR_MD5_DIGEST_LENGTH); + rc = TRUE; + } + + winpr_HMAC_Free(hmac); + return rc; +} diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_compute.h b/winpr/libwinpr/sspi/NTLM/ntlm_compute.h new file mode 100644 index 0000000..006a449 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_compute.h @@ -0,0 +1,64 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (Compute) + * + * 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. + */ + +#ifndef WINPR_SSPI_NTLM_COMPUTE_H +#define WINPR_SSPI_NTLM_COMPUTE_H + +#include "ntlm.h" + +#include "ntlm_av_pairs.h" + +BOOL ntlm_get_version_info(NTLM_VERSION_INFO* versionInfo); +BOOL ntlm_read_version_info(wStream* s, NTLM_VERSION_INFO* versionInfo); +BOOL ntlm_write_version_info(wStream* s, const NTLM_VERSION_INFO* versionInfo); + +#ifdef WITH_DEBUG_NTLM +void ntlm_print_version_info(const NTLM_VERSION_INFO* versionInfo); +#endif + +BOOL ntlm_read_ntlm_v2_response(wStream* s, NTLMv2_RESPONSE* response); +BOOL ntlm_write_ntlm_v2_response(wStream* s, const NTLMv2_RESPONSE* response); + +void ntlm_output_target_name(NTLM_CONTEXT* context); +void ntlm_output_channel_bindings(NTLM_CONTEXT* context); + +void ntlm_current_time(BYTE* timestamp); +void ntlm_generate_timestamp(NTLM_CONTEXT* context); + +BOOL ntlm_compute_lm_v2_response(NTLM_CONTEXT* context); +BOOL ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context); + +void ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext); +void ntlm_generate_client_challenge(NTLM_CONTEXT* context); +void ntlm_generate_server_challenge(NTLM_CONTEXT* context); +void ntlm_generate_key_exchange_key(NTLM_CONTEXT* context); +void ntlm_generate_random_session_key(NTLM_CONTEXT* context); +void ntlm_generate_exported_session_key(NTLM_CONTEXT* context); +void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context); +void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context); + +BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context); +BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context); +BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context); +BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context); +BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context); + +BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size); + +#endif /* WINPR_AUTH_NTLM_COMPUTE_H */ diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_export.h b/winpr/libwinpr/sspi/NTLM/ntlm_export.h new file mode 100644 index 0000000..5249be2 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_export.h @@ -0,0 +1,40 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package + * + * Copyright 2021 Armin Novak <armin.novak@thincast.com> + * Copyright 2021 Thincast Technologies GmbH + * + * 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. + */ + +#ifndef WINPR_SSPI_NTLM_EXPORT_H +#define WINPR_SSPI_NTLM_EXPORT_H + +#include <winpr/sspi.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + extern const SecPkgInfoA NTLM_SecPkgInfoA; + extern const SecPkgInfoW NTLM_SecPkgInfoW; + extern const SecurityFunctionTableA NTLM_SecurityFunctionTableA; + extern const SecurityFunctionTableW NTLM_SecurityFunctionTableW; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_message.c b/winpr/libwinpr/sspi/NTLM/ntlm_message.c new file mode 100644 index 0000000..511ca47 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_message.c @@ -0,0 +1,1397 @@ +/** + * 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. + */ + +#include <winpr/config.h> + +#include "ntlm.h" +#include "../sspi.h" + +#include <winpr/crt.h> +#include <winpr/assert.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") + +#define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what) \ + Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \ + __func__, __FILE__, (size_t)__LINE__) + +static const char NTLM_SIGNATURE[8] = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' }; + +static void ntlm_free_message_fields_buffer(NTLM_MESSAGE_FIELDS* fields); + +const char* ntlm_get_negotiate_string(UINT32 flag) +{ + if (flag & NTLMSSP_NEGOTIATE_56) + return "NTLMSSP_NEGOTIATE_56"; + if (flag & NTLMSSP_NEGOTIATE_KEY_EXCH) + return "NTLMSSP_NEGOTIATE_KEY_EXCH"; + if (flag & NTLMSSP_NEGOTIATE_128) + return "NTLMSSP_NEGOTIATE_128"; + if (flag & NTLMSSP_RESERVED1) + return "NTLMSSP_RESERVED1"; + if (flag & NTLMSSP_RESERVED2) + return "NTLMSSP_RESERVED2"; + if (flag & NTLMSSP_RESERVED3) + return "NTLMSSP_RESERVED3"; + if (flag & NTLMSSP_NEGOTIATE_VERSION) + return "NTLMSSP_NEGOTIATE_VERSION"; + if (flag & NTLMSSP_RESERVED4) + return "NTLMSSP_RESERVED4"; + if (flag & NTLMSSP_NEGOTIATE_TARGET_INFO) + return "NTLMSSP_NEGOTIATE_TARGET_INFO"; + if (flag & NTLMSSP_REQUEST_NON_NT_SESSION_KEY) + return "NTLMSSP_REQUEST_NON_NT_SESSION_KEY"; + if (flag & NTLMSSP_RESERVED5) + return "NTLMSSP_RESERVED5"; + if (flag & NTLMSSP_NEGOTIATE_IDENTIFY) + return "NTLMSSP_NEGOTIATE_IDENTIFY"; + if (flag & NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY) + return "NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY"; + if (flag & NTLMSSP_RESERVED6) + return "NTLMSSP_RESERVED6"; + if (flag & NTLMSSP_TARGET_TYPE_SERVER) + return "NTLMSSP_TARGET_TYPE_SERVER"; + if (flag & NTLMSSP_TARGET_TYPE_DOMAIN) + return "NTLMSSP_TARGET_TYPE_DOMAIN"; + if (flag & NTLMSSP_NEGOTIATE_ALWAYS_SIGN) + return "NTLMSSP_NEGOTIATE_ALWAYS_SIGN"; + if (flag & NTLMSSP_RESERVED7) + return "NTLMSSP_RESERVED7"; + if (flag & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + return "NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED"; + if (flag & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED) + return "NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED"; + if (flag & NTLMSSP_NEGOTIATE_ANONYMOUS) + return "NTLMSSP_NEGOTIATE_ANONYMOUS"; + if (flag & NTLMSSP_RESERVED8) + return "NTLMSSP_RESERVED8"; + if (flag & NTLMSSP_NEGOTIATE_NTLM) + return "NTLMSSP_NEGOTIATE_NTLM"; + if (flag & NTLMSSP_RESERVED9) + return "NTLMSSP_RESERVED9"; + if (flag & NTLMSSP_NEGOTIATE_LM_KEY) + return "NTLMSSP_NEGOTIATE_LM_KEY"; + if (flag & NTLMSSP_NEGOTIATE_DATAGRAM) + return "NTLMSSP_NEGOTIATE_DATAGRAM"; + if (flag & NTLMSSP_NEGOTIATE_SEAL) + return "NTLMSSP_NEGOTIATE_SEAL"; + if (flag & NTLMSSP_NEGOTIATE_SIGN) + return "NTLMSSP_NEGOTIATE_SIGN"; + if (flag & NTLMSSP_RESERVED10) + return "NTLMSSP_RESERVED10"; + if (flag & NTLMSSP_REQUEST_TARGET) + return "NTLMSSP_REQUEST_TARGET"; + if (flag & NTLMSSP_NEGOTIATE_OEM) + return "NTLMSSP_NEGOTIATE_OEM"; + if (flag & NTLMSSP_NEGOTIATE_UNICODE) + return "NTLMSSP_NEGOTIATE_UNICODE"; + return "NTLMSSP_NEGOTIATE_UNKNOWN"; +} + +#if defined(WITH_DEBUG_NTLM) +static void ntlm_print_message_fields(const NTLM_MESSAGE_FIELDS* fields, const char* name) +{ + WINPR_ASSERT(fields); + WINPR_ASSERT(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); +} + +static void ntlm_print_negotiate_flags(UINT32 flags) +{ + WLog_VRB(TAG, "negotiateFlags \"0x%08" PRIX32 "\"", flags); + + for (int i = 31; i >= 0; i--) + { + if ((flags >> i) & 1) + { + const char* str = ntlm_get_negotiate_string(1 << i); + WLog_VRB(TAG, "\t%s (%d),", str, (31 - i)); + } + } +} + +static void ntlm_print_negotiate_message(const SecBuffer* NegotiateMessage, + const NTLM_NEGOTIATE_MESSAGE* message) +{ + WINPR_ASSERT(NegotiateMessage); + WINPR_ASSERT(message); + + WLog_VRB(TAG, "NEGOTIATE_MESSAGE (length = %" PRIu32 ")", NegotiateMessage->cbBuffer); + winpr_HexDump(TAG, WLOG_TRACE, NegotiateMessage->pvBuffer, NegotiateMessage->cbBuffer); + ntlm_print_negotiate_flags(message->NegotiateFlags); + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + ntlm_print_version_info(&(message->Version)); +} + +static void ntlm_print_challenge_message(const SecBuffer* ChallengeMessage, + const NTLM_CHALLENGE_MESSAGE* message, + const SecBuffer* ChallengeTargetInfo) +{ + WINPR_ASSERT(ChallengeMessage); + WINPR_ASSERT(message); + + WLog_VRB(TAG, "CHALLENGE_MESSAGE (length = %" PRIu32 ")", ChallengeMessage->cbBuffer); + winpr_HexDump(TAG, WLOG_TRACE, ChallengeMessage->pvBuffer, 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"); + + if (ChallengeTargetInfo && (ChallengeTargetInfo->cbBuffer > 0)) + { + WLog_VRB(TAG, "ChallengeTargetInfo (%" PRIu32 "):", ChallengeTargetInfo->cbBuffer); + ntlm_print_av_pair_list(ChallengeTargetInfo->pvBuffer, ChallengeTargetInfo->cbBuffer); + } +} + +static void ntlm_print_authenticate_message(const SecBuffer* AuthenticateMessage, + const NTLM_AUTHENTICATE_MESSAGE* message, UINT32 flags, + const SecBuffer* AuthenticateTargetInfo) +{ + WINPR_ASSERT(AuthenticateMessage); + WINPR_ASSERT(message); + + WLog_VRB(TAG, "AUTHENTICATE_MESSAGE (length = %" PRIu32 ")", AuthenticateMessage->cbBuffer); + winpr_HexDump(TAG, WLOG_TRACE, AuthenticateMessage->pvBuffer, AuthenticateMessage->cbBuffer); + ntlm_print_negotiate_flags(message->NegotiateFlags); + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + ntlm_print_version_info(&(message->Version)); + + if (AuthenticateTargetInfo && (AuthenticateTargetInfo->cbBuffer > 0)) + { + WLog_VRB(TAG, "AuthenticateTargetInfo (%" PRIu32 "):", AuthenticateTargetInfo->cbBuffer); + ntlm_print_av_pair_list(AuthenticateTargetInfo->pvBuffer, 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 (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK) + { + WLog_VRB(TAG, "MessageIntegrityCheck (length = 16)"); + winpr_HexDump(TAG, WLOG_TRACE, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck)); + } +} + +static void ntlm_print_authentication_complete(const NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + 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 + +static BOOL ntlm_read_message_header(wStream* s, NTLM_MESSAGE_HEADER* header, UINT32 expected) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return FALSE; + + Stream_Read(s, header->Signature, 8); + Stream_Read_UINT32(s, header->MessageType); + + if (strncmp((char*)header->Signature, NTLM_SIGNATURE, 8) != 0) + { + WLog_ERR(TAG, "NTLM_MESSAGE_HEADER Invalid signature, got %s, expected %s", + header->Signature, NTLM_SIGNATURE); + return FALSE; + } + + if (header->MessageType != expected) + { + WLog_ERR(TAG, "NTLM_MESSAGE_HEADER Invalid message tyep, got %s, expected %s", + ntlm_message_type_string(header->MessageType), ntlm_message_type_string(expected)); + return FALSE; + } + + return TRUE; +} + +static BOOL ntlm_write_message_header(wStream* s, const NTLM_MESSAGE_HEADER* header) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, sizeof(NTLM_SIGNATURE) + 4ull, + "NTLM_MESSAGE_HEADER::header")) + return FALSE; + + Stream_Write(s, header->Signature, sizeof(NTLM_SIGNATURE)); + Stream_Write_UINT32(s, header->MessageType); + + return TRUE; +} + +static BOOL ntlm_populate_message_header(NTLM_MESSAGE_HEADER* header, UINT32 MessageType) +{ + WINPR_ASSERT(header); + + CopyMemory(header->Signature, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); + header->MessageType = MessageType; + return TRUE; +} + +static BOOL ntlm_read_message_fields(wStream* s, NTLM_MESSAGE_FIELDS* fields) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(fields); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return FALSE; + + ntlm_free_message_fields_buffer(fields); + + 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 TRUE; +} + +static BOOL ntlm_write_message_fields(wStream* s, const NTLM_MESSAGE_FIELDS* fields) +{ + UINT16 MaxLen = 0; + WINPR_ASSERT(s); + WINPR_ASSERT(fields); + + MaxLen = fields->MaxLen; + if (fields->MaxLen < 1) + MaxLen = fields->Len; + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, (s), 8, "NTLM_MESSAGE_FIELDS::header")) + return FALSE; + + Stream_Write_UINT16(s, fields->Len); /* Len (2 bytes) */ + Stream_Write_UINT16(s, MaxLen); /* MaxLen (2 bytes) */ + Stream_Write_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */ + return TRUE; +} + +static BOOL ntlm_read_message_fields_buffer(wStream* s, NTLM_MESSAGE_FIELDS* fields) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(fields); + + if (fields->Len > 0) + { + const UINT32 offset = fields->BufferOffset + fields->Len; + + if (fields->BufferOffset > UINT32_MAX - fields->Len) + { + WLog_ERR(TAG, + "NTLM_MESSAGE_FIELDS::BufferOffset %" PRIu32 + " too large, maximum allowed is %" PRIu32, + fields->BufferOffset, UINT32_MAX - fields->Len); + return FALSE; + } + + if (offset > Stream_Length(s)) + { + WLog_ERR(TAG, + "NTLM_MESSAGE_FIELDS::Buffer offset %" PRIu32 " beyond received data %" PRIuz, + offset, Stream_Length(s)); + return FALSE; + } + + fields->Buffer = (PBYTE)malloc(fields->Len); + + if (!fields->Buffer) + { + WLog_ERR(TAG, "NTLM_MESSAGE_FIELDS::Buffer allocation of %" PRIu16 "bytes failed", + fields->Len); + return FALSE; + } + + Stream_SetPosition(s, fields->BufferOffset); + Stream_Read(s, fields->Buffer, fields->Len); + } + + return TRUE; +} + +static BOOL ntlm_write_message_fields_buffer(wStream* s, const NTLM_MESSAGE_FIELDS* fields) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(fields); + + if (fields->Len > 0) + { + Stream_SetPosition(s, fields->BufferOffset); + if (!NTLM_CheckAndLogRequiredCapacity(TAG, (s), fields->Len, "NTLM_MESSAGE_FIELDS::Len")) + return FALSE; + + Stream_Write(s, fields->Buffer, fields->Len); + } + return TRUE; +} + +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; + } + } +} + +static BOOL ntlm_read_negotiate_flags(wStream* s, UINT32* flags, UINT32 required, const char* name) +{ + UINT32 NegotiateFlags = 0; + char buffer[1024] = { 0 }; + WINPR_ASSERT(s); + WINPR_ASSERT(flags); + WINPR_ASSERT(name); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT32(s, NegotiateFlags); /* NegotiateFlags (4 bytes) */ + + if ((NegotiateFlags & required) != required) + { + WLog_ERR(TAG, "%s::NegotiateFlags invalid flags 0x08%" PRIx32 ", 0x%08" PRIx32 " required", + name, NegotiateFlags, required); + return FALSE; + } + + WLog_DBG(TAG, "Read flags %s", + ntlm_negotiate_flags_string(buffer, ARRAYSIZE(buffer), NegotiateFlags)); + *flags = NegotiateFlags; + return TRUE; +} + +static BOOL ntlm_write_negotiate_flags(wStream* s, UINT32 flags, const char* name) +{ + char buffer[1024] = { 0 }; + WINPR_ASSERT(s); + WINPR_ASSERT(name); + + if (!Stream_CheckAndLogRequiredCapacityEx(TAG, WLOG_WARN, s, 4ull, 1ull, + "%s(%s:%" PRIuz ") %s::NegotiateFlags", __func__, + __FILE__, (size_t)__LINE__, name)) + return FALSE; + + WLog_DBG(TAG, "Write flags %s", ntlm_negotiate_flags_string(buffer, ARRAYSIZE(buffer), flags)); + Stream_Write_UINT32(s, flags); /* NegotiateFlags (4 bytes) */ + return TRUE; +} + +static BOOL ntlm_read_message_integrity_check(wStream* s, size_t* offset, BYTE* data, size_t size, + const char* name) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(offset); + WINPR_ASSERT(data); + WINPR_ASSERT(size == WINPR_MD5_DIGEST_LENGTH); + WINPR_ASSERT(name); + + *offset = Stream_GetPosition(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, size)) + return FALSE; + + Stream_Read(s, data, size); + return TRUE; +} + +static BOOL ntlm_write_message_integrity_check(wStream* s, size_t offset, const BYTE* data, + size_t size, const char* name) +{ + size_t pos = 0; + + WINPR_ASSERT(s); + WINPR_ASSERT(data); + WINPR_ASSERT(size == WINPR_MD5_DIGEST_LENGTH); + WINPR_ASSERT(name); + + pos = Stream_GetPosition(s); + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, offset, "MessageIntegrityCheck::offset")) + return FALSE; + + Stream_SetPosition(s, offset); + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, size, "MessageIntegrityCheck::size")) + return FALSE; + + Stream_Write(s, data, size); + Stream_SetPosition(s, pos); + return TRUE; +} + +SECURITY_STATUS ntlm_read_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer) +{ + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + const NTLM_NEGOTIATE_MESSAGE empty = { 0 }; + NTLM_NEGOTIATE_MESSAGE* message = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + message = &context->NEGOTIATE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_NEGOTIATE)) + return SEC_E_INVALID_TOKEN; + + if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, + NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_NEGOTIATE_UNICODE, + "NTLM_NEGOTIATE_MESSAGE")) + return SEC_E_INVALID_TOKEN; + + context->NegotiateFlags = message->NegotiateFlags; + + /* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */ + // if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED) + { + if (!ntlm_read_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */ + return SEC_E_INVALID_TOKEN; + } + + /* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */ + // if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + { + if (!ntlm_read_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */ + return SEC_E_INVALID_TOKEN; + } + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */ + return SEC_E_INVALID_TOKEN; + } + + if (!ntlm_read_message_fields_buffer(s, &message->DomainName)) + return SEC_E_INVALID_TOKEN; + + if (!ntlm_read_message_fields_buffer(s, &message->Workstation)) + 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)) + return SEC_E_INTERNAL_ERROR; + + CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer); + context->NegotiateMessage.BufferType = buffer->BufferType; +#if defined(WITH_DEBUG_NTLM) + ntlm_print_negotiate_message(&context->NegotiateMessage, message); +#endif + ntlm_change_state(context, NTLM_STATE_CHALLENGE); + return SEC_I_CONTINUE_NEEDED; +} + +SECURITY_STATUS ntlm_write_NegotiateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer) +{ + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + const NTLM_NEGOTIATE_MESSAGE empty = { 0 }; + NTLM_NEGOTIATE_MESSAGE* message = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + message = &context->NEGOTIATE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_NEGOTIATE)) + return SEC_E_INTERNAL_ERROR; + + 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) */ + if (!ntlm_write_message_header(s, &message->header)) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_NEGOTIATE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + + /* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */ + /* DomainNameFields (8 bytes) */ + if (!ntlm_write_message_fields(s, &(message->DomainName))) + return SEC_E_INTERNAL_ERROR; + + /* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */ + /* WorkstationFields (8 bytes) */ + if (!ntlm_write_message_fields(s, &(message->Workstation))) + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_write_version_info(s, &(message->Version))) + return SEC_E_INTERNAL_ERROR; + } + + length = Stream_GetPosition(s); + WINPR_ASSERT(length <= ULONG_MAX); + buffer->cbBuffer = (ULONG)length; + + if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length)) + return SEC_E_INTERNAL_ERROR; + + CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer); + context->NegotiateMessage.BufferType = buffer->BufferType; +#if defined(WITH_DEBUG_NTLM) + ntlm_print_negotiate_message(&context->NegotiateMessage, message); +#endif + ntlm_change_state(context, NTLM_STATE_CHALLENGE); + return SEC_I_CONTINUE_NEEDED; +} + +SECURITY_STATUS ntlm_read_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer) +{ + SECURITY_STATUS status = SEC_E_INVALID_TOKEN; + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + size_t StartOffset = 0; + size_t PayloadOffset = 0; + NTLM_AV_PAIR* AvTimestamp = NULL; + const NTLM_CHALLENGE_MESSAGE empty = { 0 }; + NTLM_CHALLENGE_MESSAGE* message = NULL; + + if (!context || !buffer) + return SEC_E_INTERNAL_ERROR; + + ntlm_generate_client_challenge(context); + message = &context->CHALLENGE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + StartOffset = Stream_GetPosition(s); + + if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_CHALLENGE)) + goto fail; + + if (!ntlm_read_message_fields(s, &(message->TargetName))) /* TargetNameFields (8 bytes) */ + goto fail; + + if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, 0, "NTLM_CHALLENGE_MESSAGE")) + goto fail; + + context->NegotiateFlags = message->NegotiateFlags; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + goto fail; + + Stream_Read(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */ + CopyMemory(context->ServerChallenge, message->ServerChallenge, 8); + Stream_Read(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */ + + if (!ntlm_read_message_fields(s, &(message->TargetInfo))) /* TargetInfoFields (8 bytes) */ + goto fail; + + if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_read_version_info(s, &(message->Version))) /* 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))) + goto fail; + } + + if (message->TargetInfo.Len > 0) + { + size_t cbAvTimestamp = 0; + + if (!ntlm_read_message_fields_buffer(s, &(message->TargetInfo))) + 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); +#if defined(WITH_DEBUG_NTLM) + ntlm_print_challenge_message(&context->ChallengeMessage, message, NULL); +#endif + /* AV_PAIRs */ + + if (context->NTLMv2) + { + if (!ntlm_construct_authenticate_target_info(context)) + 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)) /* LmChallengeResponse */ + goto fail; + + if (!ntlm_compute_ntlm_v2_response(context)) /* 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 */ + if (!ntlm_generate_client_signing_key(context)) + goto fail; + if (!ntlm_generate_server_signing_key(context)) + goto fail; + /* Generate sealing keys */ + if (!ntlm_generate_client_sealing_key(context)) + goto fail; + if (!ntlm_generate_server_sealing_key(context)) + goto fail; + /* Initialize RC4 seal state using client sealing key */ + if (!ntlm_init_rc4_seal_states(context)) + goto fail; +#if defined(WITH_DEBUG_NTLM) + ntlm_print_authentication_complete(context); +#endif + ntlm_change_state(context, NTLM_STATE_AUTHENTICATE); + status = SEC_I_CONTINUE_NEEDED; +fail: + ntlm_free_message_fields_buffer(&(message->TargetName)); + return status; +} + +SECURITY_STATUS ntlm_write_ChallengeMessage(NTLM_CONTEXT* context, const PSecBuffer buffer) +{ + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + UINT32 PayloadOffset = 0; + const NTLM_CHALLENGE_MESSAGE empty = { 0 }; + NTLM_CHALLENGE_MESSAGE* message = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + message = &context->CHALLENGE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticInit(&sbuffer, 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)) /* TargetInfo */ + return SEC_E_INTERNAL_ERROR; + + CopyMemory(message->ServerChallenge, context->ServerChallenge, 8); /* ServerChallenge */ + message->NegotiateFlags = context->NegotiateFlags; + if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_CHALLENGE)) + return SEC_E_INTERNAL_ERROR; + + /* Message Header (12 bytes) */ + if (!ntlm_write_message_header(s, &message->header)) + return SEC_E_INTERNAL_ERROR; + + 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) */ + if (!ntlm_write_message_fields(s, &(message->TargetName))) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_CHALLENGE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16, "NTLM_CHALLENGE_MESSAGE::ServerChallenge")) + return SEC_E_INTERNAL_ERROR; + + Stream_Write(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */ + Stream_Write(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */ + + /* TargetInfoFields (8 bytes) */ + if (!ntlm_write_message_fields(s, &(message->TargetInfo))) + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_write_version_info(s, &(message->Version))) /* Version (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + } + + /* Payload (variable) */ + if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET) + { + if (!ntlm_write_message_fields_buffer(s, &(message->TargetName))) + return SEC_E_INTERNAL_ERROR; + } + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO) + { + if (!ntlm_write_message_fields_buffer(s, &(message->TargetInfo))) + return SEC_E_INTERNAL_ERROR; + } + + length = Stream_GetPosition(s); + WINPR_ASSERT(length <= ULONG_MAX); + buffer->cbBuffer = (ULONG)length; + + if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length)) + return SEC_E_INTERNAL_ERROR; + + CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s), length); +#if defined(WITH_DEBUG_NTLM) + ntlm_print_challenge_message(&context->ChallengeMessage, message, + &context->ChallengeTargetInfo); +#endif + ntlm_change_state(context, NTLM_STATE_AUTHENTICATE); + return SEC_I_CONTINUE_NEEDED; +} + +SECURITY_STATUS ntlm_read_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer) +{ + SECURITY_STATUS status = SEC_E_INVALID_TOKEN; + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + UINT32 flags = 0; + NTLM_AV_PAIR* AvFlags = NULL; + size_t PayloadBufferOffset = 0; + const NTLM_AUTHENTICATE_MESSAGE empty = { 0 }; + NTLM_AUTHENTICATE_MESSAGE* message = NULL; + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + credentials = context->credentials; + WINPR_ASSERT(credentials); + + message = &context->AUTHENTICATE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_AUTHENTICATE)) + goto fail; + + if (!ntlm_read_message_fields( + s, &(message->LmChallengeResponse))) /* LmChallengeResponseFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields( + s, &(message->NtChallengeResponse))) /* NtChallengeResponseFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields(s, &(message->UserName))) /* UserNameFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields( + s, + &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKeyFields (8 bytes) */ + goto fail; + + if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, 0, "NTLM_AUTHENTICATE_MESSAGE")) + goto fail; + + 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))) /* Version (8 bytes) */ + goto fail; + } + + PayloadBufferOffset = Stream_GetPosition(s); + + status = SEC_E_INTERNAL_ERROR; + if (!ntlm_read_message_fields_buffer(s, &(message->DomainName))) /* DomainName */ + goto fail; + + if (!ntlm_read_message_fields_buffer(s, &(message->UserName))) /* UserName */ + goto fail; + + if (!ntlm_read_message_fields_buffer(s, &(message->Workstation))) /* Workstation */ + goto fail; + + if (!ntlm_read_message_fields_buffer(s, + &(message->LmChallengeResponse))) /* LmChallengeResponse */ + goto fail; + + if (!ntlm_read_message_fields_buffer(s, + &(message->NtChallengeResponse))) /* NtChallengeResponse */ + goto fail; + + if (message->NtChallengeResponse.Len > 0) + { + size_t cbAvFlags = 0; + wStream ssbuffer; + wStream* snt = Stream_StaticConstInit(&ssbuffer, message->NtChallengeResponse.Buffer, + message->NtChallengeResponse.Len); + + if (!snt) + goto fail; + + status = SEC_E_INVALID_TOKEN; + if (!ntlm_read_ntlm_v2_response(snt, &(context->NTLMv2Response))) + 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))) /* 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) + { + status = SEC_E_INVALID_TOKEN; + if (!ntlm_read_message_integrity_check( + s, &context->MessageIntegrityCheckOffset, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck), "NTLM_AUTHENTICATE_MESSAGE")) + goto fail; + } + + status = SEC_E_INTERNAL_ERROR; + +#if defined(WITH_DEBUG_NTLM) + ntlm_print_authenticate_message(&context->AuthenticateMessage, message, flags, NULL); +#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; + } + + if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY) + { + if (!ntlm_compute_lm_v2_response(context)) /* LmChallengeResponse */ + return SEC_E_INTERNAL_ERROR; + } + + if (!ntlm_compute_ntlm_v2_response(context)) /* 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) + { + BYTE messageIntegrityCheck[16] = { 0 }; + + ntlm_compute_message_integrity_check(context, messageIntegrityCheck, + sizeof(messageIntegrityCheck)); + CopyMemory( + &((PBYTE)context->AuthenticateMessage.pvBuffer)[context->MessageIntegrityCheckOffset], + message->MessageIntegrityCheck, sizeof(message->MessageIntegrityCheck)); + + if (memcmp(messageIntegrityCheck, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck)) != 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 */ + if (!ntlm_generate_client_signing_key(context)) + return SEC_E_INTERNAL_ERROR; + if (!ntlm_generate_server_signing_key(context)) + return SEC_E_INTERNAL_ERROR; + /* Generate sealing keys */ + if (!ntlm_generate_client_sealing_key(context)) + return SEC_E_INTERNAL_ERROR; + if (!ntlm_generate_server_sealing_key(context)) + return SEC_E_INTERNAL_ERROR; + /* Initialize RC4 seal state */ + if (!ntlm_init_rc4_seal_states(context)) + return SEC_E_INTERNAL_ERROR; +#if defined(WITH_DEBUG_NTLM) + ntlm_print_authentication_complete(context); +#endif + ntlm_change_state(context, 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; + +fail: + return status; +} + +/** + * Send NTLMSSP AUTHENTICATE_MESSAGE. msdn{cc236643} + * + * @param context Pointer to the NTLM context + * @param buffer The buffer to write + */ + +SECURITY_STATUS ntlm_write_AuthenticateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer) +{ + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + UINT32 PayloadBufferOffset = 0; + const NTLM_AUTHENTICATE_MESSAGE empty = { 0 }; + NTLM_AUTHENTICATE_MESSAGE* message = NULL; + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + credentials = context->credentials; + WINPR_ASSERT(credentials); + + message = &context->AUTHENTICATE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticInit(&sbuffer, 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; + if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_AUTHENTICATE)) + return SEC_E_INVALID_TOKEN; + if (!ntlm_write_message_header(s, &message->header)) /* Message Header (12 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields( + s, &(message->LmChallengeResponse))) /* LmChallengeResponseFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields( + s, &(message->NtChallengeResponse))) /* NtChallengeResponseFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields(s, &(message->UserName))) /* UserNameFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields( + s, + &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKeyFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_AUTHENTICATE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_write_version_info(s, &(message->Version))) /* Version (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + } + + if (context->UseMIC) + { + const BYTE data[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + + context->MessageIntegrityCheckOffset = Stream_GetPosition(s); + if (!ntlm_write_message_integrity_check(s, Stream_GetPosition(s), data, sizeof(data), + "NTLM_AUTHENTICATE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + } + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED) + { + if (!ntlm_write_message_fields_buffer(s, &(message->DomainName))) /* DomainName */ + return SEC_E_INTERNAL_ERROR; + } + + if (!ntlm_write_message_fields_buffer(s, &(message->UserName))) /* UserName */ + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + { + if (!ntlm_write_message_fields_buffer(s, &(message->Workstation))) /* Workstation */ + return SEC_E_INTERNAL_ERROR; + } + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY) + { + if (!ntlm_write_message_fields_buffer( + s, &(message->LmChallengeResponse))) /* LmChallengeResponse */ + return SEC_E_INTERNAL_ERROR; + } + if (!ntlm_write_message_fields_buffer( + s, &(message->NtChallengeResponse))) /* NtChallengeResponse */ + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) + { + if (!ntlm_write_message_fields_buffer( + s, &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKey */ + return SEC_E_INTERNAL_ERROR; + } + + length = Stream_GetPosition(s); + WINPR_ASSERT(length <= ULONG_MAX); + + if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length)) + 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, + sizeof(message->MessageIntegrityCheck)); + if (!ntlm_write_message_integrity_check( + s, context->MessageIntegrityCheckOffset, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck), "NTLM_AUTHENTICATE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + } + +#if defined(WITH_DEBUG_NTLM) + ntlm_print_authenticate_message(&context->AuthenticateMessage, message, + context->UseMIC ? MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK : 0, + &context->AuthenticateTargetInfo); +#endif + ntlm_change_state(context, NTLM_STATE_FINAL); + return SEC_E_OK; +} diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_message.h b/winpr/libwinpr/sspi/NTLM/ntlm_message.h new file mode 100644 index 0000000..58ff35d --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_message.h @@ -0,0 +1,36 @@ +/** + * 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. + */ + +#ifndef WINPR_SSPI_NTLM_MESSAGE_H +#define WINPR_SSPI_NTLM_MESSAGE_H + +#include "ntlm.h" + +SECURITY_STATUS ntlm_read_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer); +SECURITY_STATUS ntlm_write_NegotiateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer); +SECURITY_STATUS ntlm_read_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer); +SECURITY_STATUS ntlm_write_ChallengeMessage(NTLM_CONTEXT* context, const PSecBuffer buffer); +SECURITY_STATUS ntlm_read_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer); +SECURITY_STATUS ntlm_write_AuthenticateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer); + +SECURITY_STATUS ntlm_server_AuthenticateComplete(NTLM_CONTEXT* context); + +const char* ntlm_get_negotiate_string(UINT32 flag); + +#endif /* WINPR_SSPI_NTLM_MESSAGE_H */ |