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