FreeRDP
Loading...
Searching...
No Matches
negotiate.c
1
21#include <winpr/config.h>
22
23#include <winpr/crt.h>
24#include <winpr/wtypes.h>
25#include <winpr/assert.h>
26#include <winpr/sspi.h>
27#include <winpr/tchar.h>
28#include <winpr/registry.h>
29#include <winpr/build-config.h>
30#include <winpr/asn1.h>
31
32#include "negotiate.h"
33
34#include "../NTLM/ntlm.h"
35#include "../NTLM/ntlm_export.h"
36#include "../Kerberos/kerberos.h"
37#include "../sspi.h"
38#include "../../utils.h"
39#include "../../log.h"
40#define TAG WINPR_TAG("negotiate")
41
42#define NEGO_REG_KEY "Software\\%s\\SSPI\\Negotiate"
43
44static const char PACKAGE_NAME_DISABLE_ALL[] = "none";
45static const char PACKAGE_NAME_NTLM[] = "ntlm";
46static const char PACKAGE_NAME_KERBEROS[] = "kerberos";
47static const char PACKAGE_NAME_KERBEROS_U2U[] = "u2u";
48
49typedef struct
50{
51 const TCHAR* name;
52 const SecurityFunctionTableA* table;
53 const SecurityFunctionTableW* table_w;
54} SecPkg;
55
56struct Mech_st
57{
58 const WinPrAsn1_OID* oid;
59 const SecPkg* pkg;
60 const UINT flags;
61 const BOOL preferred;
62};
63
64typedef struct
65{
66 const Mech* mech;
67 CredHandle cred;
68 BOOL valid;
69} MechCred;
70
71const SecPkgInfoA NEGOTIATE_SecPkgInfoA = {
72 0x00083BB3, /* fCapabilities */
73 1, /* wVersion */
74 0x0009, /* wRPCID */
75 0x00002FE0, /* cbMaxToken */
76 "Negotiate", /* Name */
77 "Microsoft Package Negotiator" /* Comment */
78};
79
80static WCHAR NEGOTIATE_SecPkgInfoW_NameBuffer[32] = { 0 };
81static WCHAR NEGOTIATE_SecPkgInfoW_CommentBuffer[32] = { 0 };
82
83const SecPkgInfoW NEGOTIATE_SecPkgInfoW = {
84 0x00083BB3, /* fCapabilities */
85 1, /* wVersion */
86 0x0009, /* wRPCID */
87 0x00002FE0, /* cbMaxToken */
88 NEGOTIATE_SecPkgInfoW_NameBuffer, /* Name */
89 NEGOTIATE_SecPkgInfoW_CommentBuffer /* Comment */
90};
91
92static const WinPrAsn1_OID spnego_OID = { 6, (BYTE*)"\x2b\x06\x01\x05\x05\x02" };
93static const WinPrAsn1_OID kerberos_u2u_OID = { 10,
94 (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" };
95static const WinPrAsn1_OID kerberos_OID = { 9, (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
96static const WinPrAsn1_OID kerberos_wrong_OID = { 9,
97 (BYTE*)"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02" };
98static const WinPrAsn1_OID ntlm_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" };
99
100static const WinPrAsn1_OID negoex_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e" };
101
102#ifdef WITH_KRB5
103static const SecPkg SecPkgTable[] = {
104 { KERBEROS_SSP_NAME, &KERBEROS_SecurityFunctionTableA, &KERBEROS_SecurityFunctionTableW },
105 { KERBEROS_SSP_NAME, &KERBEROS_SecurityFunctionTableA, &KERBEROS_SecurityFunctionTableW },
106 { NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA, &NTLM_SecurityFunctionTableW }
107};
108
109static const Mech MechTable[] = {
110 { &kerberos_u2u_OID, &SecPkgTable[0], ISC_REQ_INTEGRITY | ISC_REQ_USE_SESSION_KEY, TRUE },
111 { &kerberos_OID, &SecPkgTable[1], ISC_REQ_INTEGRITY, TRUE },
112 { &ntlm_OID, &SecPkgTable[2], 0, FALSE },
113};
114#else
115static const SecPkg SecPkgTable[] = { { NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA,
116 &NTLM_SecurityFunctionTableW } };
117
118static const Mech MechTable[] = {
119 { &ntlm_OID, &SecPkgTable[0], 0, FALSE },
120};
121#endif
122
123static const size_t MECH_COUNT = sizeof(MechTable) / sizeof(Mech);
124
125enum NegState
126{
127 NOSTATE = -1,
128 ACCEPT_COMPLETED,
129 ACCEPT_INCOMPLETE,
130 REJECT,
131 REQUEST_MIC
132};
133
134typedef struct
135{
136 enum NegState negState;
137 BOOL init;
138 WinPrAsn1_OID supportedMech;
139 SecBuffer mechTypes;
140 SecBuffer mechToken;
141 SecBuffer mic;
142} NegToken;
143
144static const NegToken empty_neg_token = { NOSTATE, FALSE, { 0, NULL },
145 { 0, 0, NULL }, { 0, 0, NULL }, { 0, 0, NULL } };
146
147static NEGOTIATE_CONTEXT* negotiate_ContextNew(NEGOTIATE_CONTEXT* init_context)
148{
149 NEGOTIATE_CONTEXT* context = NULL;
150
151 WINPR_ASSERT(init_context);
152
153 context = calloc(1, sizeof(NEGOTIATE_CONTEXT));
154 if (!context)
155 return NULL;
156
157 if (init_context->spnego)
158 {
159 init_context->mechTypes.pvBuffer = malloc(init_context->mechTypes.cbBuffer);
160 if (!init_context->mechTypes.pvBuffer)
161 {
162 free(context);
163 return NULL;
164 }
165 }
166
167 *context = *init_context;
168
169 return context;
170}
171
172static void negotiate_ContextFree(NEGOTIATE_CONTEXT* context)
173{
174 WINPR_ASSERT(context);
175
176 if (context->mechTypes.pvBuffer)
177 free(context->mechTypes.pvBuffer);
178 free(context);
179}
180
181static const char* negotiate_mech_name(const WinPrAsn1_OID* oid)
182{
183 if (sspi_gss_oid_compare(oid, &spnego_OID))
184 return "SPNEGO (1.3.6.1.5.5.2)";
185 else if (sspi_gss_oid_compare(oid, &kerberos_u2u_OID))
186 return "Kerberos user to user (1.2.840.113554.1.2.2.3)";
187 else if (sspi_gss_oid_compare(oid, &kerberos_OID))
188 return "Kerberos (1.2.840.113554.1.2.2)";
189 else if (sspi_gss_oid_compare(oid, &kerberos_wrong_OID))
190 return "Kerberos [wrong OID] (1.2.840.48018.1.2.2)";
191 else if (sspi_gss_oid_compare(oid, &ntlm_OID))
192 return "NTLM (1.3.6.1.4.1.311.2.2.10)";
193 else if (sspi_gss_oid_compare(oid, &negoex_OID))
194 return "NegoEx (1.3.6.1.4.1.311.2.2.30)";
195 else
196 return "Unknown mechanism";
197}
198
199static const Mech* negotiate_GetMechByOID(const WinPrAsn1_OID* oid)
200{
201 WINPR_ASSERT(oid);
202
203 WinPrAsn1_OID testOid = *oid;
204
205 if (sspi_gss_oid_compare(&testOid, &kerberos_wrong_OID))
206 {
207 testOid.len = kerberos_OID.len;
208 testOid.data = kerberos_OID.data;
209 }
210
211 for (size_t i = 0; i < MECH_COUNT; i++)
212 {
213 if (sspi_gss_oid_compare(&testOid, MechTable[i].oid))
214 return &MechTable[i];
215 }
216 return NULL;
217}
218
219static PSecHandle negotiate_FindCredential(MechCred* creds, const Mech* mech)
220{
221 WINPR_ASSERT(creds);
222
223 if (!mech)
224 return NULL;
225
226 for (size_t i = 0; i < MECH_COUNT; i++)
227 {
228 MechCred* cred = &creds[i];
229
230 if (cred->mech == mech)
231 {
232 if (cred->valid)
233 return &cred->cred;
234 return NULL;
235 }
236 }
237
238 return NULL;
239}
240
241static BOOL negotiate_get_dword(HKEY hKey, const char* subkey, DWORD* pdwValue)
242{
243 DWORD dwValue = 0;
244 DWORD dwType = 0;
245 DWORD dwSize = sizeof(dwValue);
246 LONG rc = RegQueryValueExA(hKey, subkey, NULL, &dwType, (BYTE*)&dwValue, &dwSize);
247
248 if (rc != ERROR_SUCCESS)
249 return FALSE;
250 if (dwType != REG_DWORD)
251 return FALSE;
252
253 *pdwValue = dwValue;
254 return TRUE;
255}
256
257static BOOL negotiate_get_config_from_auth_package_list(void* pAuthData, BOOL* kerberos, BOOL* ntlm,
258 BOOL* u2u)
259{
260 BOOL rc = FALSE;
261 char* tok_ctx = NULL;
262 char* PackageList = NULL;
263
264 if (!sspi_CopyAuthPackageListA((const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData, &PackageList))
265 return FALSE;
266
267 char* tok_ptr = strtok_s(PackageList, ",", &tok_ctx);
268
269 while (tok_ptr)
270 {
271 const char* PackageName = tok_ptr;
272 BOOL PackageInclude = TRUE;
273
274 if (PackageName[0] == '!')
275 {
276 PackageName = &PackageName[1];
277 PackageInclude = FALSE;
278 }
279
280 if (_stricmp(PackageName, PACKAGE_NAME_NTLM) == 0)
281 {
282 *ntlm = PackageInclude;
283 }
284 else if (_stricmp(PackageName, PACKAGE_NAME_KERBEROS) == 0)
285 {
286 *kerberos = PackageInclude;
287 }
288 else if (_stricmp(PackageName, PACKAGE_NAME_KERBEROS_U2U) == 0)
289 {
290 *u2u = PackageInclude;
291 }
292 else if (_stricmp(PackageName, PACKAGE_NAME_DISABLE_ALL) == 0)
293 {
294 *kerberos = FALSE;
295 *ntlm = FALSE;
296 *u2u = FALSE;
297
298 if (PackageName != PackageList)
299 {
300 WLog_WARN(TAG, "Special keyword '%s' not first in list, aborting", PackageName);
301 goto fail;
302 }
303 }
304 else
305 {
306 WLog_WARN(TAG, "Unknown authentication package name: %s, ignoring", PackageName);
307 }
308
309 tok_ptr = strtok_s(NULL, ",", &tok_ctx);
310 }
311
312 rc = TRUE;
313fail:
314 free(PackageList);
315 return rc;
316}
317
318static BOOL negotiate_get_config(void* pAuthData, BOOL* kerberos, BOOL* ntlm, BOOL* u2u)
319{
320 HKEY hKey = NULL;
321
322 WINPR_ASSERT(kerberos);
323 WINPR_ASSERT(ntlm);
324 WINPR_ASSERT(u2u);
325
326#if !defined(WITH_KRB5_NO_NTLM_FALLBACK)
327 *ntlm = TRUE;
328#else
329 *ntlm = FALSE;
330#endif
331#if defined(WITH_KRB5)
332 *kerberos = TRUE;
333 *u2u = TRUE;
334#else
335 *kerberos = FALSE;
336 *u2u = FALSE;
337#endif
338
339 if (negotiate_get_config_from_auth_package_list(pAuthData, kerberos, ntlm, u2u))
340 {
341 return TRUE; // use explicit authentication package list
342 }
343
344 {
345 char* key = winpr_getApplicatonDetailsRegKey(NEGO_REG_KEY);
346 if (key)
347 {
348 const LONG rc =
349 RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
350 free(key);
351 if (rc == ERROR_SUCCESS)
352 {
353 DWORD dwValue = 0;
354
355 if (negotiate_get_dword(hKey, PACKAGE_NAME_KERBEROS, &dwValue))
356 *kerberos = (dwValue != 0) ? TRUE : FALSE;
357
358 if (negotiate_get_dword(hKey, PACKAGE_NAME_KERBEROS_U2U, &dwValue))
359 *u2u = (dwValue != 0) ? TRUE : FALSE;
360
361#if !defined(WITH_KRB5_NO_NTLM_FALLBACK)
362 if (negotiate_get_dword(hKey, PACKAGE_NAME_NTLM, &dwValue))
363 *ntlm = (dwValue != 0) ? TRUE : FALSE;
364#endif
365
366 RegCloseKey(hKey);
367 }
368 }
369 }
370
371 return TRUE;
372}
373
374static BOOL negotiate_write_neg_token(PSecBuffer output_buffer, NegToken* token)
375{
376 WINPR_ASSERT(output_buffer);
377 WINPR_ASSERT(token);
378
379 BOOL ret = FALSE;
380 WinPrAsn1Encoder* enc = NULL;
381 WinPrAsn1_MemoryChunk mechTypes = { token->mechTypes.cbBuffer, token->mechTypes.pvBuffer };
382 WinPrAsn1_OctetString mechToken = { token->mechToken.cbBuffer, token->mechToken.pvBuffer };
383 WinPrAsn1_OctetString mechListMic = { token->mic.cbBuffer, token->mic.pvBuffer };
384 wStream s;
385 size_t len = 0;
386
387 enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
388 if (!enc)
389 return FALSE;
390
391 /* For NegTokenInit wrap in an initialContextToken */
392 if (token->init)
393 {
394 /* InitialContextToken [APPLICATION 0] IMPLICIT SEQUENCE */
395 if (!WinPrAsn1EncAppContainer(enc, 0))
396 goto cleanup;
397
398 /* thisMech MechType OID */
399 if (!WinPrAsn1EncOID(enc, &spnego_OID))
400 goto cleanup;
401 }
402
403 /* innerContextToken [0] NegTokenInit or [1] NegTokenResp */
404 if (!WinPrAsn1EncContextualSeqContainer(enc, token->init ? 0 : 1))
405 goto cleanup;
406
407 WLog_DBG(TAG, token->init ? "Writing negTokenInit..." : "Writing negTokenResp...");
408
409 /* mechTypes [0] MechTypeList (mechTypes already contains the SEQUENCE tag) */
410 if (token->init)
411 {
412 if (!WinPrAsn1EncContextualRawContent(enc, 0, &mechTypes))
413 goto cleanup;
414 WLog_DBG(TAG, "\tmechTypes [0] (%" PRIu32 " bytes)", token->mechTypes.cbBuffer);
415 }
416 /* negState [0] ENUMERATED */
417 else if (token->negState != NOSTATE)
418 {
419 if (!WinPrAsn1EncContextualEnumerated(enc, 0, token->negState))
420 goto cleanup;
421 WLog_DBG(TAG, "\tnegState [0] (%d)", token->negState);
422 }
423
424 /* supportedMech [1] OID */
425 if (token->supportedMech.len)
426 {
427 if (!WinPrAsn1EncContextualOID(enc, 1, &token->supportedMech))
428 goto cleanup;
429 WLog_DBG(TAG, "\tsupportedMech [1] (%s)", negotiate_mech_name(&token->supportedMech));
430 }
431
432 /* mechToken [2] OCTET STRING */
433 if (token->mechToken.cbBuffer)
434 {
435 if (WinPrAsn1EncContextualOctetString(enc, 2, &mechToken) == 0)
436 goto cleanup;
437 WLog_DBG(TAG, "\tmechToken [2] (%" PRIu32 " bytes)", token->mechToken.cbBuffer);
438 }
439
440 /* mechListMIC [3] OCTET STRING */
441 if (token->mic.cbBuffer)
442 {
443 if (WinPrAsn1EncContextualOctetString(enc, 3, &mechListMic) == 0)
444 goto cleanup;
445 WLog_DBG(TAG, "\tmechListMIC [3] (%" PRIu32 " bytes)", token->mic.cbBuffer);
446 }
447
448 /* NegTokenInit or NegTokenResp */
449 if (!WinPrAsn1EncEndContainer(enc))
450 goto cleanup;
451
452 if (token->init)
453 {
454 /* initialContextToken */
455 if (!WinPrAsn1EncEndContainer(enc))
456 goto cleanup;
457 }
458
459 if (!WinPrAsn1EncStreamSize(enc, &len) || len > output_buffer->cbBuffer)
460 goto cleanup;
461
462 if (len > UINT32_MAX)
463 goto cleanup;
464
465 Stream_StaticInit(&s, output_buffer->pvBuffer, len);
466
467 if (WinPrAsn1EncToStream(enc, &s))
468 {
469 output_buffer->cbBuffer = (UINT32)len;
470 ret = TRUE;
471 }
472
473cleanup:
474 WinPrAsn1Encoder_Free(&enc);
475 return ret;
476}
477
478static BOOL negotiate_read_neg_token(PSecBuffer input, NegToken* token)
479{
480 WinPrAsn1Decoder dec = { .encoding = WINPR_ASN1_BER, { 0 } };
481 WinPrAsn1Decoder dec2 = { .encoding = WINPR_ASN1_BER, { 0 } };
482 WinPrAsn1_OID oid = { 0 };
483 WinPrAsn1_tagId contextual = 0;
484 WinPrAsn1_tag tag = 0;
485 size_t len = 0;
486 WinPrAsn1_OctetString octet_string = { 0 };
487 BOOL err = 0;
488
489 WINPR_ASSERT(input);
490 WINPR_ASSERT(token);
491
492 WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, input->pvBuffer, input->cbBuffer);
493
494 if (!WinPrAsn1DecPeekTag(&dec, &tag))
495 return FALSE;
496
497 if (tag == 0x60)
498 {
499 /* initialContextToken [APPLICATION 0] */
500 if (!WinPrAsn1DecReadApp(&dec, &tag, &dec2) || tag != 0)
501 return FALSE;
502 dec = dec2;
503
504 /* thisMech OID */
505 if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
506 return FALSE;
507
508 if (!sspi_gss_oid_compare(&spnego_OID, &oid))
509 return FALSE;
510
511 /* [0] NegTokenInit */
512 if (!WinPrAsn1DecReadContextualSequence(&dec, 0, &err, &dec2))
513 return FALSE;
514
515 token->init = TRUE;
516 }
517 /* [1] NegTokenResp */
518 else if (!WinPrAsn1DecReadContextualSequence(&dec, 1, &err, &dec2))
519 return FALSE;
520 dec = dec2;
521
522 WLog_DBG(TAG, token->init ? "Reading negTokenInit..." : "Reading negTokenResp...");
523
524 /* Read NegTokenResp sequence members */
525 do
526 {
527 if (!WinPrAsn1DecReadContextualTag(&dec, &contextual, &dec2))
528 return FALSE;
529
530 switch (contextual)
531 {
532 case 0:
533 if (token->init)
534 {
535 /* mechTypes [0] MechTypeList */
536 wStream s = WinPrAsn1DecGetStream(&dec2);
537 token->mechTypes.BufferType = SECBUFFER_TOKEN;
538 const size_t mlen = Stream_Length(&s);
539 if (mlen > UINT32_MAX)
540 return FALSE;
541 token->mechTypes.cbBuffer = (UINT32)mlen;
542 token->mechTypes.pvBuffer = Stream_Buffer(&s);
543 WLog_DBG(TAG, "\tmechTypes [0] (%" PRIu32 " bytes)", token->mechTypes.cbBuffer);
544 }
545 else
546 {
547 /* negState [0] ENUMERATED */
548 WinPrAsn1_ENUMERATED rd = 0;
549 if (!WinPrAsn1DecReadEnumerated(&dec2, &rd))
550 return FALSE;
551 switch (rd)
552 {
553 case NOSTATE:
554 case ACCEPT_COMPLETED:
555 case ACCEPT_INCOMPLETE:
556 case REJECT:
557 case REQUEST_MIC:
558 break;
559 default:
560 WLog_ERR(TAG, "Invalid negState enumeration value %d", rd);
561 return FALSE;
562 }
563
564 token->negState = WINPR_ASSERTING_INT_CAST(enum NegState, rd);
565 WLog_DBG(TAG, "\tnegState [0] (%d)", rd);
566 }
567 break;
568 case 1:
569 if (token->init)
570 {
571 /* reqFlags [1] ContextFlags BIT STRING (ignored) */
572 if (!WinPrAsn1DecPeekTagAndLen(&dec2, &tag, &len) || (tag != ER_TAG_BIT_STRING))
573 return FALSE;
574 WLog_DBG(TAG, "\treqFlags [1] (%" PRIuz " bytes)", len);
575 }
576 else
577 {
578 /* supportedMech [1] MechType */
579 if (!WinPrAsn1DecReadOID(&dec2, &token->supportedMech, FALSE))
580 return FALSE;
581 WLog_DBG(TAG, "\tsupportedMech [1] (%s)",
582 negotiate_mech_name(&token->supportedMech));
583 }
584 break;
585 case 2:
586 /* mechToken [2] OCTET STRING */
587 if (!WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE))
588 return FALSE;
589 if (octet_string.len > UINT32_MAX)
590 return FALSE;
591 token->mechToken.cbBuffer = (UINT32)octet_string.len;
592 token->mechToken.pvBuffer = octet_string.data;
593 token->mechToken.BufferType = SECBUFFER_TOKEN;
594 WLog_DBG(TAG, "\tmechToken [2] (%" PRIuz " bytes)", octet_string.len);
595 break;
596 case 3:
597 /* mechListMic [3] OCTET STRING */
598 if (!WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE))
599 return FALSE;
600 if (octet_string.len > UINT32_MAX)
601 return FALSE;
602 token->mic.cbBuffer = (UINT32)octet_string.len;
603 token->mic.pvBuffer = octet_string.data;
604 token->mic.BufferType = SECBUFFER_TOKEN;
605 WLog_DBG(TAG, "\tmechListMIC [3] (%" PRIuz " bytes)", octet_string.len);
606 break;
607 default:
608 WLog_ERR(TAG, "unknown contextual item %d", contextual);
609 return FALSE;
610 }
611 } while (WinPrAsn1DecPeekTag(&dec, &tag));
612
613 return TRUE;
614}
615
616static SECURITY_STATUS negotiate_mic_exchange(NEGOTIATE_CONTEXT* context, NegToken* input_token,
617 NegToken* output_token, PSecBuffer output_buffer)
618{
619 SecBuffer mic_buffers[2] = { 0 };
620 SecBufferDesc mic_buffer_desc = { SECBUFFER_VERSION, 2, mic_buffers };
621 SECURITY_STATUS status = 0;
622
623 WINPR_ASSERT(context);
624 WINPR_ASSERT(input_token);
625 WINPR_ASSERT(output_token);
626 WINPR_ASSERT(context->mech);
627 WINPR_ASSERT(context->mech->pkg);
628
629 const SecurityFunctionTableA* table = context->mech->pkg->table;
630 WINPR_ASSERT(table);
631
632 mic_buffers[0] = context->mechTypes;
633
634 /* Verify MIC if we received one */
635 if (input_token->mic.cbBuffer > 0)
636 {
637 mic_buffers[1] = input_token->mic;
638
639 status = table->VerifySignature(&context->sub_context, &mic_buffer_desc, 0, 0);
640 if (status != SEC_E_OK)
641 return status;
642
643 output_token->negState = ACCEPT_COMPLETED;
644 }
645
646 /* If peer expects a MIC then generate it */
647 if (input_token->negState != ACCEPT_COMPLETED)
648 {
649 /* Store the mic token after the mech token in the output buffer */
650 output_token->mic.BufferType = SECBUFFER_TOKEN;
651 if (output_buffer)
652 {
653 output_token->mic.cbBuffer = output_buffer->cbBuffer - output_token->mechToken.cbBuffer;
654 output_token->mic.pvBuffer =
655 (BYTE*)output_buffer->pvBuffer + output_token->mechToken.cbBuffer;
656 }
657 mic_buffers[1] = output_token->mic;
658
659 status = table->MakeSignature(&context->sub_context, 0, &mic_buffer_desc, 0);
660 if (status != SEC_E_OK)
661 return status;
662
663 output_token->mic = mic_buffers[1];
664 }
665
666 /* When using NTLM cipher states need to be reset after mic exchange */
667 const TCHAR* name = sspi_SecureHandleGetUpperPointer(&context->sub_context);
668 if (!name)
669 return SEC_E_INTERNAL_ERROR;
670
671 if (_tcsncmp(name, NTLM_SSP_NAME, ARRAYSIZE(NTLM_SSP_NAME)) == 0)
672 {
673 if (!ntlm_reset_cipher_state(&context->sub_context))
674 return SEC_E_INTERNAL_ERROR;
675 }
676
677 return SEC_E_OK;
678}
679
680static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextW(
681 PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
682 ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
683 PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
684{
685 NEGOTIATE_CONTEXT* context = NULL;
686 NEGOTIATE_CONTEXT init_context = NEGOTIATE_CONTEXT_init();
687 MechCred* creds = NULL;
688 PCtxtHandle sub_context = NULL;
689 PCredHandle sub_cred = NULL;
690 NegToken input_token = empty_neg_token;
691 NegToken output_token = empty_neg_token;
692 PSecBuffer input_buffer = NULL;
693 PSecBuffer output_buffer = NULL;
694 PSecBuffer bindings_buffer = NULL;
695 SecBuffer mech_input_buffers[2] = { 0 };
696 SecBufferDesc mech_input = { SECBUFFER_VERSION, 2, mech_input_buffers };
697 SecBufferDesc mech_output = { SECBUFFER_VERSION, 1, &output_token.mechToken };
698 SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
699 SECURITY_STATUS sub_status = SEC_E_INTERNAL_ERROR;
700 WinPrAsn1Encoder* enc = NULL;
701 wStream s;
702 const Mech* mech = NULL;
703
704 if (!phCredential || !SecIsValidHandle(phCredential))
705 return SEC_E_NO_CREDENTIALS;
706
707 creds = sspi_SecureHandleGetLowerPointer(phCredential);
708
709 /* behave like windows SSPIs that don't want empty context */
710 if (phContext && !phContext->dwLower && !phContext->dwUpper)
711 return SEC_E_INVALID_HANDLE;
712
713 context = sspi_SecureHandleGetLowerPointer(phContext);
714
715 if (pInput)
716 {
717 input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
718 bindings_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS);
719 }
720 if (pOutput)
721 output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
722
723 if (!context)
724 {
725 enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
726 if (!enc)
727 return SEC_E_INSUFFICIENT_MEMORY;
728
729 if (!WinPrAsn1EncSeqContainer(enc))
730 goto cleanup;
731
732 for (size_t i = 0; i < MECH_COUNT; i++)
733 {
734 MechCred* cred = &creds[i];
735 const SecPkg* pkg = MechTable[i].pkg;
736 WINPR_ASSERT(pkg);
737 WINPR_ASSERT(pkg->table_w);
738
739 if (!cred->valid)
740 {
741 WLog_DBG(TAG, "Unavailable mechanism: %s", negotiate_mech_name(cred->mech->oid));
742 continue;
743 }
744
745 /* Send an optimistic token for the first valid mechanism */
746 if (!init_context.mech)
747 {
748 /* Use the output buffer to store the optimistic token */
749 if (!output_buffer)
750 goto cleanup;
751
752 CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
753
754 if (bindings_buffer)
755 mech_input_buffers[0] = *bindings_buffer;
756
757 WINPR_ASSERT(pkg->table_w->InitializeSecurityContextW);
758 sub_status = pkg->table_w->InitializeSecurityContextW(
759 &cred->cred, NULL, pszTargetName, fContextReq | cred->mech->flags, Reserved1,
760 TargetDataRep, &mech_input, Reserved2, &init_context.sub_context, &mech_output,
761 pfContextAttr, ptsExpiry);
762
763 /* If the mechanism failed we can't use it; skip */
764 if (IsSecurityStatusError(sub_status))
765 {
766 if (SecIsValidHandle(&init_context.sub_context))
767 {
768 WINPR_ASSERT(pkg->table_w->DeleteSecurityContext);
769 pkg->table_w->DeleteSecurityContext(&init_context.sub_context);
770 }
771 cred->valid = FALSE;
772 continue;
773 }
774
775 init_context.mech = cred->mech;
776 }
777
778 if (!WinPrAsn1EncOID(enc, cred->mech->oid))
779 goto cleanup;
780 WLog_DBG(TAG, "Available mechanism: %s", negotiate_mech_name(cred->mech->oid));
781 }
782
783 /* No usable mechanisms were found */
784 if (!init_context.mech)
785 goto cleanup;
786
787 /* If the only available mech is NTLM use it directly otherwise use spnego */
788 if (init_context.mech->oid == &ntlm_OID)
789 {
790 init_context.spnego = FALSE;
791 output_buffer->cbBuffer = output_token.mechToken.cbBuffer;
792 WLog_DBG(TAG, "Using direct NTLM");
793 }
794 else
795 {
796 init_context.spnego = TRUE;
797 init_context.mechTypes.BufferType = SECBUFFER_DATA;
798 const size_t cb = WinPrAsn1EncEndContainer(enc);
799 WINPR_ASSERT(cb <= UINT32_MAX);
800 init_context.mechTypes.cbBuffer = (UINT32)cb;
801 }
802
803 /* Allocate memory for the new context */
804 context = negotiate_ContextNew(&init_context);
805 if (!context)
806 {
807 init_context.mech->pkg->table->DeleteSecurityContext(&init_context.sub_context);
808 WinPrAsn1Encoder_Free(&enc);
809 return SEC_E_INSUFFICIENT_MEMORY;
810 }
811
812 sspi_SecureHandleSetUpperPointer(phNewContext, NEGO_SSP_NAME);
813 sspi_SecureHandleSetLowerPointer(phNewContext, context);
814
815 if (!context->spnego)
816 {
817 status = sub_status;
818 goto cleanup;
819 }
820
821 /* Write mechTypesList */
822 Stream_StaticInit(&s, context->mechTypes.pvBuffer, context->mechTypes.cbBuffer);
823 if (!WinPrAsn1EncToStream(enc, &s))
824 goto cleanup;
825
826 output_token.mechTypes.cbBuffer = context->mechTypes.cbBuffer;
827 output_token.mechTypes.pvBuffer = context->mechTypes.pvBuffer;
828 output_token.init = TRUE;
829
830 if (sub_status == SEC_E_OK)
831 context->state = NEGOTIATE_STATE_FINAL_OPTIMISTIC;
832 }
833 else
834 {
835 if (!input_buffer)
836 return SEC_E_INVALID_TOKEN;
837
838 sub_context = &context->sub_context;
839 sub_cred = negotiate_FindCredential(creds, context->mech);
840
841 if (!context->spnego)
842 {
843 return context->mech->pkg->table_w->InitializeSecurityContextW(
844 sub_cred, sub_context, pszTargetName, fContextReq | context->mech->flags, Reserved1,
845 TargetDataRep, pInput, Reserved2, sub_context, pOutput, pfContextAttr, ptsExpiry);
846 }
847
848 if (!negotiate_read_neg_token(input_buffer, &input_token))
849 return SEC_E_INVALID_TOKEN;
850
851 /* On first response check if the server doesn't like out preferred mech */
852 if (context->state < NEGOTIATE_STATE_NEGORESP && input_token.supportedMech.len &&
853 !sspi_gss_oid_compare(&input_token.supportedMech, context->mech->oid))
854 {
855 mech = negotiate_GetMechByOID(&input_token.supportedMech);
856 if (!mech)
857 return SEC_E_INVALID_TOKEN;
858
859 /* Make sure the specified mech is supported and get the appropriate credential */
860 sub_cred = negotiate_FindCredential(creds, mech);
861 if (!sub_cred)
862 return SEC_E_INVALID_TOKEN;
863
864 /* Clean up the optimistic mech */
865 context->mech->pkg->table_w->DeleteSecurityContext(&context->sub_context);
866 sub_context = NULL;
867
868 context->mech = mech;
869 context->mic = TRUE;
870 }
871
872 /* Check neg_state (required on first response) */
873 if (context->state < NEGOTIATE_STATE_NEGORESP)
874 {
875 switch (input_token.negState)
876 {
877 case NOSTATE:
878 return SEC_E_INVALID_TOKEN;
879 case REJECT:
880 return SEC_E_LOGON_DENIED;
881 case REQUEST_MIC:
882 context->mic = TRUE;
883 /* fallthrough */
884 WINPR_FALLTHROUGH
885 case ACCEPT_INCOMPLETE:
886 context->state = NEGOTIATE_STATE_NEGORESP;
887 break;
888 case ACCEPT_COMPLETED:
889 if (context->state == NEGOTIATE_STATE_INITIAL)
890 context->state = NEGOTIATE_STATE_NEGORESP;
891 else
892 context->state = NEGOTIATE_STATE_FINAL;
893 break;
894 default:
895 break;
896 }
897
898 WLog_DBG(TAG, "Negotiated mechanism: %s", negotiate_mech_name(context->mech->oid));
899 }
900
901 if (context->state == NEGOTIATE_STATE_NEGORESP)
902 {
903 /* Store the mech token in the output buffer */
904 if (!output_buffer)
905 goto cleanup;
906 CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
907
908 mech_input_buffers[0] = input_token.mechToken;
909 if (bindings_buffer)
910 mech_input_buffers[1] = *bindings_buffer;
911
912 status = context->mech->pkg->table_w->InitializeSecurityContextW(
913 sub_cred, sub_context, pszTargetName, fContextReq | context->mech->flags, Reserved1,
914 TargetDataRep, input_token.mechToken.cbBuffer ? &mech_input : NULL, Reserved2,
915 &context->sub_context, &mech_output, pfContextAttr, ptsExpiry);
916
917 if (IsSecurityStatusError(status))
918 return status;
919 }
920
921 if (status == SEC_E_OK)
922 {
923 if (output_token.mechToken.cbBuffer > 0)
924 context->state = NEGOTIATE_STATE_MIC;
925 else
926 context->state = NEGOTIATE_STATE_FINAL;
927 }
928
929 /* Check if the acceptor sent its final token without a mic */
930 if (context->state == NEGOTIATE_STATE_FINAL && input_token.mic.cbBuffer == 0)
931 {
932 if (context->mic || input_token.negState != ACCEPT_COMPLETED)
933 return SEC_E_INVALID_TOKEN;
934
935 if (output_buffer)
936 output_buffer->cbBuffer = 0;
937 return SEC_E_OK;
938 }
939
940 if ((context->state == NEGOTIATE_STATE_MIC && context->mic) ||
941 context->state == NEGOTIATE_STATE_FINAL)
942 {
943 status = negotiate_mic_exchange(context, &input_token, &output_token, output_buffer);
944 if (status != SEC_E_OK)
945 return status;
946 }
947 }
948
949 if (input_token.negState == ACCEPT_COMPLETED)
950 {
951 if (output_buffer)
952 output_buffer->cbBuffer = 0;
953 return SEC_E_OK;
954 }
955
956 if (output_token.negState == ACCEPT_COMPLETED)
957 status = SEC_E_OK;
958 else
959 status = SEC_I_CONTINUE_NEEDED;
960
961 if (!negotiate_write_neg_token(output_buffer, &output_token))
962 status = SEC_E_INTERNAL_ERROR;
963
964cleanup:
965 WinPrAsn1Encoder_Free(&enc);
966 return status;
967}
968
969static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextA(
970 PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
971 ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
972 PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
973{
974 SECURITY_STATUS status = 0;
975 SEC_WCHAR* pszTargetNameW = NULL;
976
977 if (pszTargetName)
978 {
979 pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, NULL);
980 if (!pszTargetNameW)
981 return SEC_E_INTERNAL_ERROR;
982 }
983
984 status = negotiate_InitializeSecurityContextW(
985 phCredential, phContext, pszTargetNameW, fContextReq, Reserved1, TargetDataRep, pInput,
986 Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
987 free(pszTargetNameW);
988 return status;
989}
990
991static const Mech* guessMech(PSecBuffer input_buffer, BOOL* spNego, WinPrAsn1_OID* oid)
992{
993 WinPrAsn1Decoder decoder = { .encoding = WINPR_ASN1_BER, { 0 } };
994 WinPrAsn1Decoder appDecoder = { .encoding = WINPR_ASN1_BER, { 0 } };
995 WinPrAsn1_tagId tag = 0;
996 const char ssp[] = "NTLMSSP";
997
998 *spNego = FALSE;
999
1000 /* Check for NTLM token */
1001 if (input_buffer->cbBuffer >= 8 && strncmp(input_buffer->pvBuffer, ssp, sizeof(ssp)) == 0)
1002 {
1003 *oid = ntlm_OID;
1004 return negotiate_GetMechByOID(&ntlm_OID);
1005 }
1006
1007 /* Read initialContextToken or raw Kerberos token */
1008 WinPrAsn1Decoder_InitMem(&decoder, WINPR_ASN1_DER, input_buffer->pvBuffer,
1009 input_buffer->cbBuffer);
1010
1011 if (!WinPrAsn1DecReadApp(&decoder, &tag, &appDecoder) || tag != 0)
1012 return NULL;
1013
1014 if (!WinPrAsn1DecReadOID(&appDecoder, oid, FALSE))
1015 return NULL;
1016
1017 if (sspi_gss_oid_compare(oid, &spnego_OID))
1018 {
1019 *spNego = TRUE;
1020 return NULL;
1021 }
1022
1023 return negotiate_GetMechByOID(oid);
1024}
1025
1026static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
1027 PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq,
1028 ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr,
1029 PTimeStamp ptsTimeStamp)
1030{
1031 NEGOTIATE_CONTEXT* context = NULL;
1032 NEGOTIATE_CONTEXT init_context = NEGOTIATE_CONTEXT_init();
1033 MechCred* creds = NULL;
1034 PCredHandle sub_cred = NULL;
1035 NegToken input_token = empty_neg_token;
1036 NegToken output_token = empty_neg_token;
1037 PSecBuffer input_buffer = NULL;
1038 PSecBuffer output_buffer = NULL;
1039 SecBufferDesc mech_input = { SECBUFFER_VERSION, 1, &input_token.mechToken };
1040 SecBufferDesc mech_output = { SECBUFFER_VERSION, 1, &output_token.mechToken };
1041 SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
1042 WinPrAsn1Decoder dec = { .encoding = WINPR_ASN1_BER, { 0 } };
1043 WinPrAsn1Decoder dec2 = { .encoding = WINPR_ASN1_BER, { 0 } };
1044 WinPrAsn1_tagId tag = 0;
1045 WinPrAsn1_OID oid = { 0 };
1046 const Mech* first_mech = NULL;
1047
1048 if (!phCredential || !SecIsValidHandle(phCredential))
1049 return SEC_E_NO_CREDENTIALS;
1050
1051 creds = sspi_SecureHandleGetLowerPointer(phCredential);
1052
1053 if (!pInput)
1054 return SEC_E_INVALID_TOKEN;
1055
1056 /* behave like windows SSPIs that don't want empty context */
1057 if (phContext && !phContext->dwLower && !phContext->dwUpper)
1058 return SEC_E_INVALID_HANDLE;
1059
1060 context = sspi_SecureHandleGetLowerPointer(phContext);
1061
1062 input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
1063 if (pOutput)
1064 output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
1065
1066 if (!context)
1067 {
1068 init_context.mech = guessMech(input_buffer, &init_context.spnego, &oid);
1069 if (!init_context.mech && !init_context.spnego)
1070 return SEC_E_INVALID_TOKEN;
1071
1072 WLog_DBG(TAG, "Mechanism: %s", negotiate_mech_name(&oid));
1073
1074 if (init_context.spnego)
1075 {
1076 /* Process spnego token */
1077 if (!negotiate_read_neg_token(input_buffer, &input_token))
1078 return SEC_E_INVALID_TOKEN;
1079
1080 /* First token must be negoTokenInit and must contain a mechList */
1081 if (!input_token.init || input_token.mechTypes.cbBuffer == 0)
1082 return SEC_E_INVALID_TOKEN;
1083
1084 init_context.mechTypes.BufferType = SECBUFFER_DATA;
1085 init_context.mechTypes.cbBuffer = input_token.mechTypes.cbBuffer;
1086
1087 /* Prepare to read mechList */
1088 WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, input_token.mechTypes.pvBuffer,
1089 input_token.mechTypes.cbBuffer);
1090
1091 if (!WinPrAsn1DecReadSequence(&dec, &dec2))
1092 return SEC_E_INVALID_TOKEN;
1093 dec = dec2;
1094
1095 /* If an optimistic token was provided pass it into the first mech */
1096 if (input_token.mechToken.cbBuffer)
1097 {
1098 if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
1099 return SEC_E_INVALID_TOKEN;
1100
1101 init_context.mech = negotiate_GetMechByOID(&oid);
1102
1103 if (init_context.mech)
1104 {
1105 if (output_buffer)
1106 output_token.mechToken = *output_buffer;
1107 WLog_DBG(TAG, "Requested mechanism: %s",
1108 negotiate_mech_name(init_context.mech->oid));
1109 }
1110 }
1111 }
1112
1113 if (init_context.mech)
1114 {
1115 sub_cred = negotiate_FindCredential(creds, init_context.mech);
1116
1117 status = init_context.mech->pkg->table->AcceptSecurityContext(
1118 sub_cred, NULL, init_context.spnego ? &mech_input : pInput, fContextReq,
1119 TargetDataRep, &init_context.sub_context,
1120 init_context.spnego ? &mech_output : pOutput, pfContextAttr, ptsTimeStamp);
1121 }
1122
1123 if (IsSecurityStatusError(status))
1124 {
1125 if (!init_context.spnego)
1126 return status;
1127
1128 init_context.mic = TRUE;
1129 first_mech = init_context.mech;
1130 init_context.mech = NULL;
1131 output_token.mechToken.cbBuffer = 0;
1132 }
1133
1134 while (!init_context.mech && WinPrAsn1DecPeekTag(&dec, &tag))
1135 {
1136 /* Read each mechanism */
1137 if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
1138 return SEC_E_INVALID_TOKEN;
1139
1140 init_context.mech = negotiate_GetMechByOID(&oid);
1141 WLog_DBG(TAG, "Requested mechanism: %s", negotiate_mech_name(&oid));
1142
1143 /* Microsoft may send two versions of the kerberos OID */
1144 if (init_context.mech == first_mech)
1145 init_context.mech = NULL;
1146
1147 if (init_context.mech && !negotiate_FindCredential(creds, init_context.mech))
1148 init_context.mech = NULL;
1149 }
1150
1151 if (!init_context.mech)
1152 return SEC_E_INTERNAL_ERROR;
1153
1154 context = negotiate_ContextNew(&init_context);
1155 if (!context)
1156 {
1157 if (!IsSecurityStatusError(status))
1158 init_context.mech->pkg->table->DeleteSecurityContext(&init_context.sub_context);
1159 return SEC_E_INSUFFICIENT_MEMORY;
1160 }
1161
1162 sspi_SecureHandleSetUpperPointer(phNewContext, NEGO_SSP_NAME);
1163 sspi_SecureHandleSetLowerPointer(phNewContext, context);
1164
1165 if (!init_context.spnego)
1166 return status;
1167
1168 CopyMemory(init_context.mechTypes.pvBuffer, input_token.mechTypes.pvBuffer,
1169 input_token.mechTypes.cbBuffer);
1170
1171 if (!context->mech->preferred)
1172 {
1173 output_token.negState = REQUEST_MIC;
1174 context->mic = TRUE;
1175 }
1176 else
1177 {
1178 output_token.negState = ACCEPT_INCOMPLETE;
1179 }
1180
1181 if (status == SEC_E_OK)
1182 context->state = NEGOTIATE_STATE_FINAL;
1183 else
1184 context->state = NEGOTIATE_STATE_NEGORESP;
1185
1186 output_token.supportedMech = oid;
1187 WLog_DBG(TAG, "Accepted mechanism: %s", negotiate_mech_name(&output_token.supportedMech));
1188 }
1189 else
1190 {
1191 sub_cred = negotiate_FindCredential(creds, context->mech);
1192 if (!sub_cred)
1193 return SEC_E_NO_CREDENTIALS;
1194
1195 if (!context->spnego)
1196 {
1197 return context->mech->pkg->table->AcceptSecurityContext(
1198 sub_cred, &context->sub_context, pInput, fContextReq, TargetDataRep,
1199 &context->sub_context, pOutput, pfContextAttr, ptsTimeStamp);
1200 }
1201
1202 if (!negotiate_read_neg_token(input_buffer, &input_token))
1203 return SEC_E_INVALID_TOKEN;
1204
1205 /* Process the mechanism token */
1206 if (input_token.mechToken.cbBuffer > 0)
1207 {
1208 if (context->state != NEGOTIATE_STATE_NEGORESP)
1209 return SEC_E_INVALID_TOKEN;
1210
1211 /* Use the output buffer to store the optimistic token */
1212 if (output_buffer)
1213 CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
1214
1215 status = context->mech->pkg->table->AcceptSecurityContext(
1216 sub_cred, &context->sub_context, &mech_input, fContextReq | context->mech->flags,
1217 TargetDataRep, &context->sub_context, &mech_output, pfContextAttr, ptsTimeStamp);
1218
1219 if (IsSecurityStatusError(status))
1220 return status;
1221
1222 if (status == SEC_E_OK)
1223 context->state = NEGOTIATE_STATE_FINAL;
1224 }
1225 else if (context->state == NEGOTIATE_STATE_NEGORESP)
1226 return SEC_E_INVALID_TOKEN;
1227 }
1228
1229 if (context->state == NEGOTIATE_STATE_FINAL)
1230 {
1231 /* Check if initiator sent the last mech token without a mic and a mic was required */
1232 if (context->mic && output_token.mechToken.cbBuffer == 0 && input_token.mic.cbBuffer == 0)
1233 return SEC_E_INVALID_TOKEN;
1234
1235 if (context->mic || input_token.mic.cbBuffer > 0)
1236 {
1237 status = negotiate_mic_exchange(context, &input_token, &output_token, output_buffer);
1238 if (status != SEC_E_OK)
1239 return status;
1240 }
1241 else
1242 output_token.negState = ACCEPT_COMPLETED;
1243 }
1244
1245 if (input_token.negState == ACCEPT_COMPLETED)
1246 {
1247 if (output_buffer)
1248 output_buffer->cbBuffer = 0;
1249 return SEC_E_OK;
1250 }
1251
1252 if (output_token.negState == ACCEPT_COMPLETED)
1253 status = SEC_E_OK;
1254 else
1255 status = SEC_I_CONTINUE_NEEDED;
1256
1257 if (!negotiate_write_neg_token(output_buffer, &output_token))
1258 return SEC_E_INTERNAL_ERROR;
1259
1260 return status;
1261}
1262
1263static SECURITY_STATUS SEC_ENTRY negotiate_CompleteAuthToken(PCtxtHandle phContext,
1264 PSecBufferDesc pToken)
1265{
1266 NEGOTIATE_CONTEXT* context = NULL;
1267 SECURITY_STATUS status = SEC_E_OK;
1268 context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1269
1270 if (!context)
1271 return SEC_E_INVALID_HANDLE;
1272
1273 WINPR_ASSERT(context->mech);
1274 WINPR_ASSERT(context->mech->pkg);
1275 WINPR_ASSERT(context->mech->pkg->table);
1276 if (context->mech->pkg->table->CompleteAuthToken)
1277 status = context->mech->pkg->table->CompleteAuthToken(&context->sub_context, pToken);
1278
1279 return status;
1280}
1281
1282static SECURITY_STATUS SEC_ENTRY negotiate_DeleteSecurityContext(PCtxtHandle phContext)
1283{
1284 NEGOTIATE_CONTEXT* context = NULL;
1285 SECURITY_STATUS status = SEC_E_OK;
1286 context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1287 const SecPkg* pkg = NULL;
1288
1289 if (!context)
1290 return SEC_E_INVALID_HANDLE;
1291
1292 WINPR_ASSERT(context->mech);
1293 WINPR_ASSERT(context->mech->pkg);
1294 WINPR_ASSERT(context->mech->pkg->table);
1295 pkg = context->mech->pkg;
1296
1297 if (pkg->table->DeleteSecurityContext)
1298 status = pkg->table->DeleteSecurityContext(&context->sub_context);
1299
1300 negotiate_ContextFree(context);
1301 return status;
1302}
1303
1304static SECURITY_STATUS SEC_ENTRY
1305negotiate_ImpersonateSecurityContext(WINPR_ATTR_UNUSED PCtxtHandle phContext)
1306{
1307 return SEC_E_OK;
1308}
1309
1310static SECURITY_STATUS SEC_ENTRY
1311negotiate_RevertSecurityContext(WINPR_ATTR_UNUSED PCtxtHandle phContext)
1312{
1313 return SEC_E_OK;
1314}
1315
1316static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesW(PCtxtHandle phContext,
1317 ULONG ulAttribute, void* pBuffer)
1318{
1319 NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1320
1321 if (!context)
1322 return SEC_E_INVALID_HANDLE;
1323
1324 WINPR_ASSERT(context->mech);
1325 WINPR_ASSERT(context->mech->pkg);
1326 WINPR_ASSERT(context->mech->pkg->table_w);
1327 if (context->mech->pkg->table_w->QueryContextAttributesW)
1328 return context->mech->pkg->table_w->QueryContextAttributesW(&context->sub_context,
1329 ulAttribute, pBuffer);
1330
1331 return SEC_E_UNSUPPORTED_FUNCTION;
1332}
1333
1334static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesA(PCtxtHandle phContext,
1335 ULONG ulAttribute, void* pBuffer)
1336{
1337 NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1338
1339 if (!context)
1340 return SEC_E_INVALID_HANDLE;
1341
1342 WINPR_ASSERT(context->mech);
1343 WINPR_ASSERT(context->mech->pkg);
1344 WINPR_ASSERT(context->mech->pkg->table);
1345 if (context->mech->pkg->table->QueryContextAttributesA)
1346 return context->mech->pkg->table->QueryContextAttributesA(&context->sub_context,
1347 ulAttribute, pBuffer);
1348
1349 return SEC_E_UNSUPPORTED_FUNCTION;
1350}
1351
1352static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesW(PCtxtHandle phContext,
1353 ULONG ulAttribute, void* pBuffer,
1354 ULONG cbBuffer)
1355{
1356 NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1357
1358 if (!context)
1359 return SEC_E_INVALID_HANDLE;
1360
1361 WINPR_ASSERT(context->mech);
1362 WINPR_ASSERT(context->mech->pkg);
1363 WINPR_ASSERT(context->mech->pkg->table_w);
1364 if (context->mech->pkg->table_w->SetContextAttributesW)
1365 return context->mech->pkg->table_w->SetContextAttributesW(&context->sub_context,
1366 ulAttribute, pBuffer, cbBuffer);
1367
1368 return SEC_E_UNSUPPORTED_FUNCTION;
1369}
1370
1371static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesA(PCtxtHandle phContext,
1372 ULONG ulAttribute, void* pBuffer,
1373 ULONG cbBuffer)
1374{
1375 NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1376
1377 if (!context)
1378 return SEC_E_INVALID_HANDLE;
1379
1380 WINPR_ASSERT(context->mech);
1381 WINPR_ASSERT(context->mech->pkg);
1382 WINPR_ASSERT(context->mech->pkg->table);
1383 if (context->mech->pkg->table->SetContextAttributesA)
1384 return context->mech->pkg->table->SetContextAttributesA(&context->sub_context, ulAttribute,
1385 pBuffer, cbBuffer);
1386
1387 return SEC_E_UNSUPPORTED_FUNCTION;
1388}
1389
1390static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesW(PCredHandle phCredential,
1391 ULONG ulAttribute,
1392 void* pBuffer, ULONG cbBuffer)
1393{
1394 MechCred* creds = NULL;
1395 BOOL success = FALSE;
1396 SECURITY_STATUS secStatus = 0;
1397
1398 creds = sspi_SecureHandleGetLowerPointer(phCredential);
1399
1400 if (!creds)
1401 return SEC_E_INVALID_HANDLE;
1402
1403 for (size_t i = 0; i < MECH_COUNT; i++)
1404 {
1405 MechCred* cred = &creds[i];
1406
1407 WINPR_ASSERT(cred->mech);
1408 WINPR_ASSERT(cred->mech->pkg);
1409 WINPR_ASSERT(cred->mech->pkg->table);
1410 WINPR_ASSERT(cred->mech->pkg->table_w->SetCredentialsAttributesW);
1411 secStatus = cred->mech->pkg->table_w->SetCredentialsAttributesW(&cred->cred, ulAttribute,
1412 pBuffer, cbBuffer);
1413
1414 if (secStatus == SEC_E_OK)
1415 {
1416 success = TRUE;
1417 }
1418 }
1419
1420 // return success if at least one submodule accepts the credential attribute
1421 return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION);
1422}
1423
1424static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesA(PCredHandle phCredential,
1425 ULONG ulAttribute,
1426 void* pBuffer, ULONG cbBuffer)
1427{
1428 MechCred* creds = NULL;
1429 BOOL success = FALSE;
1430 SECURITY_STATUS secStatus = 0;
1431
1432 creds = sspi_SecureHandleGetLowerPointer(phCredential);
1433
1434 if (!creds)
1435 return SEC_E_INVALID_HANDLE;
1436
1437 for (size_t i = 0; i < MECH_COUNT; i++)
1438 {
1439 MechCred* cred = &creds[i];
1440
1441 if (!cred->valid)
1442 continue;
1443
1444 WINPR_ASSERT(cred->mech);
1445 WINPR_ASSERT(cred->mech->pkg);
1446 WINPR_ASSERT(cred->mech->pkg->table);
1447 WINPR_ASSERT(cred->mech->pkg->table->SetCredentialsAttributesA);
1448 secStatus = cred->mech->pkg->table->SetCredentialsAttributesA(&cred->cred, ulAttribute,
1449 pBuffer, cbBuffer);
1450
1451 if (secStatus == SEC_E_OK)
1452 {
1453 success = TRUE;
1454 }
1455 }
1456
1457 // return success if at least one submodule accepts the credential attribute
1458 return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION);
1459}
1460
1461static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleW(
1462 SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
1463 void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
1464 PTimeStamp ptsExpiry)
1465{
1466 BOOL kerberos = FALSE;
1467 BOOL ntlm = FALSE;
1468 BOOL u2u = FALSE;
1469
1470 if (!negotiate_get_config(pAuthData, &kerberos, &ntlm, &u2u))
1471 return SEC_E_INTERNAL_ERROR;
1472
1473 MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred));
1474
1475 if (!creds)
1476 return SEC_E_INTERNAL_ERROR;
1477
1478 for (size_t i = 0; i < MECH_COUNT; i++)
1479 {
1480 MechCred* cred = &creds[i];
1481 const SecPkg* pkg = MechTable[i].pkg;
1482 cred->mech = &MechTable[i];
1483
1484 if (!kerberos && sspi_gss_oid_compare(MechTable[i].oid, &kerberos_OID))
1485 continue;
1486 if (!u2u && sspi_gss_oid_compare(MechTable[i].oid, &kerberos_u2u_OID))
1487 continue;
1488 if (!ntlm && _tcsncmp(SecPkgTable[i].name, NTLM_SSP_NAME, ARRAYSIZE(NTLM_SSP_NAME)) == 0)
1489 continue;
1490
1491 WINPR_ASSERT(pkg->table_w);
1492 WINPR_ASSERT(pkg->table_w->AcquireCredentialsHandleW);
1493 if (pkg->table_w->AcquireCredentialsHandleW(
1494 pszPrincipal, pszPackage, fCredentialUse, pvLogonID, pAuthData, pGetKeyFn,
1495 pvGetKeyArgument, &cred->cred, ptsExpiry) != SEC_E_OK)
1496 continue;
1497
1498 cred->valid = TRUE;
1499 }
1500
1501 sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds);
1502 sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME);
1503 return SEC_E_OK;
1504}
1505
1506static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleA(
1507 SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
1508 void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
1509 PTimeStamp ptsExpiry)
1510{
1511 BOOL kerberos = FALSE;
1512 BOOL ntlm = FALSE;
1513 BOOL u2u = FALSE;
1514
1515 if (!negotiate_get_config(pAuthData, &kerberos, &ntlm, &u2u))
1516 return SEC_E_INTERNAL_ERROR;
1517
1518 MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred));
1519
1520 if (!creds)
1521 return SEC_E_INTERNAL_ERROR;
1522
1523 for (size_t i = 0; i < MECH_COUNT; i++)
1524 {
1525 const SecPkg* pkg = MechTable[i].pkg;
1526 MechCred* cred = &creds[i];
1527
1528 cred->mech = &MechTable[i];
1529
1530 if (!kerberos && sspi_gss_oid_compare(MechTable[i].oid, &kerberos_OID))
1531 continue;
1532 if (!u2u && sspi_gss_oid_compare(MechTable[i].oid, &kerberos_u2u_OID))
1533 continue;
1534 if (!ntlm && _tcsncmp(SecPkgTable[i].name, NTLM_SSP_NAME, ARRAYSIZE(NTLM_SSP_NAME)) == 0)
1535 continue;
1536
1537 WINPR_ASSERT(pkg->table);
1538 WINPR_ASSERT(pkg->table->AcquireCredentialsHandleA);
1539 if (pkg->table->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse,
1540 pvLogonID, pAuthData, pGetKeyFn, pvGetKeyArgument,
1541 &cred->cred, ptsExpiry) != SEC_E_OK)
1542 continue;
1543
1544 cred->valid = TRUE;
1545 }
1546
1547 sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds);
1548 sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME);
1549 return SEC_E_OK;
1550}
1551
1552static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesW(
1553 WINPR_ATTR_UNUSED PCredHandle phCredential, WINPR_ATTR_UNUSED ULONG ulAttribute,
1554 WINPR_ATTR_UNUSED void* pBuffer)
1555{
1556 WLog_ERR(TAG, "TODO: Implement");
1557 return SEC_E_UNSUPPORTED_FUNCTION;
1558}
1559
1560static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesA(
1561 WINPR_ATTR_UNUSED PCredHandle phCredential, WINPR_ATTR_UNUSED ULONG ulAttribute,
1562 WINPR_ATTR_UNUSED void* pBuffer)
1563{
1564 WLog_ERR(TAG, "TODO: Implement");
1565 return SEC_E_UNSUPPORTED_FUNCTION;
1566}
1567
1568static SECURITY_STATUS SEC_ENTRY negotiate_FreeCredentialsHandle(PCredHandle phCredential)
1569{
1570 MechCred* creds = NULL;
1571
1572 creds = sspi_SecureHandleGetLowerPointer(phCredential);
1573 if (!creds)
1574 return SEC_E_INVALID_HANDLE;
1575
1576 for (size_t i = 0; i < MECH_COUNT; i++)
1577 {
1578 MechCred* cred = &creds[i];
1579
1580 WINPR_ASSERT(cred->mech);
1581 WINPR_ASSERT(cred->mech->pkg);
1582 WINPR_ASSERT(cred->mech->pkg->table);
1583 WINPR_ASSERT(cred->mech->pkg->table->FreeCredentialsHandle);
1584 cred->mech->pkg->table->FreeCredentialsHandle(&cred->cred);
1585 }
1586 free(creds);
1587
1588 return SEC_E_OK;
1589}
1590
1591static SECURITY_STATUS SEC_ENTRY negotiate_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
1592 PSecBufferDesc pMessage,
1593 ULONG MessageSeqNo)
1594{
1595 NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1596
1597 if (!context)
1598 return SEC_E_INVALID_HANDLE;
1599
1600 if (context->mic)
1601 MessageSeqNo++;
1602
1603 WINPR_ASSERT(context->mech);
1604 WINPR_ASSERT(context->mech->pkg);
1605 WINPR_ASSERT(context->mech->pkg->table);
1606 if (context->mech->pkg->table->EncryptMessage)
1607 return context->mech->pkg->table->EncryptMessage(&context->sub_context, fQOP, pMessage,
1608 MessageSeqNo);
1609
1610 return SEC_E_UNSUPPORTED_FUNCTION;
1611}
1612
1613static SECURITY_STATUS SEC_ENTRY negotiate_DecryptMessage(PCtxtHandle phContext,
1614 PSecBufferDesc pMessage,
1615 ULONG MessageSeqNo, ULONG* pfQOP)
1616{
1617 NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1618
1619 if (!context)
1620 return SEC_E_INVALID_HANDLE;
1621
1622 if (context->mic)
1623 MessageSeqNo++;
1624
1625 WINPR_ASSERT(context->mech);
1626 WINPR_ASSERT(context->mech->pkg);
1627 WINPR_ASSERT(context->mech->pkg->table);
1628 if (context->mech->pkg->table->DecryptMessage)
1629 return context->mech->pkg->table->DecryptMessage(&context->sub_context, pMessage,
1630 MessageSeqNo, pfQOP);
1631
1632 return SEC_E_UNSUPPORTED_FUNCTION;
1633}
1634
1635static SECURITY_STATUS SEC_ENTRY negotiate_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
1636 PSecBufferDesc pMessage,
1637 ULONG MessageSeqNo)
1638{
1639 NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1640
1641 if (!context)
1642 return SEC_E_INVALID_HANDLE;
1643
1644 if (context->mic)
1645 MessageSeqNo++;
1646
1647 WINPR_ASSERT(context->mech);
1648 WINPR_ASSERT(context->mech->pkg);
1649 WINPR_ASSERT(context->mech->pkg->table);
1650 if (context->mech->pkg->table->MakeSignature)
1651 return context->mech->pkg->table->MakeSignature(&context->sub_context, fQOP, pMessage,
1652 MessageSeqNo);
1653
1654 return SEC_E_UNSUPPORTED_FUNCTION;
1655}
1656
1657static SECURITY_STATUS SEC_ENTRY negotiate_VerifySignature(PCtxtHandle phContext,
1658 PSecBufferDesc pMessage,
1659 ULONG MessageSeqNo, ULONG* pfQOP)
1660{
1661 NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1662
1663 if (!context)
1664 return SEC_E_INVALID_HANDLE;
1665
1666 if (context->mic)
1667 MessageSeqNo++;
1668
1669 WINPR_ASSERT(context->mech);
1670 WINPR_ASSERT(context->mech->pkg);
1671 WINPR_ASSERT(context->mech->pkg->table);
1672 if (context->mech->pkg->table->VerifySignature)
1673 return context->mech->pkg->table->VerifySignature(&context->sub_context, pMessage,
1674 MessageSeqNo, pfQOP);
1675
1676 return SEC_E_UNSUPPORTED_FUNCTION;
1677}
1678
1679const SecurityFunctionTableA NEGOTIATE_SecurityFunctionTableA = {
1680 3, /* dwVersion */
1681 NULL, /* EnumerateSecurityPackages */
1682 negotiate_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
1683 negotiate_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
1684 negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */
1685 NULL, /* Reserved2 */
1686 negotiate_InitializeSecurityContextA, /* InitializeSecurityContext */
1687 negotiate_AcceptSecurityContext, /* AcceptSecurityContext */
1688 negotiate_CompleteAuthToken, /* CompleteAuthToken */
1689 negotiate_DeleteSecurityContext, /* DeleteSecurityContext */
1690 NULL, /* ApplyControlToken */
1691 negotiate_QueryContextAttributesA, /* QueryContextAttributes */
1692 negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
1693 negotiate_RevertSecurityContext, /* RevertSecurityContext */
1694 negotiate_MakeSignature, /* MakeSignature */
1695 negotiate_VerifySignature, /* VerifySignature */
1696 NULL, /* FreeContextBuffer */
1697 NULL, /* QuerySecurityPackageInfo */
1698 NULL, /* Reserved3 */
1699 NULL, /* Reserved4 */
1700 NULL, /* ExportSecurityContext */
1701 NULL, /* ImportSecurityContext */
1702 NULL, /* AddCredentials */
1703 NULL, /* Reserved8 */
1704 NULL, /* QuerySecurityContextToken */
1705 negotiate_EncryptMessage, /* EncryptMessage */
1706 negotiate_DecryptMessage, /* DecryptMessage */
1707 negotiate_SetContextAttributesA, /* SetContextAttributes */
1708 negotiate_SetCredentialsAttributesA, /* SetCredentialsAttributes */
1709};
1710
1711const SecurityFunctionTableW NEGOTIATE_SecurityFunctionTableW = {
1712 3, /* dwVersion */
1713 NULL, /* EnumerateSecurityPackages */
1714 negotiate_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
1715 negotiate_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
1716 negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */
1717 NULL, /* Reserved2 */
1718 negotiate_InitializeSecurityContextW, /* InitializeSecurityContext */
1719 negotiate_AcceptSecurityContext, /* AcceptSecurityContext */
1720 negotiate_CompleteAuthToken, /* CompleteAuthToken */
1721 negotiate_DeleteSecurityContext, /* DeleteSecurityContext */
1722 NULL, /* ApplyControlToken */
1723 negotiate_QueryContextAttributesW, /* QueryContextAttributes */
1724 negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
1725 negotiate_RevertSecurityContext, /* RevertSecurityContext */
1726 negotiate_MakeSignature, /* MakeSignature */
1727 negotiate_VerifySignature, /* VerifySignature */
1728 NULL, /* FreeContextBuffer */
1729 NULL, /* QuerySecurityPackageInfo */
1730 NULL, /* Reserved3 */
1731 NULL, /* Reserved4 */
1732 NULL, /* ExportSecurityContext */
1733 NULL, /* ImportSecurityContext */
1734 NULL, /* AddCredentials */
1735 NULL, /* Reserved8 */
1736 NULL, /* QuerySecurityContextToken */
1737 negotiate_EncryptMessage, /* EncryptMessage */
1738 negotiate_DecryptMessage, /* DecryptMessage */
1739 negotiate_SetContextAttributesW, /* SetContextAttributes */
1740 negotiate_SetCredentialsAttributesW, /* SetCredentialsAttributes */
1741};
1742
1743BOOL NEGOTIATE_init(void)
1744{
1745 InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Name, NEGOTIATE_SecPkgInfoW_NameBuffer,
1746 ARRAYSIZE(NEGOTIATE_SecPkgInfoW_NameBuffer));
1747 InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Comment, NEGOTIATE_SecPkgInfoW_CommentBuffer,
1748 ARRAYSIZE(NEGOTIATE_SecPkgInfoW_CommentBuffer));
1749
1750 return TRUE;
1751}