Coverage Report

Created: 2026-09-14 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/sspi/NTLM/ntlm_compute.c
Line
Count
Source
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * NTLM Security Package (Compute)
4
 *
5
 * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <winpr/config.h>
21
22
#include <winpr/assert.h>
23
24
#include "ntlm.h"
25
#include "../sspi.h"
26
27
#include <winpr/crt.h>
28
#include <winpr/sam.h>
29
#include <winpr/ntlm.h>
30
#include <winpr/print.h>
31
#include <winpr/crypto.h>
32
#include <winpr/sysinfo.h>
33
34
#include "ntlm_compute.h"
35
36
#include "../../log.h"
37
1.81k
#define TAG WINPR_TAG("sspi.NTLM")
38
39
#define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what)                                    \
40
0
  Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \
41
0
                                       __func__, __FILE__, (size_t)__LINE__)
42
43
static char NTLM_CLIENT_SIGN_MAGIC[] = "session key to client-to-server signing key magic constant";
44
static char NTLM_SERVER_SIGN_MAGIC[] = "session key to server-to-client signing key magic constant";
45
static char NTLM_CLIENT_SEAL_MAGIC[] = "session key to client-to-server sealing key magic constant";
46
static char NTLM_SERVER_SEAL_MAGIC[] = "session key to server-to-client sealing key magic constant";
47
48
static const BYTE NTLM_NULL_BUFFER[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
49
                                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
50
51
/**
52
 * Populate VERSION structure msdn{cc236654}
53
 * @param versionInfo A pointer to the version struct
54
 *
55
 * @return \b TRUE for success, \b FALSE for failure
56
 */
57
58
BOOL ntlm_get_version_info(NTLM_VERSION_INFO* versionInfo)
59
1.84k
{
60
1.84k
  WINPR_ASSERT(versionInfo);
61
62
#if defined(WITH_WINPR_DEPRECATED)
63
  OSVERSIONINFOA osVersionInfo = WINPR_C_ARRAY_INIT;
64
  osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
65
  if (!GetVersionExA(&osVersionInfo))
66
    return FALSE;
67
  versionInfo->ProductMajorVersion = (UINT8)osVersionInfo.dwMajorVersion;
68
  versionInfo->ProductMinorVersion = (UINT8)osVersionInfo.dwMinorVersion;
69
  versionInfo->ProductBuild = (UINT16)osVersionInfo.dwBuildNumber;
70
#else
71
  /* Always return fixed version number.
72
   *
73
   * ProductVersion is fixed since windows 10 to Major 10, Minor 0
74
   * ProductBuild taken from https://en.wikipedia.org/wiki/Windows_11_version_history
75
   * with most recent (pre) release build number
76
   */
77
1.84k
  versionInfo->ProductMajorVersion = 10;
78
1.84k
  versionInfo->ProductMinorVersion = 0;
79
1.84k
  versionInfo->ProductBuild = 22631;
80
1.84k
#endif
81
1.84k
  ZeroMemory(versionInfo->Reserved, sizeof(versionInfo->Reserved));
82
1.84k
  versionInfo->NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3;
83
1.84k
  return TRUE;
84
1.84k
}
85
86
/**
87
 * Read VERSION structure. msdn{cc236654}
88
 * @param s A pointer to a stream to read
89
 * @param versionInfo A pointer to the struct to read data to
90
 *
91
 * @return \b TRUE for success, \b FALSE for failure
92
 */
93
94
BOOL ntlm_read_version_info(wStream* s, NTLM_VERSION_INFO* versionInfo)
95
757
{
96
757
  WINPR_ASSERT(s);
97
757
  WINPR_ASSERT(versionInfo);
98
99
757
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
100
8
    return FALSE;
101
102
749
  Stream_Read_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */
103
749
  Stream_Read_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */
104
749
  Stream_Read_UINT16(s, versionInfo->ProductBuild);       /* ProductBuild (2 bytes) */
105
749
  Stream_Read(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */
106
749
  Stream_Read_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */
107
749
  return TRUE;
108
757
}
109
110
/**
111
 * Write VERSION structure. msdn{cc236654}
112
 * @param s A pointer to the stream to write to
113
 * @param versionInfo A pointer to the buffer to read the data from
114
 *
115
 * @return \b TRUE for success, \b FALSE for failure
116
 */
117
118
BOOL ntlm_write_version_info(wStream* s, const NTLM_VERSION_INFO* versionInfo)
119
1.81k
{
120
1.81k
  WINPR_ASSERT(s);
121
1.81k
  WINPR_ASSERT(versionInfo);
122
123
1.81k
  if (!Stream_CheckAndLogRequiredCapacityEx(
124
1.81k
          TAG, WLOG_WARN, s, 5ull + sizeof(versionInfo->Reserved), 1ull,
125
1.81k
          "%s(%s:%" PRIuz ") NTLM_VERSION_INFO", __func__, __FILE__, (size_t)__LINE__))
126
0
    return FALSE;
127
128
1.81k
  Stream_Write_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */
129
1.81k
  Stream_Write_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */
130
1.81k
  Stream_Write_UINT16(s, versionInfo->ProductBuild);       /* ProductBuild (2 bytes) */
131
1.81k
  Stream_Write(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */
132
1.81k
  Stream_Write_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */
133
1.81k
  return TRUE;
134
1.81k
}
135
136
/**
137
 * Print VERSION structure. msdn{cc236654}
138
 * @param versionInfo A pointer to the struct containing the data to print
139
 */
140
#ifdef WITH_DEBUG_NTLM
141
void ntlm_print_version_info(const NTLM_VERSION_INFO* versionInfo)
142
{
143
  WINPR_ASSERT(versionInfo);
144
145
  WLog_VRB(TAG, "VERSION ={");
146
  WLog_VRB(TAG, "\tProductMajorVersion: %" PRIu8 "", versionInfo->ProductMajorVersion);
147
  WLog_VRB(TAG, "\tProductMinorVersion: %" PRIu8 "", versionInfo->ProductMinorVersion);
148
  WLog_VRB(TAG, "\tProductBuild: %" PRIu16 "", versionInfo->ProductBuild);
149
  WLog_VRB(TAG, "\tReserved: 0x%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "", versionInfo->Reserved[0],
150
           versionInfo->Reserved[1], versionInfo->Reserved[2]);
151
  WLog_VRB(TAG, "\tNTLMRevisionCurrent: 0x%02" PRIX8 "", versionInfo->NTLMRevisionCurrent);
152
}
153
#endif
154
155
static BOOL ntlm_read_ntlm_v2_client_challenge(wStream* s, NTLMv2_CLIENT_CHALLENGE* challenge)
156
256
{
157
256
  size_t size = 0;
158
256
  WINPR_ASSERT(s);
159
256
  WINPR_ASSERT(challenge);
160
161
256
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 28))
162
3
    return FALSE;
163
164
253
  Stream_Read_UINT8(s, challenge->RespType);
165
253
  Stream_Read_UINT8(s, challenge->HiRespType);
166
253
  Stream_Read_UINT16(s, challenge->Reserved1);
167
253
  Stream_Read_UINT32(s, challenge->Reserved2);
168
253
  Stream_Read(s, challenge->Timestamp, 8);
169
253
  Stream_Read(s, challenge->ClientChallenge, 8);
170
253
  Stream_Read_UINT32(s, challenge->Reserved3);
171
253
  size = Stream_Length(s) - Stream_GetPosition(s);
172
173
253
  if (size > UINT32_MAX)
174
0
  {
175
0
    WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::cbAvPairs too large, got %" PRIuz "bytes", size);
176
0
    return FALSE;
177
0
  }
178
179
253
  challenge->cbAvPairs = (UINT32)size;
180
253
  challenge->AvPairs = (NTLM_AV_PAIR*)malloc(challenge->cbAvPairs);
181
182
253
  if (!challenge->AvPairs)
183
0
  {
184
0
    WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::AvPairs failed to allocate %" PRIu32 "bytes",
185
0
             challenge->cbAvPairs);
186
0
    return FALSE;
187
0
  }
188
189
253
  Stream_Read(s, challenge->AvPairs, size);
190
253
  return TRUE;
191
253
}
192
193
static BOOL ntlm_write_ntlm_v2_client_challenge(wStream* s,
194
                                                const NTLMv2_CLIENT_CHALLENGE* challenge)
195
0
{
196
0
  ULONG length = 0;
197
198
0
  WINPR_ASSERT(s);
199
0
  WINPR_ASSERT(challenge);
200
201
0
  if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 28, "NTLMv2_CLIENT_CHALLENGE"))
202
0
    return FALSE;
203
204
0
  Stream_Write_UINT8(s, challenge->RespType);
205
0
  Stream_Write_UINT8(s, challenge->HiRespType);
206
0
  Stream_Write_UINT16(s, challenge->Reserved1);
207
0
  Stream_Write_UINT32(s, challenge->Reserved2);
208
0
  Stream_Write(s, challenge->Timestamp, 8);
209
0
  Stream_Write(s, challenge->ClientChallenge, 8);
210
0
  Stream_Write_UINT32(s, challenge->Reserved3);
211
0
  length = ntlm_av_pair_list_length(challenge->AvPairs, challenge->cbAvPairs);
212
213
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
214
0
    return FALSE;
215
216
0
  Stream_Write(s, challenge->AvPairs, length);
217
0
  return TRUE;
218
0
}
219
220
BOOL ntlm_read_ntlm_v2_response(wStream* s, NTLMv2_RESPONSE* response)
221
258
{
222
258
  WINPR_ASSERT(s);
223
258
  WINPR_ASSERT(response);
224
225
258
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
226
2
    return FALSE;
227
228
256
  Stream_Read(s, response->Response, 16);
229
256
  return ntlm_read_ntlm_v2_client_challenge(s, &(response->Challenge));
230
258
}
231
232
BOOL ntlm_write_ntlm_v2_response(wStream* s, const NTLMv2_RESPONSE* response)
233
0
{
234
0
  WINPR_ASSERT(s);
235
0
  WINPR_ASSERT(response);
236
237
0
  if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16ull, "NTLMv2_RESPONSE"))
238
0
    return FALSE;
239
240
0
  Stream_Write(s, response->Response, 16);
241
0
  return ntlm_write_ntlm_v2_client_challenge(s, &(response->Challenge));
242
0
}
243
244
/**
245
 * Get current time, in tenths of microseconds since midnight of January 1, 1601.
246
 * @param[out] timestamp 64-bit little-endian timestamp
247
 */
248
249
static void ntlm_current_time(BYTE* timestamp, WINPR_ATTR_UNUSED size_t size)
250
724
{
251
724
  FILETIME ft = WINPR_C_ARRAY_INIT;
252
253
724
  WINPR_ASSERT(timestamp);
254
724
  WINPR_ASSERT(size >= sizeof(ft));
255
256
724
  GetSystemTimeAsFileTime(&ft);
257
724
  CopyMemory(timestamp, &(ft), sizeof(ft));
258
724
}
259
260
/**
261
 * Generate timestamp for AUTHENTICATE_MESSAGE.
262
 *
263
 * @param context A pointer to the NTLM context
264
 */
265
266
void ntlm_generate_timestamp(NTLM_CONTEXT* context)
267
789
{
268
789
  WINPR_ASSERT(context);
269
270
789
  if (memcmp(context->ChallengeTimestamp, NTLM_NULL_BUFFER, 8) != 0)
271
65
    CopyMemory(context->Timestamp, context->ChallengeTimestamp, 8);
272
724
  else
273
724
    ntlm_current_time(context->Timestamp, sizeof(context->Timestamp));
274
789
}
275
276
static BOOL ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
277
0
{
278
0
  BOOL rc = FALSE;
279
0
  WINPR_SAM_ENTRY* entry = nullptr;
280
281
0
  WINPR_ASSERT(context);
282
0
  WINPR_ASSERT(hash);
283
284
0
  SSPI_CREDENTIALS* credentials = context->credentials;
285
0
  WINPR_SAM* sam = SamOpen(context->SamFile, TRUE);
286
287
0
  if (!sam)
288
0
    goto fail;
289
290
0
  if ((credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE) != 0)
291
0
  {
292
0
    entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User,
293
0
                           credentials->identity.UserLength * sizeof(WCHAR),
294
0
                           (LPWSTR)credentials->identity.Domain,
295
0
                           credentials->identity.DomainLength * sizeof(WCHAR));
296
297
0
    if (!entry)
298
0
    {
299
0
      entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User,
300
0
                             credentials->identity.UserLength * sizeof(WCHAR), nullptr, 0);
301
0
    }
302
0
  }
303
0
  else if ((credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_ANSI) != 0)
304
0
  {
305
0
    entry = SamLookupUserA(
306
0
        sam, (char*)credentials->identity.User, credentials->identity.UserLength * sizeof(CHAR),
307
0
        (char*)credentials->identity.Domain, credentials->identity.DomainLength * sizeof(CHAR));
308
309
0
    if (!entry)
310
0
    {
311
0
      entry = SamLookupUserA(sam, (char*)credentials->identity.User,
312
0
                             credentials->identity.UserLength * sizeof(CHAR), nullptr, 0);
313
0
    }
314
0
  }
315
0
  else
316
0
    goto fail;
317
318
0
  if (!entry)
319
0
    goto fail;
320
321
#ifdef WITH_DEBUG_NTLM
322
  WLog_VRB(TAG, "NTLM Hash:");
323
  winpr_HexDump(TAG, WLOG_DEBUG, entry->NtHash, 16);
324
#endif
325
0
  rc = NTOWFv2FromHashW(entry->NtHash, (LPWSTR)credentials->identity.User,
326
0
                        credentials->identity.UserLength * sizeof(WCHAR),
327
0
                        (LPWSTR)credentials->identity.Domain,
328
0
                        credentials->identity.DomainLength * sizeof(WCHAR), hash);
329
330
0
fail:
331
0
  SamFreeEntry(sam, entry);
332
0
  SamClose(sam);
333
0
  if (!rc)
334
0
    WLog_ERR(TAG, "Error: Could not find user in SAM database");
335
336
0
  return rc;
337
0
}
338
339
static int hexchar2nibble(WCHAR wc)
340
0
{
341
#if defined(__BIG_ENDIAN__)
342
  union
343
  {
344
    BYTE b[2];
345
    WCHAR w;
346
  } cnv;
347
  cnv.w = wc;
348
  const BYTE b = cnv.b[0];
349
  cnv.b[0] = cnv.b[1];
350
  cnv.b[1] = b;
351
  wc = cnv.w;
352
#endif
353
354
0
  switch (wc)
355
0
  {
356
0
    case L'0':
357
0
    case L'1':
358
0
    case L'2':
359
0
    case L'3':
360
0
    case L'4':
361
0
    case L'5':
362
0
    case L'6':
363
0
    case L'7':
364
0
    case L'8':
365
0
    case L'9':
366
0
      return wc - L'0';
367
0
    case L'a':
368
0
    case L'b':
369
0
    case L'c':
370
0
    case L'd':
371
0
    case L'e':
372
0
    case L'f':
373
0
      return wc - L'a' + 10;
374
0
    case L'A':
375
0
    case L'B':
376
0
    case L'C':
377
0
    case L'D':
378
0
    case L'E':
379
0
    case L'F':
380
0
      return wc - L'A' + 10;
381
0
    default:
382
0
      return -1;
383
0
  }
384
0
}
385
static int ntlm_convert_password_hash(NTLM_CONTEXT* context, BYTE* hash, size_t hashlen)
386
0
{
387
0
  const size_t required_len = 2ull * hashlen;
388
389
0
  WINPR_ASSERT(context);
390
0
  WINPR_ASSERT(hash);
391
392
0
  SSPI_CREDENTIALS* credentials = context->credentials;
393
0
  const ULONG PasswordHashLength = credentials->identity.PasswordLength;
394
395
0
  if (PasswordHashLength != required_len)
396
0
  {
397
0
    WLog_ERR(TAG,
398
0
             "PasswordHash has invalid length %" PRIu32 " must be exactly %" PRIuz " bytes",
399
0
             PasswordHashLength, required_len);
400
0
    return -1;
401
0
  }
402
403
0
  const WCHAR* PasswordHash = credentials->identity.Password;
404
0
  for (size_t x = 0; x < hashlen; x++)
405
0
  {
406
0
    const int hi = hexchar2nibble(PasswordHash[2 * x]);
407
0
    if (hi < 0)
408
0
    {
409
0
      WLog_ERR(TAG, "PasswordHash has an invalid value at position %" PRIuz, 2 * x);
410
0
      return -1;
411
0
    }
412
0
    const int lo = hexchar2nibble(PasswordHash[2 * x + 1]);
413
0
    if (lo < 0)
414
0
    {
415
0
      WLog_ERR(TAG, "PasswordHash has an invalid value at position %" PRIuz, 2 * x + 1);
416
0
      return -1;
417
0
    }
418
0
    const BYTE val = (BYTE)((hi << 4) | lo);
419
0
    hash[x] = val;
420
0
  }
421
422
0
  return 1;
423
0
}
424
425
static BOOL ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
426
864
{
427
864
  WINPR_ASSERT(context);
428
864
  WINPR_ASSERT(hash);
429
430
864
  SSPI_CREDENTIALS* credentials = context->credentials;
431
#ifdef WITH_DEBUG_NTLM
432
433
  if (credentials)
434
  {
435
    WLog_VRB(TAG, "Password (length = %" PRIuz ")",
436
             credentials->identity.PasswordLength * sizeof(WCHAR));
437
    winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Password,
438
                  credentials->identity.PasswordLength * 2);
439
    WLog_VRB(TAG, "Username (length = %" PRIuz ")",
440
             credentials->identity.UserLength * sizeof(WCHAR));
441
    winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.User,
442
                  credentials->identity.UserLength * 2);
443
    WLog_VRB(TAG, "Domain (length = %" PRIuz ")",
444
             credentials->identity.DomainLength * sizeof(WCHAR));
445
    winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Domain,
446
                  credentials->identity.DomainLength * 2);
447
  }
448
  else
449
    WLog_VRB(TAG, "Strange, NTLM_CONTEXT is missing valid credentials...");
450
451
  WLog_VRB(TAG, "Workstation (length = %" PRIu16 ")", context->Workstation.Length);
452
  winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)context->Workstation.Buffer, context->Workstation.Length);
453
  WLog_VRB(TAG, "NTOWFv2, NTLMv2 Hash");
454
  winpr_HexDump(TAG, WLOG_TRACE, context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH);
455
#endif
456
457
864
  if (memcmp(context->NtlmV2Hash, NTLM_NULL_BUFFER, 16) != 0)
458
684
    return TRUE;
459
460
180
  if (!credentials)
461
0
    return FALSE;
462
180
  else if (memcmp(context->NtlmHash, NTLM_NULL_BUFFER, 16) != 0)
463
0
  {
464
0
    if ((credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE) != 0)
465
0
    {
466
0
      return NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
467
0
                              credentials->identity.UserLength * 2,
468
0
                              (LPWSTR)credentials->identity.Domain,
469
0
                              credentials->identity.DomainLength * 2, hash);
470
0
    }
471
0
    else if ((credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_ANSI) != 0)
472
0
    {
473
0
      return NTOWFv2FromHashA(context->NtlmHash, (char*)credentials->identity.User,
474
0
                              credentials->identity.UserLength,
475
0
                              (char*)credentials->identity.Domain,
476
0
                              credentials->identity.DomainLength, hash);
477
0
    }
478
0
    else
479
0
      return FALSE;
480
0
  }
481
180
  else if (credentials->identity.Flags & SEC_WINPR_AUTH_IDENTITY_PASSWORD_HASH)
482
0
  {
483
    /* Special case for WinPR: password hash */
484
0
    if (ntlm_convert_password_hash(context, context->NtlmHash, sizeof(context->NtlmHash)) < 0)
485
0
      return FALSE;
486
487
0
    return NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
488
0
                            credentials->identity.UserLength * 2,
489
0
                            (LPWSTR)credentials->identity.Domain,
490
0
                            credentials->identity.DomainLength * 2, hash);
491
0
  }
492
180
  else if (credentials->identity.Password)
493
180
  {
494
180
    return NTOWFv2W(
495
180
        (LPWSTR)credentials->identity.Password, credentials->identity.PasswordLength * 2,
496
180
        (LPWSTR)credentials->identity.User, credentials->identity.UserLength * 2,
497
180
        (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * 2, hash);
498
180
  }
499
0
  else if (context->HashCallback)
500
0
  {
501
0
    SecBuffer proofValue = WINPR_C_ARRAY_INIT;
502
0
    SecBuffer micValue = WINPR_C_ARRAY_INIT;
503
504
0
    if (ntlm_computeProofValue(context, &proofValue) != SEC_E_OK)
505
0
      return FALSE;
506
507
0
    if (ntlm_computeMicValue(context, &micValue) != SEC_E_OK)
508
0
    {
509
0
      sspi_SecBufferFree(&proofValue);
510
0
      return FALSE;
511
0
    }
512
513
0
    const SECURITY_STATUS ret = context->HashCallback(
514
0
        context->HashCallbackArg, &credentials->identity, &proofValue,
515
0
        context->EncryptedRandomSessionKey, context->AUTHENTICATE_MESSAGE.MessageIntegrityCheck,
516
0
        &micValue, hash);
517
0
    sspi_SecBufferFree(&proofValue);
518
0
    sspi_SecBufferFree(&micValue);
519
0
    return ret == SEC_E_OK;
520
0
  }
521
0
  else if (context->UseSamFileDatabase)
522
0
  {
523
0
    return ntlm_fetch_ntlm_v2_hash(context, hash);
524
0
  }
525
526
0
  return TRUE;
527
180
}
528
529
SECURITY_STATUS ntlm_compute_lm_v2_response(NTLM_CONTEXT* context)
530
432
{
531
432
  BYTE* response = nullptr;
532
432
  BYTE value[WINPR_MD5_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
533
534
432
  WINPR_ASSERT(context);
535
536
432
  if (context->LmCompatibilityLevel < 2)
537
0
  {
538
0
    if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24))
539
0
      return SEC_E_INSUFFICIENT_MEMORY;
540
541
0
    ZeroMemory(context->LmChallengeResponse.pvBuffer, 24);
542
0
    return SEC_E_OK;
543
0
  }
544
545
  /* Compute the NTLMv2 hash */
546
547
432
  if (!ntlm_compute_ntlm_v2_hash(context, context->NtlmV2Hash))
548
0
    return SEC_E_NO_CREDENTIALS;
549
550
  /* Concatenate the server and client challenges */
551
432
  CopyMemory(value, context->ServerChallenge, 8);
552
432
  CopyMemory(&value[8], context->ClientChallenge, 8);
553
554
432
  if (!ntlm_SecBufferRealloc(&context->LmChallengeResponse, 24))
555
0
    return SEC_E_INSUFFICIENT_MEMORY;
556
557
432
  response = (BYTE*)context->LmChallengeResponse.pvBuffer;
558
  /* Compute the HMAC-MD5 hash of the resulting value using the NTLMv2 hash as the key */
559
432
  if (!winpr_HMAC(WINPR_MD_MD5, (void*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, (BYTE*)value,
560
432
                  WINPR_MD5_DIGEST_LENGTH, response, WINPR_MD5_DIGEST_LENGTH))
561
0
    return SEC_E_ALGORITHM_MISMATCH;
562
563
  /* Concatenate the resulting HMAC-MD5 hash and the client challenge, giving us the LMv2 response
564
   * (24 bytes) */
565
432
  CopyMemory(&response[16], context->ClientChallenge, 8);
566
432
  return SEC_E_OK;
567
432
}
568
569
/**
570
 * Compute NTLMv2 Response.
571
 *
572
 * NTLMv2_RESPONSE msdn{cc236653}
573
 * NTLMv2 Authentication msdn{cc236700}
574
 *
575
 * @param context A pointer to the NTLM context
576
 * @return \b TRUE for success, \b FALSE for failure
577
 */
578
579
SECURITY_STATUS ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
580
432
{
581
432
  SecBuffer ntlm_v2_temp = WINPR_C_ARRAY_INIT;
582
432
  SecBuffer ntlm_v2_temp_chal = WINPR_C_ARRAY_INIT;
583
584
432
  WINPR_ASSERT(context);
585
586
432
  PSecBuffer TargetInfo = &context->ChallengeTargetInfo;
587
432
  SECURITY_STATUS ret = SEC_E_INSUFFICIENT_MEMORY;
588
589
432
  if (!sspi_SecBufferAlloc(&ntlm_v2_temp, TargetInfo->cbBuffer + 28))
590
0
    goto exit;
591
592
432
  ZeroMemory(ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
593
432
  {
594
432
    BYTE* blob = (BYTE*)ntlm_v2_temp.pvBuffer;
595
596
    /* Compute the NTLMv2 hash */
597
432
    ret = SEC_E_NO_CREDENTIALS;
598
432
    if (!ntlm_compute_ntlm_v2_hash(context, (BYTE*)context->NtlmV2Hash))
599
0
      goto exit;
600
601
    /* Construct temp */
602
432
    blob[0] = 1; /* RespType (1 byte) */
603
432
    blob[1] = 1; /* HighRespType (1 byte) */
604
    /* Reserved1 (2 bytes) */
605
    /* Reserved2 (4 bytes) */
606
432
    CopyMemory(&blob[8], context->Timestamp, 8);        /* Timestamp (8 bytes) */
607
432
    CopyMemory(&blob[16], context->ClientChallenge, 8); /* ClientChallenge (8 bytes) */
608
    /* Reserved3 (4 bytes) */
609
432
    CopyMemory(&blob[28], TargetInfo->pvBuffer, TargetInfo->cbBuffer);
610
#ifdef WITH_DEBUG_NTLM
611
    WLog_VRB(TAG, "NTLMv2 Response Temp Blob");
612
    winpr_HexDump(TAG, WLOG_TRACE, ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
613
#endif
614
432
  }
615
  /* Concatenate server challenge with temp */
616
432
  ret = SEC_E_INSUFFICIENT_MEMORY;
617
432
  if (!sspi_SecBufferAlloc(&ntlm_v2_temp_chal, ntlm_v2_temp.cbBuffer + 8))
618
0
    goto exit;
619
620
432
  {
621
432
    BYTE* blob = (BYTE*)ntlm_v2_temp_chal.pvBuffer;
622
432
    CopyMemory(blob, context->ServerChallenge, 8);
623
432
    CopyMemory(&blob[8], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
624
432
    if (!winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
625
432
                    (BYTE*)ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer,
626
432
                    context->NtProofString, WINPR_MD5_DIGEST_LENGTH))
627
0
      goto exit;
628
432
  }
629
630
  /* NtChallengeResponse, Concatenate NTProofStr with temp */
631
432
  if (!ntlm_SecBufferRealloc(&context->NtChallengeResponse, ntlm_v2_temp.cbBuffer + 16))
632
0
    goto exit;
633
634
432
  {
635
432
    BYTE* blob = (BYTE*)context->NtChallengeResponse.pvBuffer;
636
432
    CopyMemory(blob, context->NtProofString, WINPR_MD5_DIGEST_LENGTH);
637
432
    CopyMemory(&blob[16], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
638
432
  }
639
  /* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2 hash as the key */
640
432
  if (!winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
641
432
                  context->NtProofString, WINPR_MD5_DIGEST_LENGTH, context->SessionBaseKey,
642
432
                  WINPR_MD5_DIGEST_LENGTH))
643
0
    goto exit;
644
432
  ret = SEC_E_OK;
645
432
exit:
646
432
  sspi_SecBufferFree(&ntlm_v2_temp);
647
432
  sspi_SecBufferFree(&ntlm_v2_temp_chal);
648
432
  return ret;
649
432
}
650
651
/**
652
 * Encrypt the given plain text using RC4 and the given key.
653
 * @param key RC4 key
654
 * @param length text length
655
 * @param plaintext plain text
656
 * @param ciphertext cipher text
657
 */
658
659
BOOL ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext)
660
184
{
661
184
  WINPR_RC4_CTX* rc4 = winpr_RC4_New(key, 16);
662
663
184
  if (!rc4)
664
0
    return FALSE;
665
666
184
  const BOOL rc = winpr_RC4_Update(rc4, length, plaintext, ciphertext);
667
184
  winpr_RC4_Free(rc4);
668
184
  return rc;
669
184
}
670
671
/**
672
 * Generate client challenge (8-byte nonce).
673
 * @param context A pointer to the NTLM context
674
 */
675
676
BOOL ntlm_generate_client_challenge(NTLM_CONTEXT* context)
677
587
{
678
587
  WINPR_ASSERT(context);
679
680
  /* ClientChallenge is used in computation of LMv2 and NTLMv2 responses */
681
587
  if (memcmp(context->ClientChallenge, NTLM_NULL_BUFFER, sizeof(context->ClientChallenge)) != 0)
682
0
    return TRUE;
683
684
587
  return winpr_RAND(context->ClientChallenge, sizeof(context->ClientChallenge)) >= 0;
685
587
}
686
687
/**
688
 * Generate server challenge (8-byte nonce).
689
 * @param context A pointer to the NTLM context
690
 */
691
692
BOOL ntlm_generate_server_challenge(NTLM_CONTEXT* context)
693
609
{
694
609
  WINPR_ASSERT(context);
695
696
609
  if (memcmp(context->ServerChallenge, NTLM_NULL_BUFFER, sizeof(context->ServerChallenge)) != 0)
697
0
    return TRUE;
698
699
609
  return winpr_RAND(context->ServerChallenge, sizeof(context->ServerChallenge)) >= 0;
700
609
}
701
702
/**
703
 * Generate KeyExchangeKey (the 128-bit SessionBaseKey). msdn{cc236710}
704
 * @param context A pointer to the NTLM context
705
 */
706
707
BOOL ntlm_generate_key_exchange_key(NTLM_CONTEXT* context)
708
432
{
709
432
  WINPR_ASSERT(context);
710
432
  WINPR_ASSERT(sizeof(context->KeyExchangeKey) == sizeof(context->SessionBaseKey));
711
712
  /* In NTLMv2, KeyExchangeKey is the 128-bit SessionBaseKey */
713
432
  CopyMemory(context->KeyExchangeKey, context->SessionBaseKey, sizeof(context->KeyExchangeKey));
714
432
  return TRUE;
715
432
}
716
717
/**
718
 * Generate RandomSessionKey (16-byte nonce).
719
 * @param context A pointer to the NTLM context
720
 */
721
722
BOOL ntlm_generate_random_session_key(NTLM_CONTEXT* context)
723
180
{
724
180
  WINPR_ASSERT(context);
725
180
  return winpr_RAND(context->RandomSessionKey, sizeof(context->RandomSessionKey)) >= 0;
726
180
}
727
728
/**
729
 * Generate ExportedSessionKey (the RandomSessionKey, exported)
730
 * @param context A pointer to the NTLM context
731
 */
732
733
BOOL ntlm_generate_exported_session_key(NTLM_CONTEXT* context)
734
432
{
735
432
  WINPR_ASSERT(context);
736
432
  WINPR_ASSERT(sizeof(context->ExportedSessionKey) >= sizeof(context->RandomSessionKey));
737
738
432
  CopyMemory(context->ExportedSessionKey, context->RandomSessionKey,
739
432
             sizeof(context->ExportedSessionKey));
740
432
  return TRUE;
741
432
}
742
743
/**
744
 * Encrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key).
745
 * @param context A pointer to the NTLM context
746
 */
747
748
BOOL ntlm_encrypt_random_session_key(NTLM_CONTEXT* context)
749
180
{
750
  /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
751
   * KeyExchangeKey */
752
180
  WINPR_ASSERT(context);
753
180
  return ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey,
754
180
                   context->EncryptedRandomSessionKey);
755
180
}
756
757
/**
758
 * Decrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key).
759
 * @param context A pointer to the NTLM context
760
 */
761
762
BOOL ntlm_decrypt_random_session_key(NTLM_CONTEXT* context)
763
252
{
764
252
  WINPR_ASSERT(context);
765
766
  /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
767
   * KeyExchangeKey */
768
769
  /**
770
   *  if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
771
   *    Set RandomSessionKey to RC4K(KeyExchangeKey,
772
   * AUTHENTICATE_MESSAGE.EncryptedRandomSessionKey) else Set RandomSessionKey to KeyExchangeKey
773
   */
774
252
  if (context->NegotiateKeyExchange)
775
4
  {
776
4
    WINPR_ASSERT(sizeof(context->EncryptedRandomSessionKey) ==
777
4
                 sizeof(context->RandomSessionKey));
778
4
    return ntlm_rc4k(context->KeyExchangeKey, sizeof(context->EncryptedRandomSessionKey),
779
4
                     context->EncryptedRandomSessionKey, context->RandomSessionKey);
780
4
  }
781
248
  else
782
248
  {
783
248
    WINPR_ASSERT(sizeof(context->RandomSessionKey) == sizeof(context->KeyExchangeKey));
784
248
    CopyMemory(context->RandomSessionKey, context->KeyExchangeKey,
785
248
               sizeof(context->RandomSessionKey));
786
248
  }
787
248
  return TRUE;
788
252
}
789
790
/**
791
 * Generate signing key msdn{cc236711}
792
 *
793
 * @param exported_session_key ExportedSessionKey
794
 * @param sign_magic Sign magic string
795
 * @param signing_key Destination signing key
796
 *
797
 * @return \b TRUE for success, \b FALSE for failure
798
 */
799
800
static BOOL ntlm_generate_signing_key(BYTE* exported_session_key, const SecBuffer* sign_magic,
801
                                      BYTE* signing_key)
802
720
{
803
720
  BOOL rc = FALSE;
804
805
720
  WINPR_ASSERT(exported_session_key);
806
720
  WINPR_ASSERT(sign_magic);
807
720
  WINPR_ASSERT(signing_key);
808
809
720
  const size_t length = WINPR_MD5_DIGEST_LENGTH + sign_magic->cbBuffer;
810
720
  BYTE* value = (BYTE*)malloc(length);
811
812
720
  if (!value)
813
0
    goto out;
814
815
  /* Concatenate ExportedSessionKey with sign magic */
816
720
  CopyMemory(value, exported_session_key, WINPR_MD5_DIGEST_LENGTH);
817
720
  CopyMemory(&value[WINPR_MD5_DIGEST_LENGTH], sign_magic->pvBuffer, sign_magic->cbBuffer);
818
819
720
  rc = winpr_Digest(WINPR_MD_MD5, value, length, signing_key, WINPR_MD5_DIGEST_LENGTH);
820
821
720
out:
822
720
  free(value);
823
720
  return rc;
824
720
}
825
826
/**
827
 * Generate client signing key (ClientSigningKey). msdn{cc236711}
828
 * @param context A pointer to the NTLM context
829
 *
830
 * @return \b TRUE for success, \b FALSE for failure
831
 */
832
833
BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context)
834
180
{
835
180
  const SecBuffer signMagic = { sizeof(NTLM_CLIENT_SIGN_MAGIC), 0, NTLM_CLIENT_SIGN_MAGIC };
836
837
180
  WINPR_ASSERT(context);
838
180
  return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
839
180
                                   context->ClientSigningKey);
840
180
}
841
842
/**
843
 * Generate server signing key (ServerSigningKey). msdn{cc236711}
844
 * @param context A pointer to the NTLM context
845
 *
846
 * @return \b TRUE for success, \b FALSE for failure
847
 */
848
849
BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context)
850
180
{
851
180
  const SecBuffer signMagic = { sizeof(NTLM_SERVER_SIGN_MAGIC), 0, NTLM_SERVER_SIGN_MAGIC };
852
853
180
  WINPR_ASSERT(context);
854
180
  return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
855
180
                                   context->ServerSigningKey);
856
180
}
857
858
/**
859
 * Generate client sealing key (ClientSealingKey). msdn{cc236712}
860
 * @param context A pointer to the NTLM context
861
 *
862
 * @return \b TRUE for success, \b FALSE for failure
863
 */
864
865
BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context)
866
180
{
867
180
  const SecBuffer sealMagic = { sizeof(NTLM_CLIENT_SEAL_MAGIC), 0, NTLM_CLIENT_SEAL_MAGIC };
868
869
180
  WINPR_ASSERT(context);
870
180
  return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
871
180
                                   context->ClientSealingKey);
872
180
}
873
874
/**
875
 * Generate server sealing key (ServerSealingKey). msdn{cc236712}
876
 * @param context A pointer to the NTLM context
877
 *
878
 * @return \b TRUE for success, \b FALSE for failure
879
 */
880
881
BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context)
882
180
{
883
180
  const SecBuffer sealMagic = { sizeof(NTLM_SERVER_SEAL_MAGIC), 0, NTLM_SERVER_SEAL_MAGIC };
884
885
180
  WINPR_ASSERT(context);
886
180
  return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
887
180
                                   context->ServerSealingKey);
888
180
}
889
890
/**
891
 * Initialize RC4 stream cipher states for sealing.
892
 * @param context A pointer to the NTLM context
893
 */
894
895
BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context)
896
180
{
897
180
  WINPR_ASSERT(context);
898
180
  if (context->server)
899
0
  {
900
0
    context->SendSigningKey = context->ServerSigningKey;
901
0
    context->RecvSigningKey = context->ClientSigningKey;
902
0
    context->SendSealingKey = context->ClientSealingKey;
903
0
    context->RecvSealingKey = context->ServerSealingKey;
904
0
    context->SendRc4Seal =
905
0
        winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
906
0
    context->RecvRc4Seal =
907
0
        winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
908
0
  }
909
180
  else
910
180
  {
911
180
    context->SendSigningKey = context->ClientSigningKey;
912
180
    context->RecvSigningKey = context->ServerSigningKey;
913
180
    context->SendSealingKey = context->ServerSealingKey;
914
180
    context->RecvSealingKey = context->ClientSealingKey;
915
180
    context->SendRc4Seal =
916
180
        winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
917
180
    context->RecvRc4Seal =
918
180
        winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
919
180
  }
920
180
  if (!context->SendRc4Seal)
921
0
  {
922
0
    WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal");
923
0
    return FALSE;
924
0
  }
925
180
  if (!context->RecvRc4Seal)
926
0
  {
927
0
    WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal");
928
0
    return FALSE;
929
0
  }
930
180
  return TRUE;
931
180
}
932
933
BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size)
934
252
{
935
252
  BOOL rc = FALSE;
936
  /*
937
   * Compute the HMAC-MD5 hash of ConcatenationOf(NEGOTIATE_MESSAGE,
938
   * CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE) using the ExportedSessionKey
939
   */
940
252
  WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
941
942
252
  WINPR_ASSERT(context);
943
252
  WINPR_ASSERT(mic);
944
252
  WINPR_ASSERT(size >= WINPR_MD5_DIGEST_LENGTH);
945
946
252
  memset(mic, 0, size);
947
252
  if (!hmac)
948
0
    return FALSE;
949
950
252
  if (!winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->ExportedSessionKey, WINPR_MD5_DIGEST_LENGTH))
951
0
    goto fail;
952
953
252
  if (!winpr_HMAC_Update(hmac, (BYTE*)context->NegotiateMessage.pvBuffer,
954
252
                         context->NegotiateMessage.cbBuffer))
955
0
    goto fail;
956
252
  if (!winpr_HMAC_Update(hmac, (BYTE*)context->ChallengeMessage.pvBuffer,
957
252
                         context->ChallengeMessage.cbBuffer))
958
0
    goto fail;
959
960
252
  if (context->MessageIntegrityCheckOffset > 0)
961
252
  {
962
252
    const BYTE* auth = (BYTE*)context->AuthenticateMessage.pvBuffer;
963
252
    const BYTE data[WINPR_MD5_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
964
252
    const size_t rest = context->MessageIntegrityCheckOffset + sizeof(data);
965
966
252
    if (rest > context->AuthenticateMessage.cbBuffer)
967
9
      goto fail;
968
243
    if (!winpr_HMAC_Update(hmac, &auth[0], context->MessageIntegrityCheckOffset))
969
0
      goto fail;
970
243
    if (!winpr_HMAC_Update(hmac, data, sizeof(data)))
971
0
      goto fail;
972
243
    if (!winpr_HMAC_Update(hmac, &auth[rest], context->AuthenticateMessage.cbBuffer - rest))
973
0
      goto fail;
974
243
  }
975
0
  else
976
0
  {
977
0
    if (!winpr_HMAC_Update(hmac, (BYTE*)context->AuthenticateMessage.pvBuffer,
978
0
                           context->AuthenticateMessage.cbBuffer))
979
0
      goto fail;
980
0
  }
981
243
  rc = winpr_HMAC_Final(hmac, mic, WINPR_MD5_DIGEST_LENGTH);
982
983
252
fail:
984
252
  winpr_HMAC_Free(hmac);
985
252
  return rc;
986
243
}