FreeRDP
Loading...
Searching...
No Matches
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 = 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 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
249static void ntlm_current_time(BYTE* timestamp, WINPR_ATTR_UNUSED size_t size)
250{
251 FILETIME ft = WINPR_C_ARRAY_INIT;
252
253 WINPR_ASSERT(timestamp);
254 WINPR_ASSERT(size >= sizeof(ft));
255
256 GetSystemTimeAsFileTime(&ft);
257 CopyMemory(timestamp, &(ft), sizeof(ft));
258}
259
266void ntlm_generate_timestamp(NTLM_CONTEXT* context)
267{
268 WINPR_ASSERT(context);
269
270 if (memcmp(context->ChallengeTimestamp, NTLM_NULL_BUFFER, 8) != 0)
271 CopyMemory(context->Timestamp, context->ChallengeTimestamp, 8);
272 else
273 ntlm_current_time(context->Timestamp, sizeof(context->Timestamp));
274}
275
276static BOOL ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
277{
278 BOOL rc = FALSE;
279 WINPR_SAM_ENTRY* entry = nullptr;
280
281 WINPR_ASSERT(context);
282 WINPR_ASSERT(hash);
283
284 SSPI_CREDENTIALS* credentials = context->credentials;
285 WINPR_SAM* sam = SamOpen(context->SamFile, TRUE);
286
287 if (!sam)
288 goto fail;
289
290 if ((credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE) != 0)
291 {
292 entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User,
293 credentials->identity.UserLength * sizeof(WCHAR),
294 (LPWSTR)credentials->identity.Domain,
295 credentials->identity.DomainLength * sizeof(WCHAR));
296
297 if (!entry)
298 {
299 entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User,
300 credentials->identity.UserLength * sizeof(WCHAR), nullptr, 0);
301 }
302 }
303 else if ((credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_ANSI) != 0)
304 {
305 entry = SamLookupUserA(
306 sam, (char*)credentials->identity.User, credentials->identity.UserLength * sizeof(CHAR),
307 (char*)credentials->identity.Domain, credentials->identity.DomainLength * sizeof(CHAR));
308
309 if (!entry)
310 {
311 entry = SamLookupUserA(sam, (char*)credentials->identity.User,
312 credentials->identity.UserLength * sizeof(CHAR), nullptr, 0);
313 }
314 }
315 else
316 goto fail;
317
318 if (!entry)
319 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 rc = NTOWFv2FromHashW(entry->NtHash, (LPWSTR)credentials->identity.User,
326 credentials->identity.UserLength * sizeof(WCHAR),
327 (LPWSTR)credentials->identity.Domain,
328 credentials->identity.DomainLength * sizeof(WCHAR), hash);
329
330fail:
331 SamFreeEntry(sam, entry);
332 SamClose(sam);
333 if (!rc)
334 WLog_ERR(TAG, "Error: Could not find user in SAM database");
335
336 return rc;
337}
338
339static int hexchar2nibble(WCHAR wc)
340{
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 switch (wc)
355 {
356 case L'0':
357 case L'1':
358 case L'2':
359 case L'3':
360 case L'4':
361 case L'5':
362 case L'6':
363 case L'7':
364 case L'8':
365 case L'9':
366 return wc - L'0';
367 case L'a':
368 case L'b':
369 case L'c':
370 case L'd':
371 case L'e':
372 case L'f':
373 return wc - L'a' + 10;
374 case L'A':
375 case L'B':
376 case L'C':
377 case L'D':
378 case L'E':
379 case L'F':
380 return wc - L'A' + 10;
381 default:
382 return -1;
383 }
384}
385static int ntlm_convert_password_hash(NTLM_CONTEXT* context, BYTE* hash, size_t hashlen)
386{
387 const size_t required_len = 2ull * hashlen;
388
389 WINPR_ASSERT(context);
390 WINPR_ASSERT(hash);
391
392 SSPI_CREDENTIALS* credentials = context->credentials;
393 const ULONG PasswordHashLength = credentials->identity.PasswordLength;
394
395 if (PasswordHashLength != required_len)
396 {
397 WLog_ERR(TAG,
398 "PasswordHash has invalid length %" PRIu32 " must be exactly %" PRIuz " bytes",
399 PasswordHashLength, required_len);
400 return -1;
401 }
402
403 const WCHAR* PasswordHash = credentials->identity.Password;
404 for (size_t x = 0; x < hashlen; x++)
405 {
406 const int hi = hexchar2nibble(PasswordHash[2 * x]);
407 if (hi < 0)
408 {
409 WLog_ERR(TAG, "PasswordHash has an invalid value at position %" PRIuz, 2 * x);
410 return -1;
411 }
412 const int lo = hexchar2nibble(PasswordHash[2 * x + 1]);
413 if (lo < 0)
414 {
415 WLog_ERR(TAG, "PasswordHash has an invalid value at position %" PRIuz, 2 * x + 1);
416 return -1;
417 }
418 const BYTE val = (BYTE)((hi << 4) | lo);
419 hash[x] = val;
420 }
421
422 return 1;
423}
424
425static BOOL ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash)
426{
427 WINPR_ASSERT(context);
428 WINPR_ASSERT(hash);
429
430 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 if (memcmp(context->NtlmV2Hash, NTLM_NULL_BUFFER, 16) != 0)
458 return TRUE;
459
460 if (!credentials)
461 return FALSE;
462 else if (memcmp(context->NtlmHash, NTLM_NULL_BUFFER, 16) != 0)
463 {
464 if ((credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE) != 0)
465 {
466 return NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
467 credentials->identity.UserLength * 2,
468 (LPWSTR)credentials->identity.Domain,
469 credentials->identity.DomainLength * 2, hash);
470 }
471 else if ((credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_ANSI) != 0)
472 {
473 return NTOWFv2FromHashA(context->NtlmHash, (char*)credentials->identity.User,
474 credentials->identity.UserLength,
475 (char*)credentials->identity.Domain,
476 credentials->identity.DomainLength, hash);
477 }
478 else
479 return FALSE;
480 }
481 else if (credentials->identity.Flags & SEC_WINPR_AUTH_IDENTITY_PASSWORD_HASH)
482 {
483 /* Special case for WinPR: password hash */
484 if (ntlm_convert_password_hash(context, context->NtlmHash, sizeof(context->NtlmHash)) < 0)
485 return FALSE;
486
487 return NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User,
488 credentials->identity.UserLength * 2,
489 (LPWSTR)credentials->identity.Domain,
490 credentials->identity.DomainLength * 2, hash);
491 }
492 else if (credentials->identity.Password)
493 {
494 return NTOWFv2W(
495 (LPWSTR)credentials->identity.Password, credentials->identity.PasswordLength * 2,
496 (LPWSTR)credentials->identity.User, credentials->identity.UserLength * 2,
497 (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * 2, hash);
498 }
499 else if (context->HashCallback)
500 {
501 SecBuffer proofValue = WINPR_C_ARRAY_INIT;
502 SecBuffer micValue = WINPR_C_ARRAY_INIT;
503
504 if (ntlm_computeProofValue(context, &proofValue) != SEC_E_OK)
505 return FALSE;
506
507 if (ntlm_computeMicValue(context, &micValue) != SEC_E_OK)
508 {
509 sspi_SecBufferFree(&proofValue);
510 return FALSE;
511 }
512
513 const SECURITY_STATUS ret = context->HashCallback(
514 context->HashCallbackArg, &credentials->identity, &proofValue,
515 context->EncryptedRandomSessionKey, context->AUTHENTICATE_MESSAGE.MessageIntegrityCheck,
516 &micValue, hash);
517 sspi_SecBufferFree(&proofValue);
518 sspi_SecBufferFree(&micValue);
519 return ret == SEC_E_OK;
520 }
521 else if (context->UseSamFileDatabase)
522 {
523 return ntlm_fetch_ntlm_v2_hash(context, hash);
524 }
525
526 return TRUE;
527}
528
529SECURITY_STATUS ntlm_compute_lm_v2_response(NTLM_CONTEXT* context)
530{
531 BYTE* response = nullptr;
532 BYTE value[WINPR_MD5_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
533
534 WINPR_ASSERT(context);
535
536 if (context->LmCompatibilityLevel < 2)
537 {
538 if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24))
539 return SEC_E_INSUFFICIENT_MEMORY;
540
541 ZeroMemory(context->LmChallengeResponse.pvBuffer, 24);
542 return SEC_E_OK;
543 }
544
545 /* Compute the NTLMv2 hash */
546
547 if (!ntlm_compute_ntlm_v2_hash(context, context->NtlmV2Hash))
548 return SEC_E_NO_CREDENTIALS;
549
550 /* Concatenate the server and client challenges */
551 CopyMemory(value, context->ServerChallenge, 8);
552 CopyMemory(&value[8], context->ClientChallenge, 8);
553
554 if (!ntlm_SecBufferRealloc(&context->LmChallengeResponse, 24))
555 return SEC_E_INSUFFICIENT_MEMORY;
556
557 response = (BYTE*)context->LmChallengeResponse.pvBuffer;
558 /* Compute the HMAC-MD5 hash of the resulting value using the NTLMv2 hash as the key */
559 if (!winpr_HMAC(WINPR_MD_MD5, (void*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, (BYTE*)value,
560 WINPR_MD5_DIGEST_LENGTH, response, WINPR_MD5_DIGEST_LENGTH))
561 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 CopyMemory(&response[16], context->ClientChallenge, 8);
566 return SEC_E_OK;
567}
568
579SECURITY_STATUS ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context)
580{
581 SecBuffer ntlm_v2_temp = WINPR_C_ARRAY_INIT;
582 SecBuffer ntlm_v2_temp_chal = WINPR_C_ARRAY_INIT;
583
584 WINPR_ASSERT(context);
585
586 PSecBuffer TargetInfo = &context->ChallengeTargetInfo;
587 SECURITY_STATUS ret = SEC_E_INSUFFICIENT_MEMORY;
588
589 if (!sspi_SecBufferAlloc(&ntlm_v2_temp, TargetInfo->cbBuffer + 28))
590 goto exit;
591
592 ZeroMemory(ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
593 {
594 BYTE* blob = (BYTE*)ntlm_v2_temp.pvBuffer;
595
596 /* Compute the NTLMv2 hash */
597 ret = SEC_E_NO_CREDENTIALS;
598 if (!ntlm_compute_ntlm_v2_hash(context, (BYTE*)context->NtlmV2Hash))
599 goto exit;
600
601 /* Construct temp */
602 blob[0] = 1; /* RespType (1 byte) */
603 blob[1] = 1; /* HighRespType (1 byte) */
604 /* Reserved1 (2 bytes) */
605 /* Reserved2 (4 bytes) */
606 CopyMemory(&blob[8], context->Timestamp, 8); /* Timestamp (8 bytes) */
607 CopyMemory(&blob[16], context->ClientChallenge, 8); /* ClientChallenge (8 bytes) */
608 /* Reserved3 (4 bytes) */
609 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 }
615 /* Concatenate server challenge with temp */
616 ret = SEC_E_INSUFFICIENT_MEMORY;
617 if (!sspi_SecBufferAlloc(&ntlm_v2_temp_chal, ntlm_v2_temp.cbBuffer + 8))
618 goto exit;
619
620 {
621 BYTE* blob = (BYTE*)ntlm_v2_temp_chal.pvBuffer;
622 CopyMemory(blob, context->ServerChallenge, 8);
623 CopyMemory(&blob[8], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
624 if (!winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
625 (BYTE*)ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer,
626 context->NtProofString, WINPR_MD5_DIGEST_LENGTH))
627 goto exit;
628 }
629
630 /* NtChallengeResponse, Concatenate NTProofStr with temp */
631 if (!ntlm_SecBufferRealloc(&context->NtChallengeResponse, ntlm_v2_temp.cbBuffer + 16))
632 goto exit;
633
634 {
635 BYTE* blob = (BYTE*)context->NtChallengeResponse.pvBuffer;
636 CopyMemory(blob, context->NtProofString, WINPR_MD5_DIGEST_LENGTH);
637 CopyMemory(&blob[16], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer);
638 }
639 /* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2 hash as the key */
640 if (!winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH,
641 context->NtProofString, WINPR_MD5_DIGEST_LENGTH, context->SessionBaseKey,
642 WINPR_MD5_DIGEST_LENGTH))
643 goto exit;
644 ret = SEC_E_OK;
645exit:
646 sspi_SecBufferFree(&ntlm_v2_temp);
647 sspi_SecBufferFree(&ntlm_v2_temp_chal);
648 return ret;
649}
650
659BOOL ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext)
660{
661 WINPR_RC4_CTX* rc4 = winpr_RC4_New(key, 16);
662
663 if (!rc4)
664 return FALSE;
665
666 const BOOL rc = winpr_RC4_Update(rc4, length, plaintext, ciphertext);
667 winpr_RC4_Free(rc4);
668 return rc;
669}
670
676BOOL ntlm_generate_client_challenge(NTLM_CONTEXT* context)
677{
678 WINPR_ASSERT(context);
679
680 /* ClientChallenge is used in computation of LMv2 and NTLMv2 responses */
681 if (memcmp(context->ClientChallenge, NTLM_NULL_BUFFER, sizeof(context->ClientChallenge)) != 0)
682 return TRUE;
683
684 return winpr_RAND(context->ClientChallenge, sizeof(context->ClientChallenge)) >= 0;
685}
686
692BOOL ntlm_generate_server_challenge(NTLM_CONTEXT* context)
693{
694 WINPR_ASSERT(context);
695
696 if (memcmp(context->ServerChallenge, NTLM_NULL_BUFFER, sizeof(context->ServerChallenge)) != 0)
697 return TRUE;
698
699 return winpr_RAND(context->ServerChallenge, sizeof(context->ServerChallenge)) >= 0;
700}
701
707BOOL ntlm_generate_key_exchange_key(NTLM_CONTEXT* context)
708{
709 WINPR_ASSERT(context);
710 WINPR_ASSERT(sizeof(context->KeyExchangeKey) == sizeof(context->SessionBaseKey));
711
712 /* In NTLMv2, KeyExchangeKey is the 128-bit SessionBaseKey */
713 CopyMemory(context->KeyExchangeKey, context->SessionBaseKey, sizeof(context->KeyExchangeKey));
714 return TRUE;
715}
716
722BOOL ntlm_generate_random_session_key(NTLM_CONTEXT* context)
723{
724 WINPR_ASSERT(context);
725 return winpr_RAND(context->RandomSessionKey, sizeof(context->RandomSessionKey)) >= 0;
726}
727
733BOOL ntlm_generate_exported_session_key(NTLM_CONTEXT* context)
734{
735 WINPR_ASSERT(context);
736 WINPR_ASSERT(sizeof(context->ExportedSessionKey) >= sizeof(context->RandomSessionKey));
737
738 CopyMemory(context->ExportedSessionKey, context->RandomSessionKey,
739 sizeof(context->ExportedSessionKey));
740 return TRUE;
741}
742
748BOOL ntlm_encrypt_random_session_key(NTLM_CONTEXT* context)
749{
750 /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
751 * KeyExchangeKey */
752 WINPR_ASSERT(context);
753 return ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey,
754 context->EncryptedRandomSessionKey);
755}
756
762BOOL ntlm_decrypt_random_session_key(NTLM_CONTEXT* context)
763{
764 WINPR_ASSERT(context);
765
766 /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the
767 * KeyExchangeKey */
768
774 if (context->NegotiateKeyExchange)
775 {
776 WINPR_ASSERT(sizeof(context->EncryptedRandomSessionKey) ==
777 sizeof(context->RandomSessionKey));
778 return ntlm_rc4k(context->KeyExchangeKey, sizeof(context->EncryptedRandomSessionKey),
779 context->EncryptedRandomSessionKey, context->RandomSessionKey);
780 }
781 else
782 {
783 WINPR_ASSERT(sizeof(context->RandomSessionKey) == sizeof(context->KeyExchangeKey));
784 CopyMemory(context->RandomSessionKey, context->KeyExchangeKey,
785 sizeof(context->RandomSessionKey));
786 }
787 return TRUE;
788}
789
800static BOOL ntlm_generate_signing_key(BYTE* exported_session_key, const SecBuffer* sign_magic,
801 BYTE* signing_key)
802{
803 BOOL rc = FALSE;
804
805 WINPR_ASSERT(exported_session_key);
806 WINPR_ASSERT(sign_magic);
807 WINPR_ASSERT(signing_key);
808
809 const size_t length = WINPR_MD5_DIGEST_LENGTH + sign_magic->cbBuffer;
810 BYTE* value = (BYTE*)malloc(length);
811
812 if (!value)
813 goto out;
814
815 /* Concatenate ExportedSessionKey with sign magic */
816 CopyMemory(value, exported_session_key, WINPR_MD5_DIGEST_LENGTH);
817 CopyMemory(&value[WINPR_MD5_DIGEST_LENGTH], sign_magic->pvBuffer, sign_magic->cbBuffer);
818
819 rc = winpr_Digest(WINPR_MD_MD5, value, length, signing_key, WINPR_MD5_DIGEST_LENGTH);
820
821out:
822 free(value);
823 return rc;
824}
825
833BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context)
834{
835 const SecBuffer signMagic = { sizeof(NTLM_CLIENT_SIGN_MAGIC), 0, NTLM_CLIENT_SIGN_MAGIC };
836
837 WINPR_ASSERT(context);
838 return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
839 context->ClientSigningKey);
840}
841
849BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context)
850{
851 const SecBuffer signMagic = { sizeof(NTLM_SERVER_SIGN_MAGIC), 0, NTLM_SERVER_SIGN_MAGIC };
852
853 WINPR_ASSERT(context);
854 return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic,
855 context->ServerSigningKey);
856}
857
865BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context)
866{
867 const SecBuffer sealMagic = { sizeof(NTLM_CLIENT_SEAL_MAGIC), 0, NTLM_CLIENT_SEAL_MAGIC };
868
869 WINPR_ASSERT(context);
870 return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
871 context->ClientSealingKey);
872}
873
881BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context)
882{
883 const SecBuffer sealMagic = { sizeof(NTLM_SERVER_SEAL_MAGIC), 0, NTLM_SERVER_SEAL_MAGIC };
884
885 WINPR_ASSERT(context);
886 return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic,
887 context->ServerSealingKey);
888}
889
895BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context)
896{
897 WINPR_ASSERT(context);
898 if (context->server)
899 {
900 context->SendSigningKey = context->ServerSigningKey;
901 context->RecvSigningKey = context->ClientSigningKey;
902 context->SendSealingKey = context->ClientSealingKey;
903 context->RecvSealingKey = context->ServerSealingKey;
904 context->SendRc4Seal =
905 winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
906 context->RecvRc4Seal =
907 winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
908 }
909 else
910 {
911 context->SendSigningKey = context->ClientSigningKey;
912 context->RecvSigningKey = context->ServerSigningKey;
913 context->SendSealingKey = context->ServerSealingKey;
914 context->RecvSealingKey = context->ClientSealingKey;
915 context->SendRc4Seal =
916 winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey));
917 context->RecvRc4Seal =
918 winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey));
919 }
920 if (!context->SendRc4Seal)
921 {
922 WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal");
923 return FALSE;
924 }
925 if (!context->RecvRc4Seal)
926 {
927 WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal");
928 return FALSE;
929 }
930 return TRUE;
931}
932
933BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size)
934{
935 BOOL rc = FALSE;
936 /*
937 * Compute the HMAC-MD5 hash of ConcatenationOf(NEGOTIATE_MESSAGE,
938 * CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE) using the ExportedSessionKey
939 */
940 WINPR_HMAC_CTX* hmac = winpr_HMAC_New();
941
942 WINPR_ASSERT(context);
943 WINPR_ASSERT(mic);
944 WINPR_ASSERT(size >= WINPR_MD5_DIGEST_LENGTH);
945
946 memset(mic, 0, size);
947 if (!hmac)
948 return FALSE;
949
950 if (!winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->ExportedSessionKey, WINPR_MD5_DIGEST_LENGTH))
951 goto fail;
952
953 if (!winpr_HMAC_Update(hmac, (BYTE*)context->NegotiateMessage.pvBuffer,
954 context->NegotiateMessage.cbBuffer))
955 goto fail;
956 if (!winpr_HMAC_Update(hmac, (BYTE*)context->ChallengeMessage.pvBuffer,
957 context->ChallengeMessage.cbBuffer))
958 goto fail;
959
960 if (context->MessageIntegrityCheckOffset > 0)
961 {
962 const BYTE* auth = (BYTE*)context->AuthenticateMessage.pvBuffer;
963 const BYTE data[WINPR_MD5_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
964 const size_t rest = context->MessageIntegrityCheckOffset + sizeof(data);
965
966 if (rest > context->AuthenticateMessage.cbBuffer)
967 goto fail;
968 if (!winpr_HMAC_Update(hmac, &auth[0], context->MessageIntegrityCheckOffset))
969 goto fail;
970 if (!winpr_HMAC_Update(hmac, data, sizeof(data)))
971 goto fail;
972 if (!winpr_HMAC_Update(hmac, &auth[rest], context->AuthenticateMessage.cbBuffer - rest))
973 goto fail;
974 }
975 else
976 {
977 if (!winpr_HMAC_Update(hmac, (BYTE*)context->AuthenticateMessage.pvBuffer,
978 context->AuthenticateMessage.cbBuffer))
979 goto fail;
980 }
981 rc = winpr_HMAC_Final(hmac, mic, WINPR_MD5_DIGEST_LENGTH);
982
983fail:
984 winpr_HMAC_Free(hmac);
985 return rc;
986}