FreeRDP
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 
41 static const char NEGO_REG_KEY[] =
42  "Software\\" WINPR_VENDOR_STRING "\\" WINPR_PRODUCT_STRING "\\SSPI\\Negotiate";
43 
44 typedef struct
45 {
46  const TCHAR* name;
47  const SecurityFunctionTableA* table;
48  const SecurityFunctionTableW* table_w;
49 } SecPkg;
50 
51 struct Mech_st
52 {
53  const WinPrAsn1_OID* oid;
54  const SecPkg* pkg;
55  const UINT flags;
56  const BOOL preferred;
57 };
58 
59 typedef struct
60 {
61  const Mech* mech;
62  CredHandle cred;
63  BOOL valid;
64 } MechCred;
65 
66 const 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 
75 static WCHAR NEGOTIATE_SecPkgInfoW_NameBuffer[32] = { 0 };
76 static WCHAR NEGOTIATE_SecPkgInfoW_CommentBuffer[32] = { 0 };
77 
78 const 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 
87 static const WinPrAsn1_OID spnego_OID = { 6, (BYTE*)"\x2b\x06\x01\x05\x05\x02" };
88 static const WinPrAsn1_OID kerberos_u2u_OID = { 10,
89  (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" };
90 static const WinPrAsn1_OID kerberos_OID = { 9, (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
91 static const WinPrAsn1_OID kerberos_wrong_OID = { 9,
92  (BYTE*)"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02" };
93 static const WinPrAsn1_OID ntlm_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" };
94 
95 static const WinPrAsn1_OID negoex_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e" };
96 
97 #ifdef WITH_KRB5
98 static 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 
104 static 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
110 static const SecPkg SecPkgTable[] = { { NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA,
111  &NTLM_SecurityFunctionTableW } };
112 
113 static const Mech MechTable[] = {
114  { &ntlm_OID, &SecPkgTable[0], 0, FALSE },
115 };
116 #endif
117 
118 static const size_t MECH_COUNT = sizeof(MechTable) / sizeof(Mech);
119 
120 enum NegState
121 {
122  NOSTATE = -1,
123  ACCEPT_COMPLETED,
124  ACCEPT_INCOMPLETE,
125  REJECT,
126  REQUEST_MIC
127 };
128 
129 typedef 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 
139 static const NegToken empty_neg_token = { NOSTATE, FALSE, { 0, NULL },
140  { 0, 0, NULL }, { 0, 0, NULL }, { 0, 0, NULL } };
141 
142 static 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 
167 static 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 
176 static 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 
194 static 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 
214 static 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 
236 static 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 
252 static 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 
294 static 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 
333 static 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 
432 cleanup:
433  WinPrAsn1Encoder_Free(&enc);
434  return ret;
435 }
436 
437 static BOOL negotiate_read_neg_token(PSecBuffer input, NegToken* token)
438 {
439  WinPrAsn1Decoder dec;
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 
562 static 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 
626 static 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 prefered 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 
907 cleanup:
908  WinPrAsn1Encoder_Free(&enc);
909  return status;
910 }
911 
912 static 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 
934 static 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 
969 static 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;
985  WinPrAsn1Decoder dec;
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 
1206 static 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 
1225 static 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 
1247 static SECURITY_STATUS SEC_ENTRY negotiate_ImpersonateSecurityContext(PCtxtHandle phContext)
1248 {
1249  return SEC_E_OK;
1250 }
1251 
1252 static SECURITY_STATUS SEC_ENTRY negotiate_RevertSecurityContext(PCtxtHandle phContext)
1253 {
1254  return SEC_E_OK;
1255 }
1256 
1257 static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesW(PCtxtHandle phContext,
1258  ULONG ulAttribute, void* pBuffer)
1259 {
1260  NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1261 
1262  if (!context)
1263  return SEC_E_INVALID_HANDLE;
1264 
1265  WINPR_ASSERT(context->mech);
1266  WINPR_ASSERT(context->mech->pkg);
1267  WINPR_ASSERT(context->mech->pkg->table_w);
1268  if (context->mech->pkg->table_w->QueryContextAttributesW)
1269  return context->mech->pkg->table_w->QueryContextAttributesW(&context->sub_context,
1270  ulAttribute, pBuffer);
1271 
1272  return SEC_E_UNSUPPORTED_FUNCTION;
1273 }
1274 
1275 static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesA(PCtxtHandle phContext,
1276  ULONG ulAttribute, void* pBuffer)
1277 {
1278  NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1279 
1280  if (!context)
1281  return SEC_E_INVALID_HANDLE;
1282 
1283  WINPR_ASSERT(context->mech);
1284  WINPR_ASSERT(context->mech->pkg);
1285  WINPR_ASSERT(context->mech->pkg->table);
1286  if (context->mech->pkg->table->QueryContextAttributesA)
1287  return context->mech->pkg->table->QueryContextAttributesA(&context->sub_context,
1288  ulAttribute, pBuffer);
1289 
1290  return SEC_E_UNSUPPORTED_FUNCTION;
1291 }
1292 
1293 static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesW(PCtxtHandle phContext,
1294  ULONG ulAttribute, void* pBuffer,
1295  ULONG cbBuffer)
1296 {
1297  NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1298 
1299  if (!context)
1300  return SEC_E_INVALID_HANDLE;
1301 
1302  WINPR_ASSERT(context->mech);
1303  WINPR_ASSERT(context->mech->pkg);
1304  WINPR_ASSERT(context->mech->pkg->table_w);
1305  if (context->mech->pkg->table_w->SetContextAttributesW)
1306  return context->mech->pkg->table_w->SetContextAttributesW(&context->sub_context,
1307  ulAttribute, pBuffer, cbBuffer);
1308 
1309  return SEC_E_UNSUPPORTED_FUNCTION;
1310 }
1311 
1312 static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesA(PCtxtHandle phContext,
1313  ULONG ulAttribute, void* pBuffer,
1314  ULONG cbBuffer)
1315 {
1316  NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1317 
1318  if (!context)
1319  return SEC_E_INVALID_HANDLE;
1320 
1321  WINPR_ASSERT(context->mech);
1322  WINPR_ASSERT(context->mech->pkg);
1323  WINPR_ASSERT(context->mech->pkg->table);
1324  if (context->mech->pkg->table->SetContextAttributesA)
1325  return context->mech->pkg->table->SetContextAttributesA(&context->sub_context, ulAttribute,
1326  pBuffer, cbBuffer);
1327 
1328  return SEC_E_UNSUPPORTED_FUNCTION;
1329 }
1330 
1331 static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesW(PCredHandle phCredential,
1332  ULONG ulAttribute,
1333  void* pBuffer, ULONG cbBuffer)
1334 {
1335  MechCred* creds = NULL;
1336  BOOL success = FALSE;
1337  SECURITY_STATUS secStatus = 0;
1338 
1339  creds = sspi_SecureHandleGetLowerPointer(phCredential);
1340 
1341  if (!creds)
1342  return SEC_E_INVALID_HANDLE;
1343 
1344  for (size_t i = 0; i < MECH_COUNT; i++)
1345  {
1346  MechCred* cred = &creds[i];
1347 
1348  WINPR_ASSERT(cred->mech);
1349  WINPR_ASSERT(cred->mech->pkg);
1350  WINPR_ASSERT(cred->mech->pkg->table);
1351  WINPR_ASSERT(cred->mech->pkg->table_w->SetCredentialsAttributesW);
1352  secStatus = cred->mech->pkg->table_w->SetCredentialsAttributesW(&cred->cred, ulAttribute,
1353  pBuffer, cbBuffer);
1354 
1355  if (secStatus == SEC_E_OK)
1356  {
1357  success = TRUE;
1358  }
1359  }
1360 
1361  // return success if at least one submodule accepts the credential attribute
1362  return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION);
1363 }
1364 
1365 static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesA(PCredHandle phCredential,
1366  ULONG ulAttribute,
1367  void* pBuffer, ULONG cbBuffer)
1368 {
1369  MechCred* creds = NULL;
1370  BOOL success = FALSE;
1371  SECURITY_STATUS secStatus = 0;
1372 
1373  creds = sspi_SecureHandleGetLowerPointer(phCredential);
1374 
1375  if (!creds)
1376  return SEC_E_INVALID_HANDLE;
1377 
1378  for (size_t i = 0; i < MECH_COUNT; i++)
1379  {
1380  MechCred* cred = &creds[i];
1381 
1382  if (!cred->valid)
1383  continue;
1384 
1385  WINPR_ASSERT(cred->mech);
1386  WINPR_ASSERT(cred->mech->pkg);
1387  WINPR_ASSERT(cred->mech->pkg->table);
1388  WINPR_ASSERT(cred->mech->pkg->table->SetCredentialsAttributesA);
1389  secStatus = cred->mech->pkg->table->SetCredentialsAttributesA(&cred->cred, ulAttribute,
1390  pBuffer, cbBuffer);
1391 
1392  if (secStatus == SEC_E_OK)
1393  {
1394  success = TRUE;
1395  }
1396  }
1397 
1398  // return success if at least one submodule accepts the credential attribute
1399  return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION);
1400 }
1401 
1402 static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleW(
1403  SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
1404  void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
1405  PTimeStamp ptsExpiry)
1406 {
1407  BOOL kerberos = 0;
1408  BOOL ntlm = 0;
1409 
1410  if (!negotiate_get_config(pAuthData, &kerberos, &ntlm))
1411  return SEC_E_INTERNAL_ERROR;
1412 
1413  MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred));
1414 
1415  if (!creds)
1416  return SEC_E_INTERNAL_ERROR;
1417 
1418  for (size_t i = 0; i < MECH_COUNT; i++)
1419  {
1420  MechCred* cred = &creds[i];
1421  const SecPkg* pkg = MechTable[i].pkg;
1422  cred->mech = &MechTable[i];
1423 
1424  if (!kerberos && _tcsncmp(pkg->name, KERBEROS_SSP_NAME, ARRAYSIZE(KERBEROS_SSP_NAME)) == 0)
1425  continue;
1426  if (!ntlm && _tcsncmp(SecPkgTable[i].name, NTLM_SSP_NAME, ARRAYSIZE(NTLM_SSP_NAME)) == 0)
1427  continue;
1428 
1429  WINPR_ASSERT(pkg->table_w);
1430  WINPR_ASSERT(pkg->table_w->AcquireCredentialsHandleW);
1431  if (pkg->table_w->AcquireCredentialsHandleW(
1432  pszPrincipal, pszPackage, fCredentialUse, pvLogonID, pAuthData, pGetKeyFn,
1433  pvGetKeyArgument, &cred->cred, ptsExpiry) != SEC_E_OK)
1434  continue;
1435 
1436  cred->valid = TRUE;
1437  }
1438 
1439  sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds);
1440  sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME);
1441  return SEC_E_OK;
1442 }
1443 
1444 static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleA(
1445  SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
1446  void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
1447  PTimeStamp ptsExpiry)
1448 {
1449  BOOL kerberos = 0;
1450  BOOL ntlm = 0;
1451 
1452  if (!negotiate_get_config(pAuthData, &kerberos, &ntlm))
1453  return SEC_E_INTERNAL_ERROR;
1454 
1455  MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred));
1456 
1457  if (!creds)
1458  return SEC_E_INTERNAL_ERROR;
1459 
1460  for (size_t i = 0; i < MECH_COUNT; i++)
1461  {
1462  const SecPkg* pkg = MechTable[i].pkg;
1463  MechCred* cred = &creds[i];
1464 
1465  cred->mech = &MechTable[i];
1466 
1467  if (!kerberos && _tcsncmp(pkg->name, KERBEROS_SSP_NAME, ARRAYSIZE(KERBEROS_SSP_NAME)) == 0)
1468  continue;
1469  if (!ntlm && _tcsncmp(SecPkgTable[i].name, NTLM_SSP_NAME, ARRAYSIZE(NTLM_SSP_NAME)) == 0)
1470  continue;
1471 
1472  WINPR_ASSERT(pkg->table);
1473  WINPR_ASSERT(pkg->table->AcquireCredentialsHandleA);
1474  if (pkg->table->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse,
1475  pvLogonID, pAuthData, pGetKeyFn, pvGetKeyArgument,
1476  &cred->cred, ptsExpiry) != SEC_E_OK)
1477  continue;
1478 
1479  cred->valid = TRUE;
1480  }
1481 
1482  sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds);
1483  sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME);
1484  return SEC_E_OK;
1485 }
1486 
1487 static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesW(PCredHandle phCredential,
1488  ULONG ulAttribute,
1489  void* pBuffer)
1490 {
1491  WLog_ERR(TAG, "TODO: Implement");
1492  return SEC_E_UNSUPPORTED_FUNCTION;
1493 }
1494 
1495 static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesA(PCredHandle phCredential,
1496  ULONG ulAttribute,
1497  void* pBuffer)
1498 {
1499  WLog_ERR(TAG, "TODO: Implement");
1500  return SEC_E_UNSUPPORTED_FUNCTION;
1501 }
1502 
1503 static SECURITY_STATUS SEC_ENTRY negotiate_FreeCredentialsHandle(PCredHandle phCredential)
1504 {
1505  MechCred* creds = NULL;
1506 
1507  creds = sspi_SecureHandleGetLowerPointer(phCredential);
1508  if (!creds)
1509  return SEC_E_INVALID_HANDLE;
1510 
1511  for (size_t i = 0; i < MECH_COUNT; i++)
1512  {
1513  MechCred* cred = &creds[i];
1514 
1515  WINPR_ASSERT(cred->mech);
1516  WINPR_ASSERT(cred->mech->pkg);
1517  WINPR_ASSERT(cred->mech->pkg->table);
1518  WINPR_ASSERT(cred->mech->pkg->table->FreeCredentialsHandle);
1519  cred->mech->pkg->table->FreeCredentialsHandle(&cred->cred);
1520  }
1521  free(creds);
1522 
1523  return SEC_E_OK;
1524 }
1525 
1526 static SECURITY_STATUS SEC_ENTRY negotiate_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
1527  PSecBufferDesc pMessage,
1528  ULONG MessageSeqNo)
1529 {
1530  NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1531 
1532  if (!context)
1533  return SEC_E_INVALID_HANDLE;
1534 
1535  if (context->mic)
1536  MessageSeqNo++;
1537 
1538  WINPR_ASSERT(context->mech);
1539  WINPR_ASSERT(context->mech->pkg);
1540  WINPR_ASSERT(context->mech->pkg->table);
1541  if (context->mech->pkg->table->EncryptMessage)
1542  return context->mech->pkg->table->EncryptMessage(&context->sub_context, fQOP, pMessage,
1543  MessageSeqNo);
1544 
1545  return SEC_E_UNSUPPORTED_FUNCTION;
1546 }
1547 
1548 static SECURITY_STATUS SEC_ENTRY negotiate_DecryptMessage(PCtxtHandle phContext,
1549  PSecBufferDesc pMessage,
1550  ULONG MessageSeqNo, ULONG* pfQOP)
1551 {
1552  NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1553 
1554  if (!context)
1555  return SEC_E_INVALID_HANDLE;
1556 
1557  if (context->mic)
1558  MessageSeqNo++;
1559 
1560  WINPR_ASSERT(context->mech);
1561  WINPR_ASSERT(context->mech->pkg);
1562  WINPR_ASSERT(context->mech->pkg->table);
1563  if (context->mech->pkg->table->DecryptMessage)
1564  return context->mech->pkg->table->DecryptMessage(&context->sub_context, pMessage,
1565  MessageSeqNo, pfQOP);
1566 
1567  return SEC_E_UNSUPPORTED_FUNCTION;
1568 }
1569 
1570 static SECURITY_STATUS SEC_ENTRY negotiate_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
1571  PSecBufferDesc pMessage,
1572  ULONG MessageSeqNo)
1573 {
1574  NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1575 
1576  if (!context)
1577  return SEC_E_INVALID_HANDLE;
1578 
1579  if (context->mic)
1580  MessageSeqNo++;
1581 
1582  WINPR_ASSERT(context->mech);
1583  WINPR_ASSERT(context->mech->pkg);
1584  WINPR_ASSERT(context->mech->pkg->table);
1585  if (context->mech->pkg->table->MakeSignature)
1586  return context->mech->pkg->table->MakeSignature(&context->sub_context, fQOP, pMessage,
1587  MessageSeqNo);
1588 
1589  return SEC_E_UNSUPPORTED_FUNCTION;
1590 }
1591 
1592 static SECURITY_STATUS SEC_ENTRY negotiate_VerifySignature(PCtxtHandle phContext,
1593  PSecBufferDesc pMessage,
1594  ULONG MessageSeqNo, ULONG* pfQOP)
1595 {
1596  NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
1597 
1598  if (!context)
1599  return SEC_E_INVALID_HANDLE;
1600 
1601  if (context->mic)
1602  MessageSeqNo++;
1603 
1604  WINPR_ASSERT(context->mech);
1605  WINPR_ASSERT(context->mech->pkg);
1606  WINPR_ASSERT(context->mech->pkg->table);
1607  if (context->mech->pkg->table->VerifySignature)
1608  return context->mech->pkg->table->VerifySignature(&context->sub_context, pMessage,
1609  MessageSeqNo, pfQOP);
1610 
1611  return SEC_E_UNSUPPORTED_FUNCTION;
1612 }
1613 
1614 const SecurityFunctionTableA NEGOTIATE_SecurityFunctionTableA = {
1615  3, /* dwVersion */
1616  NULL, /* EnumerateSecurityPackages */
1617  negotiate_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
1618  negotiate_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
1619  negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */
1620  NULL, /* Reserved2 */
1621  negotiate_InitializeSecurityContextA, /* InitializeSecurityContext */
1622  negotiate_AcceptSecurityContext, /* AcceptSecurityContext */
1623  negotiate_CompleteAuthToken, /* CompleteAuthToken */
1624  negotiate_DeleteSecurityContext, /* DeleteSecurityContext */
1625  NULL, /* ApplyControlToken */
1626  negotiate_QueryContextAttributesA, /* QueryContextAttributes */
1627  negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
1628  negotiate_RevertSecurityContext, /* RevertSecurityContext */
1629  negotiate_MakeSignature, /* MakeSignature */
1630  negotiate_VerifySignature, /* VerifySignature */
1631  NULL, /* FreeContextBuffer */
1632  NULL, /* QuerySecurityPackageInfo */
1633  NULL, /* Reserved3 */
1634  NULL, /* Reserved4 */
1635  NULL, /* ExportSecurityContext */
1636  NULL, /* ImportSecurityContext */
1637  NULL, /* AddCredentials */
1638  NULL, /* Reserved8 */
1639  NULL, /* QuerySecurityContextToken */
1640  negotiate_EncryptMessage, /* EncryptMessage */
1641  negotiate_DecryptMessage, /* DecryptMessage */
1642  negotiate_SetContextAttributesA, /* SetContextAttributes */
1643  negotiate_SetCredentialsAttributesA, /* SetCredentialsAttributes */
1644 };
1645 
1646 const SecurityFunctionTableW NEGOTIATE_SecurityFunctionTableW = {
1647  3, /* dwVersion */
1648  NULL, /* EnumerateSecurityPackages */
1649  negotiate_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
1650  negotiate_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
1651  negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */
1652  NULL, /* Reserved2 */
1653  negotiate_InitializeSecurityContextW, /* InitializeSecurityContext */
1654  negotiate_AcceptSecurityContext, /* AcceptSecurityContext */
1655  negotiate_CompleteAuthToken, /* CompleteAuthToken */
1656  negotiate_DeleteSecurityContext, /* DeleteSecurityContext */
1657  NULL, /* ApplyControlToken */
1658  negotiate_QueryContextAttributesW, /* QueryContextAttributes */
1659  negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
1660  negotiate_RevertSecurityContext, /* RevertSecurityContext */
1661  negotiate_MakeSignature, /* MakeSignature */
1662  negotiate_VerifySignature, /* VerifySignature */
1663  NULL, /* FreeContextBuffer */
1664  NULL, /* QuerySecurityPackageInfo */
1665  NULL, /* Reserved3 */
1666  NULL, /* Reserved4 */
1667  NULL, /* ExportSecurityContext */
1668  NULL, /* ImportSecurityContext */
1669  NULL, /* AddCredentials */
1670  NULL, /* Reserved8 */
1671  NULL, /* QuerySecurityContextToken */
1672  negotiate_EncryptMessage, /* EncryptMessage */
1673  negotiate_DecryptMessage, /* DecryptMessage */
1674  negotiate_SetContextAttributesW, /* SetContextAttributes */
1675  negotiate_SetCredentialsAttributesW, /* SetCredentialsAttributes */
1676 };
1677 
1678 BOOL NEGOTIATE_init(void)
1679 {
1680  InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Name, NEGOTIATE_SecPkgInfoW_NameBuffer,
1681  ARRAYSIZE(NEGOTIATE_SecPkgInfoW_NameBuffer));
1682  InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Comment, NEGOTIATE_SecPkgInfoW_CommentBuffer,
1683  ARRAYSIZE(NEGOTIATE_SecPkgInfoW_CommentBuffer));
1684 
1685  return TRUE;
1686 }