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