Coverage Report

Created: 2026-10-28 06:49

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