FreeRDP
ntlm_compute.c
1 
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 #define TAG WINPR_TAG("sspi.NTLM")
38 
39 #define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what) \
40  Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \
41  __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 
58 BOOL ntlm_get_version_info(NTLM_VERSION_INFO* versionInfo)
59 {
60  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  versionInfo->ProductMajorVersion = 10;
78  versionInfo->ProductMinorVersion = 0;
79  versionInfo->ProductBuild = 22631;
80 #endif
81  ZeroMemory(versionInfo->Reserved, sizeof(versionInfo->Reserved));
82  versionInfo->NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3;
83  return TRUE;
84 }
85 
94 BOOL ntlm_read_version_info(wStream* s, NTLM_VERSION_INFO* versionInfo)
95 {
96  WINPR_ASSERT(s);
97  WINPR_ASSERT(versionInfo);
98 
99  if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
100  return FALSE;
101 
102  Stream_Read_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */
103  Stream_Read_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */
104  Stream_Read_UINT16(s, versionInfo->ProductBuild); /* ProductBuild (2 bytes) */
105  Stream_Read(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */
106  Stream_Read_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */
107  return TRUE;
108 }
109 
118 BOOL ntlm_write_version_info(wStream* s, const NTLM_VERSION_INFO* versionInfo)
119 {
120  WINPR_ASSERT(s);
121  WINPR_ASSERT(versionInfo);
122 
123  if (!Stream_CheckAndLogRequiredCapacityEx(
124  TAG, WLOG_WARN, s, 5ull + sizeof(versionInfo->Reserved), 1ull,
125  "%s(%s:%" PRIuz ") NTLM_VERSION_INFO", __func__, __FILE__, (size_t)__LINE__))
126  return FALSE;
127 
128  Stream_Write_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */
129  Stream_Write_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */
130  Stream_Write_UINT16(s, versionInfo->ProductBuild); /* ProductBuild (2 bytes) */
131  Stream_Write(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */
132  Stream_Write_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */
133  return TRUE;
134 }
135 
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 {
157  size_t size = 0;
158  WINPR_ASSERT(s);
159  WINPR_ASSERT(challenge);
160 
161  if (!Stream_CheckAndLogRequiredLength(TAG, s, 28))
162  return FALSE;
163 
164  Stream_Read_UINT8(s, challenge->RespType);
165  Stream_Read_UINT8(s, challenge->HiRespType);
166  Stream_Read_UINT16(s, challenge->Reserved1);
167  Stream_Read_UINT32(s, challenge->Reserved2);
168  Stream_Read(s, challenge->Timestamp, 8);
169  Stream_Read(s, challenge->ClientChallenge, 8);
170  Stream_Read_UINT32(s, challenge->Reserved3);
171  size = Stream_Length(s) - Stream_GetPosition(s);
172 
173  if (size > UINT32_MAX)
174  {
175  WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::cbAvPairs too large, got %" PRIuz "bytes", size);
176  return FALSE;
177  }
178 
179  challenge->cbAvPairs = (UINT32)size;
180  challenge->AvPairs = (NTLM_AV_PAIR*)malloc(challenge->cbAvPairs);
181 
182  if (!challenge->AvPairs)
183  {
184  WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::AvPairs failed to allocate %" PRIu32 "bytes",
185  challenge->cbAvPairs);
186  return FALSE;
187  }
188 
189  Stream_Read(s, challenge->AvPairs, size);
190  return TRUE;
191 }
192 
193 static BOOL ntlm_write_ntlm_v2_client_challenge(wStream* s,
194  const NTLMv2_CLIENT_CHALLENGE* challenge)
195 {
196  ULONG length = 0;
197 
198  WINPR_ASSERT(s);
199  WINPR_ASSERT(challenge);
200 
201  if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 28, "NTLMv2_CLIENT_CHALLENGE"))
202  return FALSE;
203 
204  Stream_Write_UINT8(s, challenge->RespType);
205  Stream_Write_UINT8(s, challenge->HiRespType);
206  Stream_Write_UINT16(s, challenge->Reserved1);
207  Stream_Write_UINT32(s, challenge->Reserved2);
208  Stream_Write(s, challenge->Timestamp, 8);
209  Stream_Write(s, challenge->ClientChallenge, 8);
210  Stream_Write_UINT32(s, challenge->Reserved3);
211  length = ntlm_av_pair_list_length(challenge->AvPairs, challenge->cbAvPairs);
212 
213  if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
214  return FALSE;
215 
216  Stream_Write(s, challenge->AvPairs, length);
217  return TRUE;
218 }
219 
220 BOOL ntlm_read_ntlm_v2_response(wStream* s, NTLMv2_RESPONSE* response)
221 {
222  WINPR_ASSERT(s);
223  WINPR_ASSERT(response);
224 
225  if (!Stream_CheckAndLogRequiredLength(TAG, s, 16))
226  return FALSE;
227 
228  Stream_Read(s, response->Response, 16);
229  return ntlm_read_ntlm_v2_client_challenge(s, &(response->Challenge));
230 }
231 
232 BOOL ntlm_write_ntlm_v2_response(wStream* s, const NTLMv2_RESPONSE* response)
233 {
234  WINPR_ASSERT(s);
235  WINPR_ASSERT(response);
236 
237  if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16ull, "NTLMv2_RESPONSE"))
238  return FALSE;
239 
240  Stream_Write(s, response->Response, 16);
241  return ntlm_write_ntlm_v2_client_challenge(s, &(response->Challenge));
242 }
243 
249 void ntlm_current_time(BYTE* timestamp)
250 {
251  FILETIME ft = { 0 };
252 
253  WINPR_ASSERT(timestamp);
254 
255  GetSystemTimeAsFileTime(&ft);
256  CopyMemory(timestamp, &(ft), sizeof(ft));
257 }
258 
265 void ntlm_generate_timestamp(NTLM_CONTEXT* context)
266 {
267  WINPR_ASSERT(context);
268 
269  if (memcmp(context->ChallengeTimestamp, NTLM_NULL_BUFFER, 8) != 0)
270  CopyMemory(context->Timestamp, context->ChallengeTimestamp, 8);
271  else
272  ntlm_current_time(context->Timestamp);
273 }
274 
275 static BOOL ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
276 {
277  BOOL rc = FALSE;
278  WINPR_SAM* sam = NULL;
279  WINPR_SAM_ENTRY* entry = NULL;
280  SSPI_CREDENTIALS* credentials = NULL;
281 
282  WINPR_ASSERT(context);
283  WINPR_ASSERT(hash);
284 
285  credentials = context->credentials;
286  sam = SamOpen(context->SamFile, TRUE);
287 
288  if (!sam)
289  goto fail;
290 
291  entry = SamLookupUserW(
292  sam, (LPWSTR)credentials->identity.User, credentials->identity.UserLength * sizeof(WCHAR),
293  (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * sizeof(WCHAR));
294 
295  if (!entry)
296  {
297  entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User,
298  credentials->identity.UserLength * sizeof(WCHAR), NULL, 0);
299  }
300 
301  if (!entry)
302  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  NTOWFv2FromHashW(entry->NtHash, (LPWSTR)credentials->identity.User,
309  credentials->identity.UserLength * sizeof(WCHAR),
310  (LPWSTR)credentials->identity.Domain,
311  credentials->identity.DomainLength * sizeof(WCHAR), hash);
312 
313  rc = TRUE;
314 
315 fail:
316  SamFreeEntry(sam, entry);
317  SamClose(sam);
318  if (!rc)
319  WLog_ERR(TAG, "Error: Could not find user in SAM database");
320 
321  return rc;
322 }
323 
324 static int hexchar2nibble(WCHAR wc)
325 {
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  switch (wc)
340  {
341  case L'0':
342  case L'1':
343  case L'2':
344  case L'3':
345  case L'4':
346  case L'5':
347  case L'6':
348  case L'7':
349  case L'8':
350  case L'9':
351  return wc - L'0';
352  case L'a':
353  case L'b':
354  case L'c':
355  case L'd':
356  case L'e':
357  case L'f':
358  return wc - L'a' + 10;
359  case L'A':
360  case L'B':
361  case L'C':
362  case L'D':
363  case L'E':
364  case L'F':
365  return wc - L'A' + 10;
366  default:
367  return -1;
368  }
369 }
370 static int ntlm_convert_password_hash(NTLM_CONTEXT* context, BYTE* hash, size_t hashlen)
371 {
372  const size_t required_len = 2ull * hashlen;
373 
374  WINPR_ASSERT(context);
375  WINPR_ASSERT(hash);
376 
377  SSPI_CREDENTIALS* credentials = context->credentials;
378  /* Password contains a password hash of length (PasswordLength -
379  * SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) */
380  const ULONG PasswordHashLength = credentials->identity.PasswordLength -
381  /* Macro [globalScope] */ SSPI_CREDENTIALS_HASH_LENGTH_OFFSET;
382 
383  if (PasswordHashLength != required_len)
384  {
385  WLog_ERR(TAG,
386  "PasswordHash has invalid length %" PRIu32 " must be exactly %" PRIuz " bytes",
387  PasswordHashLength, required_len);
388  return -1;
389  }
390 
391  const WCHAR* PasswordHash = credentials->identity.Password;
392  for (size_t x = 0; x < hashlen; x++)
393  {
394  const int hi = hexchar2nibble(PasswordHash[2 * x]);
395  if (hi < 0)
396  {
397  WLog_ERR(TAG, "PasswordHash has an invalid value at position %" PRIuz, 2 * x);
398  return -1;
399  }
400  const int lo = hexchar2nibble(PasswordHash[2 * x + 1]);
401  if (lo < 0)
402  {
403  WLog_ERR(TAG, "PasswordHash has an invalid value at position %" PRIuz, 2 * x + 1);
404  return -1;
405  }
406  const BYTE val = (BYTE)((hi << 4) | lo);
407  hash[x] = val;
408  }
409 
410  return 1;
411 }
412 
413 static BOOL ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
414 {
415  SSPI_CREDENTIALS* credentials = NULL;
416 
417  WINPR_ASSERT(context);
418  WINPR_ASSERT(hash);
419 
420  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  if (memcmp(context->NtlmV2Hash, NTLM_NULL_BUFFER, 16) != 0)
445  return TRUE;
446 
447  if (!credentials)
448  return FALSE;
449  else if (memcmp(context->NtlmHash, NTLM_NULL_BUFFER, 16) != 0)
450  {
451  NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
452  credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain,
453  credentials->identity.DomainLength * 2, hash);
454  }
455  else if (credentials->identity.PasswordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET)
456  {
457  /* Special case for WinPR: password hash */
458  if (ntlm_convert_password_hash(context, context->NtlmHash, sizeof(context->NtlmHash)) < 0)
459  return FALSE;
460 
461  NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
462  credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain,
463  credentials->identity.DomainLength * 2, hash);
464  }
465  else if (credentials->identity.Password)
466  {
467  NTOWFv2W((LPWSTR)credentials->identity.Password, credentials->identity.PasswordLength * 2,
468  (LPWSTR)credentials->identity.User, credentials->identity.UserLength * 2,
469  (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * 2,
470  hash);
471  }
472  else if (context->HashCallback)
473  {
474  int ret = 0;
475  SecBuffer proofValue;
476  SecBuffer micValue;
477 
478  if (ntlm_computeProofValue(context, &proofValue) != SEC_E_OK)
479  return FALSE;
480 
481  if (ntlm_computeMicValue(context, &micValue) != SEC_E_OK)
482  {
483  sspi_SecBufferFree(&proofValue);
484  return FALSE;
485  }
486 
487  ret = context->HashCallback(context->HashCallbackArg, &credentials->identity, &proofValue,
488  context->EncryptedRandomSessionKey,
489  context->AUTHENTICATE_MESSAGE.MessageIntegrityCheck, &micValue,
490  hash);
491  sspi_SecBufferFree(&proofValue);
492  sspi_SecBufferFree(&micValue);
493  return ret ? TRUE : FALSE;
494  }
495  else if (context->UseSamFileDatabase)
496  {
497  return ntlm_fetch_ntlm_v2_hash(context, hash);
498  }
499 
500  return TRUE;
501 }
502 
503 SECURITY_STATUS ntlm_compute_lm_v2_response(NTLM_CONTEXT* context)
504 {
505  BYTE* response = NULL;
506  BYTE value[WINPR_MD5_DIGEST_LENGTH] = { 0 };
507 
508  WINPR_ASSERT(context);
509 
510  if (context->LmCompatibilityLevel < 2)
511  {
512  if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24))
513  return SEC_E_INSUFFICIENT_MEMORY;
514 
515  ZeroMemory(context->LmChallengeResponse.pvBuffer, 24);
516  return SEC_E_OK;
517  }
518 
519  /* Compute the NTLMv2 hash */
520 
521  if (!ntlm_compute_ntlm_v2_hash(context, context->NtlmV2Hash))
522  return SEC_E_NO_CREDENTIALS;
523 
524  /* Concatenate the server and client challenges */
525  CopyMemory(value, context->ServerChallenge, 8);
526  CopyMemory(&value[8], context->ClientChallenge, 8);
527 
528  if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24))
529  return SEC_E_INSUFFICIENT_MEMORY;
530 
531  response = (BYTE*)context->LmChallengeResponse.pvBuffer;
532  /* Compute the HMAC-MD5 hash of the resulting value using the NTLMv2 hash as the key */
533  winpr_HMAC(WINPR_MD_MD5, (void*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, (BYTE*)value,
534  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  CopyMemory(&response[16], context->ClientChallenge, 8);
538  return SEC_E_OK;
539 }
540 
551 SECURITY_STATUS ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
552 {
553  SecBuffer ntlm_v2_temp = { 0 };
554  SecBuffer ntlm_v2_temp_chal = { 0 };
555 
556  WINPR_ASSERT(context);
557 
558  PSecBuffer TargetInfo = &context->ChallengeTargetInfo;
559  SECURITY_STATUS ret = SEC_E_INSUFFICIENT_MEMORY;
560 
561  if (!sspi_SecBufferAlloc(&ntlm_v2_temp, TargetInfo->cbBuffer + 28))
562  goto exit;
563 
564  ZeroMemory(ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
565  BYTE* blob = (BYTE*)ntlm_v2_temp.pvBuffer;
566 
567  /* Compute the NTLMv2 hash */
568  ret = SEC_E_NO_CREDENTIALS;
569  if (!ntlm_compute_ntlm_v2_hash(context, (BYTE*)context->NtlmV2Hash))
570  goto exit;
571 
572  /* Construct temp */
573  blob[0] = 1; /* RespType (1 byte) */
574  blob[1] = 1; /* HighRespType (1 byte) */
575  /* Reserved1 (2 bytes) */
576  /* Reserved2 (4 bytes) */
577  CopyMemory(&blob[8], context->Timestamp, 8); /* Timestamp (8 bytes) */
578  CopyMemory(&blob[16], context->ClientChallenge, 8); /* ClientChallenge (8 bytes) */
579  /* Reserved3 (4 bytes) */
580  CopyMemory(&blob[28], TargetInfo->pvBuffer, TargetInfo->cbBuffer);
581 #ifdef WITH_DEBUG_NTLM
582  WLog_VRB(TAG, "NTLMv2 Response Temp Blob");
583  winpr_HexDump(TAG, WLOG_TRACE, ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
584 #endif
585 
586  /* Concatenate server challenge with temp */
587  ret = SEC_E_INSUFFICIENT_MEMORY;
588  if (!sspi_SecBufferAlloc(&ntlm_v2_temp_chal, ntlm_v2_temp.cbBuffer + 8))
589  goto exit;
590 
591  blob = (BYTE*)ntlm_v2_temp_chal.pvBuffer;
592  CopyMemory(blob, context->ServerChallenge, 8);
593  CopyMemory(&blob[8], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
594  winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
595  (BYTE*)ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer,
596  context->NtProofString, WINPR_MD5_DIGEST_LENGTH);
597 
598  /* NtChallengeResponse, Concatenate NTProofStr with temp */
599 
600  if (!sspi_SecBufferAlloc(&context->NtChallengeResponse, ntlm_v2_temp.cbBuffer + 16))
601  goto exit;
602 
603  blob = (BYTE*)context->NtChallengeResponse.pvBuffer;
604  CopyMemory(blob, context->NtProofString, WINPR_MD5_DIGEST_LENGTH);
605  CopyMemory(&blob[16], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
606  /* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2 hash as the key */
607  winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
608  context->NtProofString, WINPR_MD5_DIGEST_LENGTH, context->SessionBaseKey,
609  WINPR_MD5_DIGEST_LENGTH);
610  ret = SEC_E_OK;
611 exit:
612  sspi_SecBufferFree(&ntlm_v2_temp);
613  sspi_SecBufferFree(&ntlm_v2_temp_chal);
614  return ret;
615 }
616 
625 void ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext)
626 {
627  WINPR_RC4_CTX* rc4 = winpr_RC4_New(key, 16);
628 
629  if (rc4)
630  {
631  winpr_RC4_Update(rc4, length, plaintext, ciphertext);
632  winpr_RC4_Free(rc4);
633  }
634 }
635 
641 void ntlm_generate_client_challenge(NTLM_CONTEXT* context)
642 {
643  WINPR_ASSERT(context);
644 
645  /* ClientChallenge is used in computation of LMv2 and NTLMv2 responses */
646  if (memcmp(context->ClientChallenge, NTLM_NULL_BUFFER, sizeof(context->ClientChallenge)) == 0)
647  winpr_RAND(context->ClientChallenge, sizeof(context->ClientChallenge));
648 }
649 
655 void ntlm_generate_server_challenge(NTLM_CONTEXT* context)
656 {
657  WINPR_ASSERT(context);
658 
659  if (memcmp(context->ServerChallenge, NTLM_NULL_BUFFER, sizeof(context->ServerChallenge)) == 0)
660  winpr_RAND(context->ServerChallenge, sizeof(context->ServerChallenge));
661 }
662 
668 void ntlm_generate_key_exchange_key(NTLM_CONTEXT* context)
669 {
670  WINPR_ASSERT(context);
671  WINPR_ASSERT(sizeof(context->KeyExchangeKey) == sizeof(context->SessionBaseKey));
672 
673  /* In NTLMv2, KeyExchangeKey is the 128-bit SessionBaseKey */
674  CopyMemory(context->KeyExchangeKey, context->SessionBaseKey, sizeof(context->KeyExchangeKey));
675 }
676 
682 void ntlm_generate_random_session_key(NTLM_CONTEXT* context)
683 {
684  WINPR_ASSERT(context);
685  winpr_RAND(context->RandomSessionKey, sizeof(context->RandomSessionKey));
686 }
687 
693 void ntlm_generate_exported_session_key(NTLM_CONTEXT* context)
694 {
695  WINPR_ASSERT(context);
696 
697  CopyMemory(context->ExportedSessionKey, context->RandomSessionKey,
698  sizeof(context->ExportedSessionKey));
699 }
700 
706 void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context)
707 {
708  /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
709  * KeyExchangeKey */
710  WINPR_ASSERT(context);
711  ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey,
712  context->EncryptedRandomSessionKey);
713 }
714 
720 void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context)
721 {
722  WINPR_ASSERT(context);
723 
724  /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
725  * KeyExchangeKey */
726 
732  if (context->NegotiateKeyExchange)
733  {
734  WINPR_ASSERT(sizeof(context->EncryptedRandomSessionKey) ==
735  sizeof(context->RandomSessionKey));
736  ntlm_rc4k(context->KeyExchangeKey, sizeof(context->EncryptedRandomSessionKey),
737  context->EncryptedRandomSessionKey, context->RandomSessionKey);
738  }
739  else
740  {
741  WINPR_ASSERT(sizeof(context->RandomSessionKey) == sizeof(context->KeyExchangeKey));
742  CopyMemory(context->RandomSessionKey, context->KeyExchangeKey,
743  sizeof(context->RandomSessionKey));
744  }
745 }
746 
757 static BOOL ntlm_generate_signing_key(BYTE* exported_session_key, const SecBuffer* sign_magic,
758  BYTE* signing_key)
759 {
760  BOOL rc = FALSE;
761  size_t length = 0;
762  BYTE* value = NULL;
763 
764  WINPR_ASSERT(exported_session_key);
765  WINPR_ASSERT(sign_magic);
766  WINPR_ASSERT(signing_key);
767 
768  length = WINPR_MD5_DIGEST_LENGTH + sign_magic->cbBuffer;
769  value = (BYTE*)malloc(length);
770 
771  if (!value)
772  goto out;
773 
774  /* Concatenate ExportedSessionKey with sign magic */
775  CopyMemory(value, exported_session_key, WINPR_MD5_DIGEST_LENGTH);
776  CopyMemory(&value[WINPR_MD5_DIGEST_LENGTH], sign_magic->pvBuffer, sign_magic->cbBuffer);
777 
778  rc = winpr_Digest(WINPR_MD_MD5, value, length, signing_key, WINPR_MD5_DIGEST_LENGTH);
779 
780 out:
781  free(value);
782  return rc;
783 }
784 
792 BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context)
793 {
794  const SecBuffer signMagic = { sizeof(NTLM_CLIENT_SIGN_MAGIC), 0, NTLM_CLIENT_SIGN_MAGIC };
795 
796  WINPR_ASSERT(context);
797  return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
798  context->ClientSigningKey);
799 }
800 
808 BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context)
809 {
810  const SecBuffer signMagic = { sizeof(NTLM_SERVER_SIGN_MAGIC), 0, NTLM_SERVER_SIGN_MAGIC };
811 
812  WINPR_ASSERT(context);
813  return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
814  context->ServerSigningKey);
815 }
816 
824 BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context)
825 {
826  const SecBuffer sealMagic = { sizeof(NTLM_CLIENT_SEAL_MAGIC), 0, NTLM_CLIENT_SEAL_MAGIC };
827 
828  WINPR_ASSERT(context);
829  return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
830  context->ClientSealingKey);
831 }
832 
840 BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context)
841 {
842  const SecBuffer sealMagic = { sizeof(NTLM_SERVER_SEAL_MAGIC), 0, NTLM_SERVER_SEAL_MAGIC };
843 
844  WINPR_ASSERT(context);
845  return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
846  context->ServerSealingKey);
847 }
848 
854 BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context)
855 {
856  WINPR_ASSERT(context);
857  if (context->server)
858  {
859  context->SendSigningKey = context->ServerSigningKey;
860  context->RecvSigningKey = context->ClientSigningKey;
861  context->SendSealingKey = context->ClientSealingKey;
862  context->RecvSealingKey = context->ServerSealingKey;
863  context->SendRc4Seal =
864  winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
865  context->RecvRc4Seal =
866  winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
867  }
868  else
869  {
870  context->SendSigningKey = context->ClientSigningKey;
871  context->RecvSigningKey = context->ServerSigningKey;
872  context->SendSealingKey = context->ServerSealingKey;
873  context->RecvSealingKey = context->ClientSealingKey;
874  context->SendRc4Seal =
875  winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
876  context->RecvRc4Seal =
877  winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
878  }
879  if (!context->SendRc4Seal)
880  {
881  WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal");
882  return FALSE;
883  }
884  if (!context->RecvRc4Seal)
885  {
886  WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal");
887  return FALSE;
888  }
889  return TRUE;
890 }
891 
892 BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size)
893 {
894  BOOL rc = FALSE;
895  /*
896  * Compute the HMAC-MD5 hash of ConcatenationOf(NEGOTIATE_MESSAGE,
897  * CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE) using the ExportedSessionKey
898  */
899  WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
900 
901  WINPR_ASSERT(context);
902  WINPR_ASSERT(mic);
903  WINPR_ASSERT(size >= WINPR_MD5_DIGEST_LENGTH);
904 
905  memset(mic, 0, size);
906  if (!hmac)
907  return FALSE;
908 
909  if (winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->ExportedSessionKey, WINPR_MD5_DIGEST_LENGTH))
910  {
911  winpr_HMAC_Update(hmac, (BYTE*)context->NegotiateMessage.pvBuffer,
912  context->NegotiateMessage.cbBuffer);
913  winpr_HMAC_Update(hmac, (BYTE*)context->ChallengeMessage.pvBuffer,
914  context->ChallengeMessage.cbBuffer);
915 
916  if (context->MessageIntegrityCheckOffset > 0)
917  {
918  const BYTE* auth = (BYTE*)context->AuthenticateMessage.pvBuffer;
919  const BYTE data[WINPR_MD5_DIGEST_LENGTH] = { 0 };
920  const size_t rest = context->MessageIntegrityCheckOffset + sizeof(data);
921 
922  WINPR_ASSERT(rest <= context->AuthenticateMessage.cbBuffer);
923  winpr_HMAC_Update(hmac, &auth[0], context->MessageIntegrityCheckOffset);
924  winpr_HMAC_Update(hmac, data, sizeof(data));
925  winpr_HMAC_Update(hmac, &auth[rest], context->AuthenticateMessage.cbBuffer - rest);
926  }
927  else
928  {
929  winpr_HMAC_Update(hmac, (BYTE*)context->AuthenticateMessage.pvBuffer,
930  context->AuthenticateMessage.cbBuffer);
931  }
932  winpr_HMAC_Final(hmac, mic, WINPR_MD5_DIGEST_LENGTH);
933  rc = TRUE;
934  }
935 
936  winpr_HMAC_Free(hmac);
937  return rc;
938 }