Coverage Report

Created: 2024-09-08 06:20

/src/FreeRDP/winpr/libwinpr/sspi/NTLM/ntlm_compute.c
Line
Count
Source (jump to first uncovered line)
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
0
#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
0
{
60
0
  WINPR_ASSERT(versionInfo);
61
62
#if defined(WITH_WINPR_DEPRECATED)
63
  OSVERSIONINFOA osVersionInfo = { 0 };
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
0
  versionInfo->ProductMajorVersion = 10;
78
0
  versionInfo->ProductMinorVersion = 0;
79
0
  versionInfo->ProductBuild = 22631;
80
0
#endif
81
0
  ZeroMemory(versionInfo->Reserved, sizeof(versionInfo->Reserved));
82
0
  versionInfo->NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3;
83
0
  return TRUE;
84
0
}
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
0
{
96
0
  WINPR_ASSERT(s);
97
0
  WINPR_ASSERT(versionInfo);
98
99
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
100
0
    return FALSE;
101
102
0
  Stream_Read_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */
103
0
  Stream_Read_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */
104
0
  Stream_Read_UINT16(s, versionInfo->ProductBuild);       /* ProductBuild (2 bytes) */
105
0
  Stream_Read(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */
106
0
  Stream_Read_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */
107
0
  return TRUE;
108
0
}
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
0
{
120
0
  WINPR_ASSERT(s);
121
0
  WINPR_ASSERT(versionInfo);
122
123
0
  if (!Stream_CheckAndLogRequiredCapacityEx(
124
0
          TAG, WLOG_WARN, s, 5ull + sizeof(versionInfo->Reserved), 1ull,
125
0
          "%s(%s:%" PRIuz ") NTLM_VERSION_INFO", __func__, __FILE__, (size_t)__LINE__))
126
0
    return FALSE;
127
128
0
  Stream_Write_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */
129
0
  Stream_Write_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */
130
0
  Stream_Write_UINT16(s, versionInfo->ProductBuild);       /* ProductBuild (2 bytes) */
131
0
  Stream_Write(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */
132
0
  Stream_Write_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */
133
0
  return TRUE;
134
0
}
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
0
{
157
0
  size_t size = 0;
158
0
  WINPR_ASSERT(s);
159
0
  WINPR_ASSERT(challenge);
160
161
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 28))
162
0
    return FALSE;
163
164
0
  Stream_Read_UINT8(s, challenge->RespType);
165
0
  Stream_Read_UINT8(s, challenge->HiRespType);
166
0
  Stream_Read_UINT16(s, challenge->Reserved1);
167
0
  Stream_Read_UINT32(s, challenge->Reserved2);
168
0
  Stream_Read(s, challenge->Timestamp, 8);
169
0
  Stream_Read(s, challenge->ClientChallenge, 8);
170
0
  Stream_Read_UINT32(s, challenge->Reserved3);
171
0
  size = Stream_Length(s) - Stream_GetPosition(s);
172
173
0
  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
0
  challenge->cbAvPairs = (UINT32)size;
180
0
  challenge->AvPairs = (NTLM_AV_PAIR*)malloc(challenge->cbAvPairs);
181
182
0
  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
0
  Stream_Read(s, challenge->AvPairs, size);
190
0
  return TRUE;
191
0
}
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
0
{
222
0
  WINPR_ASSERT(s);
223
0
  WINPR_ASSERT(response);
224
225
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
226
0
    return FALSE;
227
228
0
  Stream_Read(s, response->Response, 16);
229
0
  return ntlm_read_ntlm_v2_client_challenge(s, &(response->Challenge));
230
0
}
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
void ntlm_current_time(BYTE* timestamp)
250
0
{
251
0
  FILETIME ft = { 0 };
252
253
0
  WINPR_ASSERT(timestamp);
254
255
0
  GetSystemTimeAsFileTime(&ft);
256
0
  CopyMemory(timestamp, &(ft), sizeof(ft));
257
0
}
258
259
/**
260
 * Generate timestamp for AUTHENTICATE_MESSAGE.
261
 *
262
 * @param context A pointer to the NTLM context
263
 */
264
265
void ntlm_generate_timestamp(NTLM_CONTEXT* context)
266
0
{
267
0
  WINPR_ASSERT(context);
268
269
0
  if (memcmp(context->ChallengeTimestamp, NTLM_NULL_BUFFER, 8) != 0)
270
0
    CopyMemory(context->Timestamp, context->ChallengeTimestamp, 8);
271
0
  else
272
0
    ntlm_current_time(context->Timestamp);
273
0
}
274
275
static BOOL ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
276
0
{
277
0
  BOOL rc = FALSE;
278
0
  WINPR_SAM* sam = NULL;
279
0
  WINPR_SAM_ENTRY* entry = NULL;
280
0
  SSPI_CREDENTIALS* credentials = NULL;
281
282
0
  WINPR_ASSERT(context);
283
0
  WINPR_ASSERT(hash);
284
285
0
  credentials = context->credentials;
286
0
  sam = SamOpen(context->SamFile, TRUE);
287
288
0
  if (!sam)
289
0
    goto fail;
290
291
0
  entry = SamLookupUserW(
292
0
      sam, (LPWSTR)credentials->identity.User, credentials->identity.UserLength * sizeof(WCHAR),
293
0
      (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * sizeof(WCHAR));
294
295
0
  if (!entry)
296
0
  {
297
0
    entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User,
298
0
                           credentials->identity.UserLength * sizeof(WCHAR), NULL, 0);
299
0
  }
300
301
0
  if (!entry)
302
0
    goto fail;
303
304
#ifdef WITH_DEBUG_NTLM
305
  WLog_VRB(TAG, "NTLM Hash:");
306
  winpr_HexDump(TAG, WLOG_DEBUG, entry->NtHash, 16);
307
#endif
308
0
  NTOWFv2FromHashW(entry->NtHash, (LPWSTR)credentials->identity.User,
309
0
                   credentials->identity.UserLength * sizeof(WCHAR),
310
0
                   (LPWSTR)credentials->identity.Domain,
311
0
                   credentials->identity.DomainLength * sizeof(WCHAR), hash);
312
313
0
  rc = TRUE;
314
315
0
fail:
316
0
  SamFreeEntry(sam, entry);
317
0
  SamClose(sam);
318
0
  if (!rc)
319
0
    WLog_ERR(TAG, "Error: Could not find user in SAM database");
320
321
0
  return rc;
322
0
}
323
324
static int hexchar2nibble(WCHAR wc)
325
0
{
326
#if defined(__BIG_ENDIAN__)
327
  union
328
  {
329
    BYTE b[2];
330
    WCHAR w;
331
  } cnv;
332
  cnv.w = wc;
333
  const BYTE b = cnv.b[0];
334
  cnv.b[0] = cnv.b[1];
335
  cnv.b[1] = b;
336
  wc = cnv.w;
337
#endif
338
339
0
  switch (wc)
340
0
  {
341
0
    case L'0':
342
0
    case L'1':
343
0
    case L'2':
344
0
    case L'3':
345
0
    case L'4':
346
0
    case L'5':
347
0
    case L'6':
348
0
    case L'7':
349
0
    case L'8':
350
0
    case L'9':
351
0
      return wc - L'0';
352
0
    case L'a':
353
0
    case L'b':
354
0
    case L'c':
355
0
    case L'd':
356
0
    case L'e':
357
0
    case L'f':
358
0
      return wc - L'a' + 10;
359
0
    case L'A':
360
0
    case L'B':
361
0
    case L'C':
362
0
    case L'D':
363
0
    case L'E':
364
0
    case L'F':
365
0
      return wc - L'A' + 10;
366
0
    default:
367
0
      return -1;
368
0
  }
369
0
}
370
static int ntlm_convert_password_hash(NTLM_CONTEXT* context, BYTE* hash, size_t hashlen)
371
0
{
372
0
  const size_t required_len = 2ull * hashlen;
373
374
0
  WINPR_ASSERT(context);
375
0
  WINPR_ASSERT(hash);
376
377
0
  SSPI_CREDENTIALS* credentials = context->credentials;
378
  /* Password contains a password hash of length (PasswordLength -
379
   * SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) */
380
0
  const ULONG PasswordHashLength = credentials->identity.PasswordLength -
381
                                   /* Macro [globalScope] */ SSPI_CREDENTIALS_HASH_LENGTH_OFFSET;
382
383
0
  if (PasswordHashLength != required_len)
384
0
  {
385
0
    WLog_ERR(TAG,
386
0
             "PasswordHash has invalid length %" PRIu32 " must be exactly %" PRIuz " bytes",
387
0
             PasswordHashLength, required_len);
388
0
    return -1;
389
0
  }
390
391
0
  const WCHAR* PasswordHash = credentials->identity.Password;
392
0
  for (size_t x = 0; x < hashlen; x++)
393
0
  {
394
0
    const int hi = hexchar2nibble(PasswordHash[2 * x]);
395
0
    if (hi < 0)
396
0
    {
397
0
      WLog_ERR(TAG, "PasswordHash has an invalid value at position %" PRIuz, 2 * x);
398
0
      return -1;
399
0
    }
400
0
    const int lo = hexchar2nibble(PasswordHash[2 * x + 1]);
401
0
    if (lo < 0)
402
0
    {
403
0
      WLog_ERR(TAG, "PasswordHash has an invalid value at position %" PRIuz, 2 * x + 1);
404
0
      return -1;
405
0
    }
406
0
    const BYTE val = (BYTE)((hi << 4) | lo);
407
0
    hash[x] = val;
408
0
  }
409
410
0
  return 1;
411
0
}
412
413
static BOOL ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
414
0
{
415
0
  SSPI_CREDENTIALS* credentials = NULL;
416
417
0
  WINPR_ASSERT(context);
418
0
  WINPR_ASSERT(hash);
419
420
0
  credentials = context->credentials;
421
#ifdef WITH_DEBUG_NTLM
422
423
  if (credentials)
424
  {
425
    WLog_VRB(TAG, "Password (length = %" PRIu32 ")", credentials->identity.PasswordLength * 2);
426
    winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Password,
427
                  credentials->identity.PasswordLength * 2);
428
    WLog_VRB(TAG, "Username (length = %" PRIu32 ")", credentials->identity.UserLength * 2);
429
    winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.User,
430
                  credentials->identity.UserLength * 2);
431
    WLog_VRB(TAG, "Domain (length = %" PRIu32 ")", credentials->identity.DomainLength * 2);
432
    winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Domain,
433
                  credentials->identity.DomainLength * 2);
434
  }
435
  else
436
    WLog_VRB(TAG, "Strange, NTLM_CONTEXT is missing valid credentials...");
437
438
  WLog_VRB(TAG, "Workstation (length = %" PRIu16 ")", context->Workstation.Length);
439
  winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)context->Workstation.Buffer, context->Workstation.Length);
440
  WLog_VRB(TAG, "NTOWFv2, NTLMv2 Hash");
441
  winpr_HexDump(TAG, WLOG_TRACE, context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH);
442
#endif
443
444
0
  if (memcmp(context->NtlmV2Hash, NTLM_NULL_BUFFER, 16) != 0)
445
0
    return TRUE;
446
447
0
  if (!credentials)
448
0
    return FALSE;
449
0
  else if (memcmp(context->NtlmHash, NTLM_NULL_BUFFER, 16) != 0)
450
0
  {
451
0
    NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
452
0
                     credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain,
453
0
                     credentials->identity.DomainLength * 2, hash);
454
0
  }
455
0
  else if (credentials->identity.PasswordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET)
456
0
  {
457
    /* Special case for WinPR: password hash */
458
0
    if (ntlm_convert_password_hash(context, context->NtlmHash, sizeof(context->NtlmHash)) < 0)
459
0
      return FALSE;
460
461
0
    NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
462
0
                     credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain,
463
0
                     credentials->identity.DomainLength * 2, hash);
464
0
  }
465
0
  else if (credentials->identity.Password)
466
0
  {
467
0
    NTOWFv2W((LPWSTR)credentials->identity.Password, credentials->identity.PasswordLength * 2,
468
0
             (LPWSTR)credentials->identity.User, credentials->identity.UserLength * 2,
469
0
             (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * 2,
470
0
             hash);
471
0
  }
472
0
  else if (context->HashCallback)
473
0
  {
474
0
    int ret = 0;
475
0
    SecBuffer proofValue;
476
0
    SecBuffer micValue;
477
478
0
    if (ntlm_computeProofValue(context, &proofValue) != SEC_E_OK)
479
0
      return FALSE;
480
481
0
    if (ntlm_computeMicValue(context, &micValue) != SEC_E_OK)
482
0
    {
483
0
      sspi_SecBufferFree(&proofValue);
484
0
      return FALSE;
485
0
    }
486
487
0
    ret = context->HashCallback(context->HashCallbackArg, &credentials->identity, &proofValue,
488
0
                                context->EncryptedRandomSessionKey,
489
0
                                context->AUTHENTICATE_MESSAGE.MessageIntegrityCheck, &micValue,
490
0
                                hash);
491
0
    sspi_SecBufferFree(&proofValue);
492
0
    sspi_SecBufferFree(&micValue);
493
0
    return ret ? TRUE : FALSE;
494
0
  }
495
0
  else if (context->UseSamFileDatabase)
496
0
  {
497
0
    return ntlm_fetch_ntlm_v2_hash(context, hash);
498
0
  }
499
500
0
  return TRUE;
501
0
}
502
503
BOOL ntlm_compute_lm_v2_response(NTLM_CONTEXT* context)
504
0
{
505
0
  BYTE* response = NULL;
506
0
  BYTE value[WINPR_MD5_DIGEST_LENGTH] = { 0 };
507
508
0
  WINPR_ASSERT(context);
509
510
0
  if (context->LmCompatibilityLevel < 2)
511
0
  {
512
0
    if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24))
513
0
      return FALSE;
514
515
0
    ZeroMemory(context->LmChallengeResponse.pvBuffer, 24);
516
0
    return TRUE;
517
0
  }
518
519
  /* Compute the NTLMv2 hash */
520
521
0
  if (!ntlm_compute_ntlm_v2_hash(context, context->NtlmV2Hash))
522
0
    return FALSE;
523
524
  /* Concatenate the server and client challenges */
525
0
  CopyMemory(value, context->ServerChallenge, 8);
526
0
  CopyMemory(&value[8], context->ClientChallenge, 8);
527
528
0
  if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24))
529
0
    return FALSE;
530
531
0
  response = (BYTE*)context->LmChallengeResponse.pvBuffer;
532
  /* Compute the HMAC-MD5 hash of the resulting value using the NTLMv2 hash as the key */
533
0
  winpr_HMAC(WINPR_MD_MD5, (void*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, (BYTE*)value,
534
0
             WINPR_MD5_DIGEST_LENGTH, response, WINPR_MD5_DIGEST_LENGTH);
535
  /* Concatenate the resulting HMAC-MD5 hash and the client challenge, giving us the LMv2 response
536
   * (24 bytes) */
537
0
  CopyMemory(&response[16], context->ClientChallenge, 8);
538
0
  return TRUE;
539
0
}
540
541
/**
542
 * Compute NTLMv2 Response.
543
 *
544
 * NTLMv2_RESPONSE msdn{cc236653}
545
 * NTLMv2 Authentication msdn{cc236700}
546
 *
547
 * @param context A pointer to the NTLM context
548
 * @return \b TRUE for success, \b FALSE for failure
549
 */
550
551
BOOL ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
552
0
{
553
0
  BYTE* blob = NULL;
554
0
  SecBuffer ntlm_v2_temp = { 0 };
555
0
  SecBuffer ntlm_v2_temp_chal = { 0 };
556
0
  PSecBuffer TargetInfo = NULL;
557
558
0
  WINPR_ASSERT(context);
559
560
0
  TargetInfo = &context->ChallengeTargetInfo;
561
0
  BOOL ret = FALSE;
562
563
0
  if (!sspi_SecBufferAlloc(&ntlm_v2_temp, TargetInfo->cbBuffer + 28))
564
0
    goto exit;
565
566
0
  ZeroMemory(ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
567
0
  blob = (BYTE*)ntlm_v2_temp.pvBuffer;
568
569
  /* Compute the NTLMv2 hash */
570
0
  if (!ntlm_compute_ntlm_v2_hash(context, (BYTE*)context->NtlmV2Hash))
571
0
    goto exit;
572
573
  /* Construct temp */
574
0
  blob[0] = 1; /* RespType (1 byte) */
575
0
  blob[1] = 1; /* HighRespType (1 byte) */
576
  /* Reserved1 (2 bytes) */
577
  /* Reserved2 (4 bytes) */
578
0
  CopyMemory(&blob[8], context->Timestamp, 8);        /* Timestamp (8 bytes) */
579
0
  CopyMemory(&blob[16], context->ClientChallenge, 8); /* ClientChallenge (8 bytes) */
580
  /* Reserved3 (4 bytes) */
581
0
  CopyMemory(&blob[28], TargetInfo->pvBuffer, TargetInfo->cbBuffer);
582
#ifdef WITH_DEBUG_NTLM
583
  WLog_VRB(TAG, "NTLMv2 Response Temp Blob");
584
  winpr_HexDump(TAG, WLOG_TRACE, ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
585
#endif
586
587
  /* Concatenate server challenge with temp */
588
589
0
  if (!sspi_SecBufferAlloc(&ntlm_v2_temp_chal, ntlm_v2_temp.cbBuffer + 8))
590
0
    goto exit;
591
592
0
  blob = (BYTE*)ntlm_v2_temp_chal.pvBuffer;
593
0
  CopyMemory(blob, context->ServerChallenge, 8);
594
0
  CopyMemory(&blob[8], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
595
0
  winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
596
0
             (BYTE*)ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer,
597
0
             context->NtProofString, WINPR_MD5_DIGEST_LENGTH);
598
599
  /* NtChallengeResponse, Concatenate NTProofStr with temp */
600
601
0
  if (!sspi_SecBufferAlloc(&context->NtChallengeResponse, ntlm_v2_temp.cbBuffer + 16))
602
0
    goto exit;
603
604
0
  blob = (BYTE*)context->NtChallengeResponse.pvBuffer;
605
0
  CopyMemory(blob, context->NtProofString, WINPR_MD5_DIGEST_LENGTH);
606
0
  CopyMemory(&blob[16], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
607
  /* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2 hash as the key */
608
0
  winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
609
0
             context->NtProofString, WINPR_MD5_DIGEST_LENGTH, context->SessionBaseKey,
610
0
             WINPR_MD5_DIGEST_LENGTH);
611
0
  ret = TRUE;
612
0
exit:
613
0
  sspi_SecBufferFree(&ntlm_v2_temp);
614
0
  sspi_SecBufferFree(&ntlm_v2_temp_chal);
615
0
  return ret;
616
0
}
617
618
/**
619
 * Encrypt the given plain text using RC4 and the given key.
620
 * @param key RC4 key
621
 * @param length text length
622
 * @param plaintext plain text
623
 * @param ciphertext cipher text
624
 */
625
626
void ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext)
627
0
{
628
0
  WINPR_RC4_CTX* rc4 = winpr_RC4_New(key, 16);
629
630
0
  if (rc4)
631
0
  {
632
0
    winpr_RC4_Update(rc4, length, plaintext, ciphertext);
633
0
    winpr_RC4_Free(rc4);
634
0
  }
635
0
}
636
637
/**
638
 * Generate client challenge (8-byte nonce).
639
 * @param context A pointer to the NTLM context
640
 */
641
642
void ntlm_generate_client_challenge(NTLM_CONTEXT* context)
643
0
{
644
0
  WINPR_ASSERT(context);
645
646
  /* ClientChallenge is used in computation of LMv2 and NTLMv2 responses */
647
0
  if (memcmp(context->ClientChallenge, NTLM_NULL_BUFFER, sizeof(context->ClientChallenge)) == 0)
648
0
    winpr_RAND(context->ClientChallenge, sizeof(context->ClientChallenge));
649
0
}
650
651
/**
652
 * Generate server challenge (8-byte nonce).
653
 * @param context A pointer to the NTLM context
654
 */
655
656
void ntlm_generate_server_challenge(NTLM_CONTEXT* context)
657
0
{
658
0
  WINPR_ASSERT(context);
659
660
0
  if (memcmp(context->ServerChallenge, NTLM_NULL_BUFFER, sizeof(context->ServerChallenge)) == 0)
661
0
    winpr_RAND(context->ServerChallenge, sizeof(context->ServerChallenge));
662
0
}
663
664
/**
665
 * Generate KeyExchangeKey (the 128-bit SessionBaseKey). msdn{cc236710}
666
 * @param context A pointer to the NTLM context
667
 */
668
669
void ntlm_generate_key_exchange_key(NTLM_CONTEXT* context)
670
0
{
671
0
  WINPR_ASSERT(context);
672
0
  WINPR_ASSERT(sizeof(context->KeyExchangeKey) == sizeof(context->SessionBaseKey));
673
674
  /* In NTLMv2, KeyExchangeKey is the 128-bit SessionBaseKey */
675
0
  CopyMemory(context->KeyExchangeKey, context->SessionBaseKey, sizeof(context->KeyExchangeKey));
676
0
}
677
678
/**
679
 * Generate RandomSessionKey (16-byte nonce).
680
 * @param context A pointer to the NTLM context
681
 */
682
683
void ntlm_generate_random_session_key(NTLM_CONTEXT* context)
684
0
{
685
0
  WINPR_ASSERT(context);
686
0
  winpr_RAND(context->RandomSessionKey, sizeof(context->RandomSessionKey));
687
0
}
688
689
/**
690
 * Generate ExportedSessionKey (the RandomSessionKey, exported)
691
 * @param context A pointer to the NTLM context
692
 */
693
694
void ntlm_generate_exported_session_key(NTLM_CONTEXT* context)
695
0
{
696
0
  WINPR_ASSERT(context);
697
698
0
  CopyMemory(context->ExportedSessionKey, context->RandomSessionKey,
699
0
             sizeof(context->ExportedSessionKey));
700
0
}
701
702
/**
703
 * Encrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key).
704
 * @param context A pointer to the NTLM context
705
 */
706
707
void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context)
708
0
{
709
  /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
710
   * KeyExchangeKey */
711
0
  WINPR_ASSERT(context);
712
0
  ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey,
713
0
            context->EncryptedRandomSessionKey);
714
0
}
715
716
/**
717
 * Decrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key).
718
 * @param context A pointer to the NTLM context
719
 */
720
721
void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context)
722
0
{
723
0
  WINPR_ASSERT(context);
724
725
  /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
726
   * KeyExchangeKey */
727
728
  /**
729
   *  if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
730
   *    Set RandomSessionKey to RC4K(KeyExchangeKey,
731
   * AUTHENTICATE_MESSAGE.EncryptedRandomSessionKey) else Set RandomSessionKey to KeyExchangeKey
732
   */
733
0
  if (context->NegotiateKeyExchange)
734
0
  {
735
0
    WINPR_ASSERT(sizeof(context->EncryptedRandomSessionKey) ==
736
0
                 sizeof(context->RandomSessionKey));
737
0
    ntlm_rc4k(context->KeyExchangeKey, sizeof(context->EncryptedRandomSessionKey),
738
0
              context->EncryptedRandomSessionKey, context->RandomSessionKey);
739
0
  }
740
0
  else
741
0
  {
742
0
    WINPR_ASSERT(sizeof(context->RandomSessionKey) == sizeof(context->KeyExchangeKey));
743
0
    CopyMemory(context->RandomSessionKey, context->KeyExchangeKey,
744
0
               sizeof(context->RandomSessionKey));
745
0
  }
746
0
}
747
748
/**
749
 * Generate signing key msdn{cc236711}
750
 *
751
 * @param exported_session_key ExportedSessionKey
752
 * @param sign_magic Sign magic string
753
 * @param signing_key Destination signing key
754
 *
755
 * @return \b TRUE for success, \b FALSE for failure
756
 */
757
758
static BOOL ntlm_generate_signing_key(BYTE* exported_session_key, const SecBuffer* sign_magic,
759
                                      BYTE* signing_key)
760
0
{
761
0
  BOOL rc = FALSE;
762
0
  size_t length = 0;
763
0
  BYTE* value = NULL;
764
765
0
  WINPR_ASSERT(exported_session_key);
766
0
  WINPR_ASSERT(sign_magic);
767
0
  WINPR_ASSERT(signing_key);
768
769
0
  length = WINPR_MD5_DIGEST_LENGTH + sign_magic->cbBuffer;
770
0
  value = (BYTE*)malloc(length);
771
772
0
  if (!value)
773
0
    goto out;
774
775
  /* Concatenate ExportedSessionKey with sign magic */
776
0
  CopyMemory(value, exported_session_key, WINPR_MD5_DIGEST_LENGTH);
777
0
  CopyMemory(&value[WINPR_MD5_DIGEST_LENGTH], sign_magic->pvBuffer, sign_magic->cbBuffer);
778
779
0
  rc = winpr_Digest(WINPR_MD_MD5, value, length, signing_key, WINPR_MD5_DIGEST_LENGTH);
780
781
0
out:
782
0
  free(value);
783
0
  return rc;
784
0
}
785
786
/**
787
 * Generate client signing key (ClientSigningKey). msdn{cc236711}
788
 * @param context A pointer to the NTLM context
789
 *
790
 * @return \b TRUE for success, \b FALSE for failure
791
 */
792
793
BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context)
794
0
{
795
0
  const SecBuffer signMagic = { sizeof(NTLM_CLIENT_SIGN_MAGIC), 0, NTLM_CLIENT_SIGN_MAGIC };
796
797
0
  WINPR_ASSERT(context);
798
0
  return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
799
0
                                   context->ClientSigningKey);
800
0
}
801
802
/**
803
 * Generate server signing key (ServerSigningKey). msdn{cc236711}
804
 * @param context A pointer to the NTLM context
805
 *
806
 * @return \b TRUE for success, \b FALSE for failure
807
 */
808
809
BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context)
810
0
{
811
0
  const SecBuffer signMagic = { sizeof(NTLM_SERVER_SIGN_MAGIC), 0, NTLM_SERVER_SIGN_MAGIC };
812
813
0
  WINPR_ASSERT(context);
814
0
  return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
815
0
                                   context->ServerSigningKey);
816
0
}
817
818
/**
819
 * Generate client sealing key (ClientSealingKey). msdn{cc236712}
820
 * @param context A pointer to the NTLM context
821
 *
822
 * @return \b TRUE for success, \b FALSE for failure
823
 */
824
825
BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context)
826
0
{
827
0
  const SecBuffer sealMagic = { sizeof(NTLM_CLIENT_SEAL_MAGIC), 0, NTLM_CLIENT_SEAL_MAGIC };
828
829
0
  WINPR_ASSERT(context);
830
0
  return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
831
0
                                   context->ClientSealingKey);
832
0
}
833
834
/**
835
 * Generate server sealing key (ServerSealingKey). msdn{cc236712}
836
 * @param context A pointer to the NTLM context
837
 *
838
 * @return \b TRUE for success, \b FALSE for failure
839
 */
840
841
BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context)
842
0
{
843
0
  const SecBuffer sealMagic = { sizeof(NTLM_SERVER_SEAL_MAGIC), 0, NTLM_SERVER_SEAL_MAGIC };
844
845
0
  WINPR_ASSERT(context);
846
0
  return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
847
0
                                   context->ServerSealingKey);
848
0
}
849
850
/**
851
 * Initialize RC4 stream cipher states for sealing.
852
 * @param context A pointer to the NTLM context
853
 */
854
855
BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context)
856
0
{
857
0
  WINPR_ASSERT(context);
858
0
  if (context->server)
859
0
  {
860
0
    context->SendSigningKey = context->ServerSigningKey;
861
0
    context->RecvSigningKey = context->ClientSigningKey;
862
0
    context->SendSealingKey = context->ClientSealingKey;
863
0
    context->RecvSealingKey = context->ServerSealingKey;
864
0
    context->SendRc4Seal =
865
0
        winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
866
0
    context->RecvRc4Seal =
867
0
        winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
868
0
  }
869
0
  else
870
0
  {
871
0
    context->SendSigningKey = context->ClientSigningKey;
872
0
    context->RecvSigningKey = context->ServerSigningKey;
873
0
    context->SendSealingKey = context->ServerSealingKey;
874
0
    context->RecvSealingKey = context->ClientSealingKey;
875
0
    context->SendRc4Seal =
876
0
        winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
877
0
    context->RecvRc4Seal =
878
0
        winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
879
0
  }
880
0
  if (!context->SendRc4Seal)
881
0
  {
882
0
    WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal");
883
0
    return FALSE;
884
0
  }
885
0
  if (!context->RecvRc4Seal)
886
0
  {
887
0
    WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal");
888
0
    return FALSE;
889
0
  }
890
0
  return TRUE;
891
0
}
892
893
BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size)
894
0
{
895
0
  BOOL rc = FALSE;
896
  /*
897
   * Compute the HMAC-MD5 hash of ConcatenationOf(NEGOTIATE_MESSAGE,
898
   * CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE) using the ExportedSessionKey
899
   */
900
0
  WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
901
902
0
  WINPR_ASSERT(context);
903
0
  WINPR_ASSERT(mic);
904
0
  WINPR_ASSERT(size >= WINPR_MD5_DIGEST_LENGTH);
905
906
0
  memset(mic, 0, size);
907
0
  if (!hmac)
908
0
    return FALSE;
909
910
0
  if (winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->ExportedSessionKey, WINPR_MD5_DIGEST_LENGTH))
911
0
  {
912
0
    winpr_HMAC_Update(hmac, (BYTE*)context->NegotiateMessage.pvBuffer,
913
0
                      context->NegotiateMessage.cbBuffer);
914
0
    winpr_HMAC_Update(hmac, (BYTE*)context->ChallengeMessage.pvBuffer,
915
0
                      context->ChallengeMessage.cbBuffer);
916
917
0
    if (context->MessageIntegrityCheckOffset > 0)
918
0
    {
919
0
      const BYTE* auth = (BYTE*)context->AuthenticateMessage.pvBuffer;
920
0
      const BYTE data[WINPR_MD5_DIGEST_LENGTH] = { 0 };
921
0
      const size_t rest = context->MessageIntegrityCheckOffset + sizeof(data);
922
923
0
      WINPR_ASSERT(rest <= context->AuthenticateMessage.cbBuffer);
924
0
      winpr_HMAC_Update(hmac, &auth[0], context->MessageIntegrityCheckOffset);
925
0
      winpr_HMAC_Update(hmac, data, sizeof(data));
926
0
      winpr_HMAC_Update(hmac, &auth[rest], context->AuthenticateMessage.cbBuffer - rest);
927
0
    }
928
0
    else
929
0
    {
930
0
      winpr_HMAC_Update(hmac, (BYTE*)context->AuthenticateMessage.pvBuffer,
931
0
                        context->AuthenticateMessage.cbBuffer);
932
0
    }
933
0
    winpr_HMAC_Final(hmac, mic, WINPR_MD5_DIGEST_LENGTH);
934
0
    rc = TRUE;
935
0
  }
936
937
0
  winpr_HMAC_Free(hmac);
938
0
  return rc;
939
0
}