FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
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
43static char NTLM_CLIENT_SIGN_MAGIC[] = "session key to client-to-server signing key magic constant";
44static char NTLM_SERVER_SIGN_MAGIC[] = "session key to server-to-client signing key magic constant";
45static char NTLM_CLIENT_SEAL_MAGIC[] = "session key to client-to-server sealing key magic constant";
46static char NTLM_SERVER_SEAL_MAGIC[] = "session key to server-to-client sealing key magic constant";
47
48static const BYTE NTLM_NULL_BUFFER[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
49 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
50
58BOOL 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
94BOOL 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
118BOOL 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
141void 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
155static 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
193static 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
220BOOL 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
232BOOL 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
249void 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
265void 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
275static 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
315fail:
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
324static 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}
370static 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
413static 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
503SECURITY_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
551SECURITY_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;
611exit:
612 sspi_SecBufferFree(&ntlm_v2_temp);
613 sspi_SecBufferFree(&ntlm_v2_temp_chal);
614 return ret;
615}
616
625void 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
641void 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
655void 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
668void 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
682void ntlm_generate_random_session_key(NTLM_CONTEXT* context)
683{
684 WINPR_ASSERT(context);
685 winpr_RAND(context->RandomSessionKey, sizeof(context->RandomSessionKey));
686}
687
693void 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
706void 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
720void 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
757static 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
780out:
781 free(value);
782 return rc;
783}
784
792BOOL 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
808BOOL 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
824BOOL 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
840BOOL 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
854BOOL 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
892BOOL 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}