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