FreeRDP
kerberos.c
1 
22 #include <winpr/config.h>
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <ctype.h>
30 
31 #include <winpr/assert.h>
32 #include <winpr/cast.h>
33 #include <winpr/asn1.h>
34 #include <winpr/crt.h>
35 #include <winpr/interlocked.h>
36 #include <winpr/sspi.h>
37 #include <winpr/print.h>
38 #include <winpr/tchar.h>
39 #include <winpr/sysinfo.h>
40 #include <winpr/registry.h>
41 #include <winpr/endian.h>
42 #include <winpr/crypto.h>
43 #include <winpr/path.h>
44 #include <winpr/wtypes.h>
45 #include <winpr/winsock.h>
46 #include <winpr/schannel.h>
47 #include <winpr/secapi.h>
48 
49 #include "kerberos.h"
50 
51 #ifdef WITH_KRB5_MIT
52 #include "krb5glue.h"
53 #include <profile.h>
54 #endif
55 
56 #ifdef WITH_KRB5_HEIMDAL
57 #include "krb5glue.h"
58 #include <krb5-protos.h>
59 #endif
60 
61 #include "../sspi.h"
62 #include "../../log.h"
63 #define TAG WINPR_TAG("sspi.Kerberos")
64 
65 #define KRB_TGT_REQ 16
66 #define KRB_TGT_REP 17
67 
68 const SecPkgInfoA KERBEROS_SecPkgInfoA = {
69  0x000F3BBF, /* fCapabilities */
70  1, /* wVersion */
71  0x0010, /* wRPCID */
72  0x0000BB80, /* cbMaxToken : 48k bytes maximum for Windows Server 2012 */
73  "Kerberos", /* Name */
74  "Kerberos Security Package" /* Comment */
75 };
76 
77 static WCHAR KERBEROS_SecPkgInfoW_NameBuffer[32] = { 0 };
78 static WCHAR KERBEROS_SecPkgInfoW_CommentBuffer[32] = { 0 };
79 
80 const SecPkgInfoW KERBEROS_SecPkgInfoW = {
81  0x000F3BBF, /* fCapabilities */
82  1, /* wVersion */
83  0x0010, /* wRPCID */
84  0x0000BB80, /* cbMaxToken : 48k bytes maximum for Windows Server 2012 */
85  KERBEROS_SecPkgInfoW_NameBuffer, /* Name */
86  KERBEROS_SecPkgInfoW_CommentBuffer /* Comment */
87 };
88 
89 #ifdef WITH_KRB5
90 
91 enum KERBEROS_STATE
92 {
93  KERBEROS_STATE_INITIAL,
94  KERBEROS_STATE_TGT_REQ,
95  KERBEROS_STATE_TGT_REP,
96  KERBEROS_STATE_AP_REQ,
97  KERBEROS_STATE_AP_REP,
98  KERBEROS_STATE_FINAL
99 };
100 
101 typedef struct KRB_CREDENTIALS_st
102 {
103  volatile LONG refCount;
104  krb5_context ctx;
105  char* kdc_url;
106  krb5_ccache ccache;
107  krb5_keytab keytab;
108  krb5_keytab client_keytab;
109  BOOL own_ccache;
110 } KRB_CREDENTIALS;
111 
112 struct s_KRB_CONTEXT
113 {
114  enum KERBEROS_STATE state;
115  KRB_CREDENTIALS* credentials;
116  krb5_auth_context auth_ctx;
117  BOOL acceptor;
118  uint32_t flags;
119  uint64_t local_seq;
120  uint64_t remote_seq;
121  struct krb5glue_keyset keyset;
122  BOOL u2u;
123  char* targetHost;
124 };
125 
126 static const WinPrAsn1_OID kerberos_OID = { 9, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
127 static const WinPrAsn1_OID kerberos_u2u_OID = { 10,
128  (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" };
129 
130 #define krb_log_exec_bool(fkt, ctx, ...) \
131  kerberos_log_msg(ctx, fkt(ctx, ##__VA_ARGS__) ? KRB5KRB_ERR_GENERIC : 0, #fkt, __FILE__, \
132  __func__, __LINE__)
133 #define krb_log_exec(fkt, ctx, ...) \
134  kerberos_log_msg(ctx, fkt(ctx, ##__VA_ARGS__), #fkt, __FILE__, __func__, __LINE__)
135 #define krb_log_exec_ptr(fkt, ctx, ...) \
136  kerberos_log_msg(*ctx, fkt(ctx, ##__VA_ARGS__), #fkt, __FILE__, __func__, __LINE__)
137 static krb5_error_code kerberos_log_msg(krb5_context ctx, krb5_error_code code, const char* what,
138  const char* file, const char* fkt, size_t line)
139 {
140  switch (code)
141  {
142  case 0:
143  case KRB5_KT_END:
144  break;
145  default:
146  {
147  const DWORD level = WLOG_ERROR;
148 
149  wLog* log = WLog_Get(TAG);
150  if (WLog_IsLevelActive(log, level))
151  {
152  const char* msg = krb5_get_error_message(ctx, code);
153  WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, "%s (%s [%d])",
154  what, msg, code);
155  krb5_free_error_message(ctx, msg);
156  }
157  }
158  break;
159  }
160  return code;
161 }
162 
163 static void credentials_unref(KRB_CREDENTIALS* credentials);
164 
165 static void kerberos_ContextFree(KRB_CONTEXT* ctx, BOOL allocated)
166 {
167  if (!ctx)
168  return;
169 
170  free(ctx->targetHost);
171  ctx->targetHost = NULL;
172 
173  if (ctx->credentials)
174  {
175  krb5_context krbctx = ctx->credentials->ctx;
176  if (krbctx)
177  {
178  if (ctx->auth_ctx)
179  krb5_auth_con_free(krbctx, ctx->auth_ctx);
180 
181  krb5glue_keys_free(krbctx, &ctx->keyset);
182  }
183 
184  credentials_unref(ctx->credentials);
185  }
186 
187  if (allocated)
188  free(ctx);
189 }
190 
191 static KRB_CONTEXT* kerberos_ContextNew(KRB_CREDENTIALS* credentials)
192 {
193  KRB_CONTEXT* context = NULL;
194 
195  context = (KRB_CONTEXT*)calloc(1, sizeof(KRB_CONTEXT));
196  if (!context)
197  return NULL;
198 
199  context->credentials = credentials;
200  InterlockedIncrement(&credentials->refCount);
201  return context;
202 }
203 
204 static krb5_error_code krb5_prompter(krb5_context context, void* data, const char* name,
205  const char* banner, int num_prompts, krb5_prompt prompts[])
206 {
207  for (int i = 0; i < num_prompts; i++)
208  {
209  krb5_prompt_type type = krb5glue_get_prompt_type(context, prompts, i);
210  if (type && (type == KRB5_PROMPT_TYPE_PREAUTH || type == KRB5_PROMPT_TYPE_PASSWORD) && data)
211  {
212  prompts[i].reply->data = _strdup((const char*)data);
213 
214  const size_t len = strlen((const char*)data);
215  if (len > UINT32_MAX)
216  return KRB5KRB_ERR_GENERIC;
217  prompts[i].reply->length = (UINT32)len;
218  }
219  }
220  return 0;
221 }
222 
223 static INLINE krb5glue_key get_key(struct krb5glue_keyset* keyset)
224 {
225  return keyset->acceptor_key ? keyset->acceptor_key
226  : keyset->initiator_key ? keyset->initiator_key
227  : keyset->session_key;
228 }
229 
230 static BOOL isValidIPv4(const char* ipAddress)
231 {
232  struct sockaddr_in sa = { 0 };
233  int result = inet_pton(AF_INET, ipAddress, &(sa.sin_addr));
234  return result != 0;
235 }
236 
237 static BOOL isValidIPv6(const char* ipAddress)
238 {
239  struct sockaddr_in6 sa = { 0 };
240  int result = inet_pton(AF_INET6, ipAddress, &(sa.sin6_addr));
241  return result != 0;
242 }
243 
244 static BOOL isValidIP(const char* ipAddress)
245 {
246  return isValidIPv4(ipAddress) || isValidIPv6(ipAddress);
247 }
248 
249 static int build_krbtgt(krb5_context ctx, krb5_data* realm, krb5_principal* ptarget)
250 {
251  /* "krbtgt/" + realm + "@" + realm */
252  size_t len = 0;
253  char* name = NULL;
254  krb5_error_code rv = KRB5_CC_NOMEM;
255 
256  (void)winpr_asprintf(&name, &len, "krbtgt/%s@%s", realm->data, realm->data);
257  if (!name || (len == 0))
258  goto fail;
259 
260  krb5_principal target = { 0 };
261  rv = krb5_parse_name(ctx, name, &target);
262  *ptarget = target;
263 fail:
264  free(name);
265  return rv;
266 }
267 
268 #endif /* WITH_KRB5 */
269 
270 static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA(
271  SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
272  void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
273  PTimeStamp ptsExpiry)
274 {
275 #ifdef WITH_KRB5
276  SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;
277  KRB_CREDENTIALS* credentials = NULL;
278  krb5_context ctx = NULL;
279  krb5_ccache ccache = NULL;
280  krb5_keytab keytab = NULL;
281  krb5_principal principal = NULL;
282  char* domain = NULL;
283  char* username = NULL;
284  char* password = NULL;
285  BOOL own_ccache = FALSE;
286  const char* const default_ccache_type = "MEMORY";
287 
288  if (pAuthData)
289  {
290  UINT32 identityFlags = sspi_GetAuthIdentityFlags(pAuthData);
291 
292  if (identityFlags & SEC_WINNT_AUTH_IDENTITY_EXTENDED)
293  krb_settings = (((SEC_WINNT_AUTH_IDENTITY_WINPR*)pAuthData)->kerberosSettings);
294 
295  if (!sspi_CopyAuthIdentityFieldsA((const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData, &username,
296  &domain, &password))
297  {
298  WLog_ERR(TAG, "Failed to copy auth identity fields");
299  goto cleanup;
300  }
301 
302  if (!pszPrincipal)
303  pszPrincipal = username;
304  }
305 
306  if (krb_log_exec_ptr(krb5_init_context, &ctx))
307  goto cleanup;
308 
309  if (domain)
310  {
311  char* udomain = _strdup(domain);
312  if (!udomain)
313  goto cleanup;
314 
315  CharUpperA(udomain);
316  /* Will use domain if realm is not specified in username */
317  krb5_error_code rv = krb_log_exec(krb5_set_default_realm, ctx, udomain);
318  free(udomain);
319 
320  if (rv)
321  goto cleanup;
322  }
323 
324  if (pszPrincipal)
325  {
326  char* cpszPrincipal = _strdup(pszPrincipal);
327  if (!cpszPrincipal)
328  goto cleanup;
329 
330  /* Find realm component if included and convert to uppercase */
331  char* p = strchr(cpszPrincipal, '@');
332  if (p)
333  CharUpperA(p);
334 
335  krb5_error_code rv = krb_log_exec(krb5_parse_name, ctx, cpszPrincipal, &principal);
336  free(cpszPrincipal);
337 
338  if (rv)
339  goto cleanup;
340  }
341 
342  if (krb_settings && krb_settings->cache)
343  {
344  if ((krb_log_exec(krb5_cc_set_default_name, ctx, krb_settings->cache)))
345  goto cleanup;
346  }
347  else
348  own_ccache = TRUE;
349 
350  if (principal)
351  {
352  /* Use the default cache if it's initialized with the right principal */
353  if (krb5_cc_cache_match(ctx, principal, &ccache) == KRB5_CC_NOTFOUND)
354  {
355  if (own_ccache)
356  {
357  if (krb_log_exec(krb5_cc_new_unique, ctx, default_ccache_type, 0, &ccache))
358  goto cleanup;
359  }
360  else
361  {
362  if (krb_log_exec(krb5_cc_resolve, ctx, krb_settings->cache, &ccache))
363  goto cleanup;
364  }
365 
366  if (krb_log_exec(krb5_cc_initialize, ctx, ccache, principal))
367  goto cleanup;
368  }
369  else
370  own_ccache = FALSE;
371  }
372  else if (fCredentialUse & SECPKG_CRED_OUTBOUND)
373  {
374  /* Use the default cache with it's default principal */
375  if (krb_log_exec(krb5_cc_default, ctx, &ccache))
376  goto cleanup;
377  if (krb_log_exec(krb5_cc_get_principal, ctx, ccache, &principal))
378  goto cleanup;
379  own_ccache = FALSE;
380  }
381  else
382  {
383  if (own_ccache)
384  {
385  if (krb_log_exec(krb5_cc_new_unique, ctx, default_ccache_type, 0, &ccache))
386  goto cleanup;
387  }
388  else
389  {
390  if (krb_log_exec(krb5_cc_resolve, ctx, krb_settings->cache, &ccache))
391  goto cleanup;
392  }
393  }
394 
395  if (krb_settings && krb_settings->keytab)
396  {
397  if (krb_log_exec(krb5_kt_resolve, ctx, krb_settings->keytab, &keytab))
398  goto cleanup;
399  }
400  else
401  {
402  if (fCredentialUse & SECPKG_CRED_INBOUND)
403  if (krb_log_exec(krb5_kt_default, ctx, &keytab))
404  goto cleanup;
405  }
406 
407  /* Get initial credentials if required */
408  if (fCredentialUse & SECPKG_CRED_OUTBOUND)
409  {
410  krb5_creds creds = { 0 };
411  krb5_creds matchCreds = { 0 };
412  krb5_flags matchFlags = KRB5_TC_MATCH_TIMES;
413 
414  krb5_timeofday(ctx, &matchCreds.times.endtime);
415  matchCreds.times.endtime += 60;
416  matchCreds.client = principal;
417 
418  if (krb_log_exec(build_krbtgt, ctx, &principal->realm, &matchCreds.server))
419  goto cleanup;
420 
421  int rv = krb5_cc_retrieve_cred(ctx, ccache, matchFlags, &matchCreds, &creds);
422  krb5_free_principal(ctx, matchCreds.server);
423  krb5_free_cred_contents(ctx, &creds);
424  if (rv)
425  {
426  if (krb_log_exec(krb5glue_get_init_creds, ctx, principal, ccache, krb5_prompter,
427  password, krb_settings))
428  goto cleanup;
429  }
430  }
431 
432  credentials = calloc(1, sizeof(KRB_CREDENTIALS));
433  if (!credentials)
434  goto cleanup;
435  credentials->refCount = 1;
436  credentials->ctx = ctx;
437  credentials->ccache = ccache;
438  credentials->keytab = keytab;
439  credentials->own_ccache = own_ccache;
440 
441 cleanup:
442 
443  free(domain);
444  free(username);
445  free(password);
446 
447  if (principal)
448  krb5_free_principal(ctx, principal);
449  if (ctx)
450  {
451  if (!credentials)
452  {
453  if (ccache)
454  {
455  if (own_ccache)
456  krb5_cc_destroy(ctx, ccache);
457  else
458  krb5_cc_close(ctx, ccache);
459  }
460  if (keytab)
461  krb5_kt_close(ctx, keytab);
462 
463  krb5_free_context(ctx);
464  }
465  }
466 
467  /* If we managed to get credentials set the output */
468  if (credentials)
469  {
470  sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials);
471  sspi_SecureHandleSetUpperPointer(phCredential, (void*)KERBEROS_SSP_NAME);
472  return SEC_E_OK;
473  }
474 
475  return SEC_E_NO_CREDENTIALS;
476 #else
477  return SEC_E_UNSUPPORTED_FUNCTION;
478 #endif
479 }
480 
481 static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleW(
482  SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
483  void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
484  PTimeStamp ptsExpiry)
485 {
486  SECURITY_STATUS status = SEC_E_INSUFFICIENT_MEMORY;
487  char* principal = NULL;
488  char* package = NULL;
489 
490  if (pszPrincipal)
491  {
492  principal = ConvertWCharToUtf8Alloc(pszPrincipal, NULL);
493  if (!principal)
494  goto fail;
495  }
496  if (pszPackage)
497  {
498  package = ConvertWCharToUtf8Alloc(pszPackage, NULL);
499  if (!package)
500  goto fail;
501  }
502 
503  status =
504  kerberos_AcquireCredentialsHandleA(principal, package, fCredentialUse, pvLogonID, pAuthData,
505  pGetKeyFn, pvGetKeyArgument, phCredential, ptsExpiry);
506 
507 fail:
508  free(principal);
509  free(package);
510 
511  return status;
512 }
513 
514 #ifdef WITH_KRB5
515 static void credentials_unref(KRB_CREDENTIALS* credentials)
516 {
517  WINPR_ASSERT(credentials);
518 
519  if (InterlockedDecrement(&credentials->refCount))
520  return;
521 
522  free(credentials->kdc_url);
523 
524  if (credentials->ccache)
525  {
526  if (credentials->own_ccache)
527  krb5_cc_destroy(credentials->ctx, credentials->ccache);
528  else
529  krb5_cc_close(credentials->ctx, credentials->ccache);
530  }
531  if (credentials->keytab)
532  krb5_kt_close(credentials->ctx, credentials->keytab);
533 
534  krb5_free_context(credentials->ctx);
535  free(credentials);
536 }
537 #endif
538 
539 static SECURITY_STATUS SEC_ENTRY kerberos_FreeCredentialsHandle(PCredHandle phCredential)
540 {
541 #ifdef WITH_KRB5
542  KRB_CREDENTIALS* credentials = sspi_SecureHandleGetLowerPointer(phCredential);
543  if (!credentials)
544  return SEC_E_INVALID_HANDLE;
545 
546  credentials_unref(credentials);
547 
548  sspi_SecureHandleInvalidate(phCredential);
549  return SEC_E_OK;
550 #else
551  return SEC_E_UNSUPPORTED_FUNCTION;
552 #endif
553 }
554 
555 static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesW(PCredHandle phCredential,
556  ULONG ulAttribute,
557  void* pBuffer)
558 {
559 #ifdef WITH_KRB5
560  switch (ulAttribute)
561  {
562  case SECPKG_CRED_ATTR_NAMES:
563  return SEC_E_OK;
564  default:
565  WLog_ERR(TAG, "TODO: QueryCredentialsAttributesW, implement ulAttribute=%08" PRIx32,
566  ulAttribute);
567  return SEC_E_UNSUPPORTED_FUNCTION;
568  }
569 
570 #else
571  return SEC_E_UNSUPPORTED_FUNCTION;
572 #endif
573 }
574 
575 static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesA(PCredHandle phCredential,
576  ULONG ulAttribute,
577  void* pBuffer)
578 {
579  return kerberos_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
580 }
581 
582 #ifdef WITH_KRB5
583 
584 static BOOL kerberos_mk_tgt_token(SecBuffer* buf, int msg_type, char* sname, char* host,
585  const krb5_data* ticket)
586 {
587  WinPrAsn1Encoder* enc = NULL;
589  wStream s;
590  size_t len = 0;
591  sspi_gss_data token;
592  BOOL ret = FALSE;
593 
594  WINPR_ASSERT(buf);
595 
596  if (msg_type != KRB_TGT_REQ && msg_type != KRB_TGT_REP)
597  return FALSE;
598  if (msg_type == KRB_TGT_REP && !ticket)
599  return FALSE;
600 
601  enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
602  if (!enc)
603  return FALSE;
604 
605  /* KERB-TGT-REQUEST (SEQUENCE) */
606  if (!WinPrAsn1EncSeqContainer(enc))
607  goto cleanup;
608 
609  /* pvno [0] INTEGER */
610  if (!WinPrAsn1EncContextualInteger(enc, 0, 5))
611  goto cleanup;
612 
613  /* msg-type [1] INTEGER */
614  if (!WinPrAsn1EncContextualInteger(enc, 1, msg_type))
615  goto cleanup;
616 
617  if (msg_type == KRB_TGT_REQ && sname)
618  {
619  /* server-name [2] PrincipalName (SEQUENCE) */
620  if (!WinPrAsn1EncContextualSeqContainer(enc, 2))
621  goto cleanup;
622 
623  /* name-type [0] INTEGER */
624  if (!WinPrAsn1EncContextualInteger(enc, 0, KRB5_NT_SRV_HST))
625  goto cleanup;
626 
627  /* name-string [1] SEQUENCE OF GeneralString */
628  if (!WinPrAsn1EncContextualSeqContainer(enc, 1))
629  goto cleanup;
630 
631  if (!WinPrAsn1EncGeneralString(enc, sname))
632  goto cleanup;
633 
634  if (host && !WinPrAsn1EncGeneralString(enc, host))
635  goto cleanup;
636 
637  if (!WinPrAsn1EncEndContainer(enc) || !WinPrAsn1EncEndContainer(enc))
638  goto cleanup;
639  }
640  else if (msg_type == KRB_TGT_REP)
641  {
642  /* ticket [2] Ticket */
643  data.data = (BYTE*)ticket->data;
644  data.len = ticket->length;
645  if (!WinPrAsn1EncContextualRawContent(enc, 2, &data))
646  goto cleanup;
647  }
648 
649  if (!WinPrAsn1EncEndContainer(enc))
650  goto cleanup;
651 
652  if (!WinPrAsn1EncStreamSize(enc, &len) || len > buf->cbBuffer)
653  goto cleanup;
654 
655  Stream_StaticInit(&s, buf->pvBuffer, len);
656  if (!WinPrAsn1EncToStream(enc, &s))
657  goto cleanup;
658 
659  token.data = buf->pvBuffer;
660  token.length = (UINT)len;
661  if (sspi_gss_wrap_token(buf, &kerberos_u2u_OID,
662  msg_type == KRB_TGT_REQ ? TOK_ID_TGT_REQ : TOK_ID_TGT_REP, &token))
663  ret = TRUE;
664 
665 cleanup:
666  WinPrAsn1Encoder_Free(&enc);
667  return ret;
668 }
669 
670 static BOOL append(char* dst, size_t dstSize, const char* src)
671 {
672  const size_t dlen = strnlen(dst, dstSize);
673  const size_t slen = strlen(src);
674  if (dlen + slen >= dstSize)
675  return FALSE;
676  if (!strncat(dst, src, slen))
677  return FALSE;
678  return TRUE;
679 }
680 
681 static BOOL kerberos_rd_tgt_req_tag2(WinPrAsn1Decoder* dec, char* buf, size_t len)
682 {
683  BOOL rc = FALSE;
684  WinPrAsn1Decoder seq = { 0 };
685 
686  /* server-name [2] PrincipalName (SEQUENCE) */
687  if (!WinPrAsn1DecReadSequence(dec, &seq))
688  goto end;
689 
690  /* name-type [0] INTEGER */
691  BOOL error = FALSE;
692  WinPrAsn1_INTEGER val = 0;
693  if (!WinPrAsn1DecReadContextualInteger(&seq, 0, &error, &val))
694  goto end;
695 
696  /* name-string [1] SEQUENCE OF GeneralString */
697  if (!WinPrAsn1DecReadContextualSequence(&seq, 1, &error, dec))
698  goto end;
699 
700  WinPrAsn1_tag tag = 0;
701  BOOL first = TRUE;
702  while (WinPrAsn1DecPeekTag(dec, &tag))
703  {
704  BOOL success = FALSE;
705  char* lstr = NULL;
706  if (!WinPrAsn1DecReadGeneralString(dec, &lstr))
707  goto fail;
708 
709  if (!first)
710  {
711  if (!append(buf, len, "/"))
712  goto fail;
713  }
714  first = FALSE;
715 
716  if (!append(buf, len, lstr))
717  goto fail;
718 
719  success = TRUE;
720  fail:
721  free(lstr);
722  if (!success)
723  goto end;
724  }
725 
726  rc = TRUE;
727 end:
728  return rc;
729 }
730 
731 static BOOL kerberos_rd_tgt_req_tag3(WinPrAsn1Decoder* dec, char* buf, size_t len)
732 {
733  /* realm [3] Realm */
734  BOOL rc = FALSE;
735  WinPrAsn1_STRING str = NULL;
736  if (!WinPrAsn1DecReadGeneralString(dec, &str))
737  goto end;
738 
739  if (!append(buf, len, "@"))
740  goto end;
741  if (!append(buf, len, str))
742  goto end;
743 
744  rc = TRUE;
745 end:
746  free(str);
747  return rc;
748 }
749 
750 static BOOL kerberos_rd_tgt_req(WinPrAsn1Decoder* dec, char** target)
751 {
752  BOOL rc = FALSE;
753 
754  if (!target)
755  return FALSE;
756  *target = NULL;
757 
758  wStream s = WinPrAsn1DecGetStream(dec);
759  const size_t len = Stream_Length(&s);
760  if (len == 0)
761  return TRUE;
762 
763  WinPrAsn1Decoder dec2 = { 0 };
764  WinPrAsn1_tagId tag = 0;
765  if (WinPrAsn1DecReadContextualTag(dec, &tag, &dec2) == 0)
766  return FALSE;
767 
768  char* buf = calloc(len + 1, sizeof(char));
769  if (!buf)
770  return FALSE;
771 
772  /* We expect ASN1 context tag values 2 or 3.
773  *
774  * In case we got value 2 an (optional) context tag value 3 might follow.
775  */
776  BOOL checkForTag3 = TRUE;
777  if (tag == 2)
778  {
779  rc = kerberos_rd_tgt_req_tag2(&dec2, buf, len);
780  if (rc)
781  {
782  const size_t res = WinPrAsn1DecReadContextualTag(dec, &tag, dec);
783  if (res == 0)
784  checkForTag3 = FALSE;
785  }
786  }
787 
788  if (checkForTag3)
789  {
790  if (tag == 3)
791  rc = kerberos_rd_tgt_req_tag3(&dec2, buf, len);
792  else
793  rc = FALSE;
794  }
795 
796  if (rc)
797  *target = buf;
798  else
799  free(buf);
800  return rc;
801 }
802 
803 static BOOL kerberos_rd_tgt_rep(WinPrAsn1Decoder* dec, krb5_data* ticket)
804 {
805  if (!ticket)
806  return FALSE;
807 
808  /* ticket [2] Ticket */
809  WinPrAsn1Decoder asnTicket = { 0 };
810  WinPrAsn1_tagId tag = 0;
811  if (WinPrAsn1DecReadContextualTag(dec, &tag, &asnTicket) == 0)
812  return FALSE;
813 
814  if (tag != 2)
815  return FALSE;
816 
817  wStream s = WinPrAsn1DecGetStream(&asnTicket);
818  ticket->data = Stream_BufferAs(&s, char);
819 
820  const size_t len = Stream_Length(&s);
821  if (len > UINT32_MAX)
822  return FALSE;
823  ticket->length = (UINT32)len;
824  return TRUE;
825 }
826 
827 static BOOL kerberos_rd_tgt_token(const sspi_gss_data* token, char** target, krb5_data* ticket)
828 {
829  BOOL error = 0;
830  WinPrAsn1_INTEGER val = 0;
831 
832  WINPR_ASSERT(token);
833 
834  if (target)
835  *target = NULL;
836 
837  WinPrAsn1Decoder der = { 0 };
838  WinPrAsn1Decoder_InitMem(&der, WINPR_ASN1_DER, (BYTE*)token->data, token->length);
839 
840  /* KERB-TGT-REQUEST (SEQUENCE) */
841  WinPrAsn1Decoder seq = { 0 };
842  if (!WinPrAsn1DecReadSequence(&der, &seq))
843  return FALSE;
844 
845  /* pvno [0] INTEGER */
846  if (!WinPrAsn1DecReadContextualInteger(&seq, 0, &error, &val) || val != 5)
847  return FALSE;
848 
849  /* msg-type [1] INTEGER */
850  if (!WinPrAsn1DecReadContextualInteger(&seq, 1, &error, &val))
851  return FALSE;
852 
853  switch (val)
854  {
855  case KRB_TGT_REQ:
856  return kerberos_rd_tgt_req(&seq, target);
857  case KRB_TGT_REP:
858  return kerberos_rd_tgt_rep(&seq, ticket);
859  default:
860  break;
861  }
862  return FALSE;
863 }
864 
865 #endif /* WITH_KRB5 */
866 
867 static BOOL kerberos_hash_channel_bindings(WINPR_DIGEST_CTX* md5, SEC_CHANNEL_BINDINGS* bindings)
868 {
869  BYTE buf[4];
870 
871  winpr_Data_Write_UINT32(buf, bindings->dwInitiatorAddrType);
872  if (!winpr_Digest_Update(md5, buf, 4))
873  return FALSE;
874 
875  winpr_Data_Write_UINT32(buf, bindings->cbInitiatorLength);
876  if (!winpr_Digest_Update(md5, buf, 4))
877  return FALSE;
878 
879  if (bindings->cbInitiatorLength &&
880  !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwInitiatorOffset,
881  bindings->cbInitiatorLength))
882  return FALSE;
883 
884  winpr_Data_Write_UINT32(buf, bindings->dwAcceptorAddrType);
885  if (!winpr_Digest_Update(md5, buf, 4))
886  return FALSE;
887 
888  winpr_Data_Write_UINT32(buf, bindings->cbAcceptorLength);
889  if (!winpr_Digest_Update(md5, buf, 4))
890  return FALSE;
891 
892  if (bindings->cbAcceptorLength &&
893  !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwAcceptorOffset,
894  bindings->cbAcceptorLength))
895  return FALSE;
896 
897  winpr_Data_Write_UINT32(buf, bindings->cbApplicationDataLength);
898  if (!winpr_Digest_Update(md5, buf, 4))
899  return FALSE;
900 
901  if (bindings->cbApplicationDataLength &&
902  !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwApplicationDataOffset,
903  bindings->cbApplicationDataLength))
904  return FALSE;
905 
906  return TRUE;
907 }
908 
909 static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA(
910  PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
911  ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
912  PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry)
913 {
914 #ifdef WITH_KRB5
915  PSecBuffer input_buffer = NULL;
916  PSecBuffer output_buffer = NULL;
917  PSecBuffer bindings_buffer = NULL;
918  WINPR_DIGEST_CTX* md5 = NULL;
919  char* target = NULL;
920  char* sname = NULL;
921  char* host = NULL;
922  krb5_data input_token = { 0 };
923  krb5_data output_token = { 0 };
924  SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
925  WinPrAsn1_OID oid = { 0 };
926  uint16_t tok_id = 0;
927  krb5_ap_rep_enc_part* reply = NULL;
928  krb5_flags ap_flags = AP_OPTS_USE_SUBKEY;
929  char cksum_contents[24] = { 0 };
930  krb5_data cksum = { 0 };
931  krb5_creds in_creds = { 0 };
932  krb5_creds* creds = NULL;
933  BOOL isNewContext = FALSE;
934  KRB_CONTEXT* context = NULL;
935  KRB_CREDENTIALS* credentials = sspi_SecureHandleGetLowerPointer(phCredential);
936 
937  /* behave like windows SSPIs that don't want empty context */
938  if (phContext && !phContext->dwLower && !phContext->dwUpper)
939  return SEC_E_INVALID_HANDLE;
940 
941  context = sspi_SecureHandleGetLowerPointer(phContext);
942 
943  if (!credentials)
944  return SEC_E_NO_CREDENTIALS;
945 
946  if (pInput)
947  {
948  input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
949  bindings_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS);
950  }
951  if (pOutput)
952  output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
953 
954  if (fContextReq & ISC_REQ_MUTUAL_AUTH)
955  ap_flags |= AP_OPTS_MUTUAL_REQUIRED;
956 
957  if (fContextReq & ISC_REQ_USE_SESSION_KEY)
958  ap_flags |= AP_OPTS_USE_SESSION_KEY;
959 
960  /* Split target name into service/hostname components */
961  if (pszTargetName)
962  {
963  target = _strdup(pszTargetName);
964  if (!target)
965  {
966  status = SEC_E_INSUFFICIENT_MEMORY;
967  goto cleanup;
968  }
969  host = strchr(target, '/');
970  if (host)
971  {
972  *host++ = 0;
973  sname = target;
974  }
975  else
976  host = target;
977  if (isValidIP(host))
978  {
979  status = SEC_E_NO_CREDENTIALS;
980  goto cleanup;
981  }
982  }
983 
984  if (!context)
985  {
986  context = kerberos_ContextNew(credentials);
987  if (!context)
988  {
989  status = SEC_E_INSUFFICIENT_MEMORY;
990  goto cleanup;
991  }
992 
993  isNewContext = TRUE;
994 
995  if (host)
996  context->targetHost = _strdup(host);
997  if (!context->targetHost)
998  {
999  status = SEC_E_INSUFFICIENT_MEMORY;
1000  goto cleanup;
1001  }
1002 
1003  if (fContextReq & ISC_REQ_USE_SESSION_KEY)
1004  {
1005  context->state = KERBEROS_STATE_TGT_REQ;
1006  context->u2u = TRUE;
1007  }
1008  else
1009  context->state = KERBEROS_STATE_AP_REQ;
1010  }
1011  else
1012  {
1013  if (!input_buffer || !sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token))
1014  goto bad_token;
1015  if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) ||
1016  (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID)))
1017  goto bad_token;
1018  }
1019 
1020  /* SSPI flags are compatible with GSS flags except INTEG_FLAG */
1021  context->flags |= (fContextReq & 0x1F);
1022  if ((fContextReq & ISC_REQ_INTEGRITY) && !(fContextReq & ISC_REQ_NO_INTEGRITY))
1023  context->flags |= SSPI_GSS_C_INTEG_FLAG;
1024 
1025  switch (context->state)
1026  {
1027  case KERBEROS_STATE_TGT_REQ:
1028 
1029  if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REQ, sname, host, NULL))
1030  goto cleanup;
1031 
1032  context->state = KERBEROS_STATE_TGT_REP;
1033  status = SEC_I_CONTINUE_NEEDED;
1034  break;
1035 
1036  case KERBEROS_STATE_TGT_REP:
1037 
1038  if (tok_id != TOK_ID_TGT_REP)
1039  goto bad_token;
1040 
1041  if (!kerberos_rd_tgt_token(&input_token, NULL, &in_creds.second_ticket))
1042  goto bad_token;
1043 
1044  /* Continue to AP-REQ */
1045  /* fallthrough */
1046  WINPR_FALLTHROUGH
1047 
1048  case KERBEROS_STATE_AP_REQ:
1049 
1050  /* Set auth_context options */
1051  if (krb_log_exec(krb5_auth_con_init, credentials->ctx, &context->auth_ctx))
1052  goto cleanup;
1053  if (krb_log_exec(krb5_auth_con_setflags, credentials->ctx, context->auth_ctx,
1054  KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY))
1055  goto cleanup;
1056  if (krb_log_exec(krb5glue_auth_con_set_cksumtype, credentials->ctx, context->auth_ctx,
1057  GSS_CHECKSUM_TYPE))
1058  goto cleanup;
1059 
1060  /* Get a service ticket */
1061  if (krb_log_exec(krb5_sname_to_principal, credentials->ctx, host, sname,
1062  KRB5_NT_SRV_HST, &in_creds.server))
1063  goto cleanup;
1064 
1065  if (krb_log_exec(krb5_cc_get_principal, credentials->ctx, credentials->ccache,
1066  &in_creds.client))
1067  {
1068  status = SEC_E_WRONG_PRINCIPAL;
1069  goto cleanup;
1070  }
1071 
1072  if (krb_log_exec(krb5_get_credentials, credentials->ctx,
1073  context->u2u ? KRB5_GC_USER_USER : 0, credentials->ccache, &in_creds,
1074  &creds))
1075  {
1076  status = SEC_E_NO_CREDENTIALS;
1077  goto cleanup;
1078  }
1079 
1080  /* Write the checksum (delegation not implemented) */
1081  cksum.data = cksum_contents;
1082  cksum.length = sizeof(cksum_contents);
1083  winpr_Data_Write_UINT32(cksum_contents, 16);
1084  winpr_Data_Write_UINT32((cksum_contents + 20), context->flags);
1085 
1086  if (bindings_buffer)
1087  {
1088  SEC_CHANNEL_BINDINGS* bindings = bindings_buffer->pvBuffer;
1089 
1090  /* Sanity checks */
1091  if (bindings_buffer->cbBuffer < sizeof(SEC_CHANNEL_BINDINGS) ||
1092  (bindings->cbInitiatorLength + bindings->dwInitiatorOffset) >
1093  bindings_buffer->cbBuffer ||
1094  (bindings->cbAcceptorLength + bindings->dwAcceptorOffset) >
1095  bindings_buffer->cbBuffer ||
1096  (bindings->cbApplicationDataLength + bindings->dwApplicationDataOffset) >
1097  bindings_buffer->cbBuffer)
1098  {
1099  status = SEC_E_BAD_BINDINGS;
1100  goto cleanup;
1101  }
1102 
1103  md5 = winpr_Digest_New();
1104  if (!md5)
1105  goto cleanup;
1106 
1107  if (!winpr_Digest_Init(md5, WINPR_MD_MD5))
1108  goto cleanup;
1109 
1110  if (!kerberos_hash_channel_bindings(md5, bindings))
1111  goto cleanup;
1112 
1113  if (!winpr_Digest_Final(md5, (BYTE*)cksum_contents + 4, 16))
1114  goto cleanup;
1115  }
1116 
1117  /* Make the AP_REQ message */
1118  if (krb_log_exec(krb5_mk_req_extended, credentials->ctx, &context->auth_ctx, ap_flags,
1119  &cksum, creds, &output_token))
1120  goto cleanup;
1121 
1122  if (!sspi_gss_wrap_token(output_buffer,
1123  context->u2u ? &kerberos_u2u_OID : &kerberos_OID,
1124  TOK_ID_AP_REQ, &output_token))
1125  goto cleanup;
1126 
1127  if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
1128  {
1129  if (krb_log_exec(krb5_auth_con_getlocalseqnumber, credentials->ctx,
1130  context->auth_ctx, (INT32*)&context->local_seq))
1131  goto cleanup;
1132  context->remote_seq ^= context->local_seq;
1133  }
1134 
1135  if (krb_log_exec(krb5glue_update_keyset, credentials->ctx, context->auth_ctx, FALSE,
1136  &context->keyset))
1137  goto cleanup;
1138 
1139  context->state = KERBEROS_STATE_AP_REP;
1140 
1141  if (context->flags & SSPI_GSS_C_MUTUAL_FLAG)
1142  status = SEC_I_CONTINUE_NEEDED;
1143  else
1144  status = SEC_E_OK;
1145  break;
1146 
1147  case KERBEROS_STATE_AP_REP:
1148 
1149  if (tok_id == TOK_ID_AP_REP)
1150  {
1151  if (krb_log_exec(krb5_rd_rep, credentials->ctx, context->auth_ctx, &input_token,
1152  &reply))
1153  goto cleanup;
1154  krb5_free_ap_rep_enc_part(credentials->ctx, reply);
1155  }
1156  else if (tok_id == TOK_ID_ERROR)
1157  {
1158  krb5glue_log_error(credentials->ctx, &input_token, TAG);
1159  goto cleanup;
1160  }
1161  else
1162  goto bad_token;
1163 
1164  if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
1165  {
1166  if (krb_log_exec(krb5_auth_con_getremoteseqnumber, credentials->ctx,
1167  context->auth_ctx, (INT32*)&context->remote_seq))
1168  goto cleanup;
1169  }
1170 
1171  if (krb_log_exec(krb5glue_update_keyset, credentials->ctx, context->auth_ctx, FALSE,
1172  &context->keyset))
1173  goto cleanup;
1174 
1175  context->state = KERBEROS_STATE_FINAL;
1176 
1177  if (output_buffer)
1178  output_buffer->cbBuffer = 0;
1179  status = SEC_E_OK;
1180  break;
1181 
1182  case KERBEROS_STATE_FINAL:
1183  default:
1184  WLog_ERR(TAG, "Kerberos in invalid state!");
1185  goto cleanup;
1186  }
1187 
1188 cleanup:
1189 {
1190  /* second_ticket is not allocated */
1191  krb5_data edata = { 0 };
1192  in_creds.second_ticket = edata;
1193  krb5_free_cred_contents(credentials->ctx, &in_creds);
1194 }
1195 
1196  krb5_free_creds(credentials->ctx, creds);
1197  if (output_token.data)
1198  krb5glue_free_data_contents(credentials->ctx, &output_token);
1199 
1200  winpr_Digest_Free(md5);
1201 
1202  free(target);
1203 
1204  if (isNewContext)
1205  {
1206  switch (status)
1207  {
1208  case SEC_E_OK:
1209  case SEC_I_CONTINUE_NEEDED:
1210  sspi_SecureHandleSetLowerPointer(phNewContext, context);
1211  sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME);
1212  break;
1213  default:
1214  kerberos_ContextFree(context, TRUE);
1215  break;
1216  }
1217  }
1218 
1219  return status;
1220 
1221 bad_token:
1222  status = SEC_E_INVALID_TOKEN;
1223  goto cleanup;
1224 #else
1225  return SEC_E_UNSUPPORTED_FUNCTION;
1226 #endif /* WITH_KRB5 */
1227 }
1228 
1229 static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextW(
1230  PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
1231  ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
1232  PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry)
1233 {
1234  SECURITY_STATUS status = 0;
1235  char* target_name = NULL;
1236 
1237  if (pszTargetName)
1238  {
1239  target_name = ConvertWCharToUtf8Alloc(pszTargetName, NULL);
1240  if (!target_name)
1241  return SEC_E_INSUFFICIENT_MEMORY;
1242  }
1243 
1244  status = kerberos_InitializeSecurityContextA(phCredential, phContext, target_name, fContextReq,
1245  Reserved1, TargetDataRep, pInput, Reserved2,
1246  phNewContext, pOutput, pfContextAttr, ptsExpiry);
1247 
1248  if (target_name)
1249  free(target_name);
1250 
1251  return status;
1252 }
1253 
1254 static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext(
1255  PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq,
1256  ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr,
1257  PTimeStamp ptsExpity)
1258 {
1259 #ifdef WITH_KRB5
1260  BOOL isNewContext = FALSE;
1261  PSecBuffer input_buffer = NULL;
1262  PSecBuffer output_buffer = NULL;
1263  WinPrAsn1_OID oid = { 0 };
1264  uint16_t tok_id = 0;
1265  krb5_data input_token = { 0 };
1266  krb5_data output_token = { 0 };
1267  SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
1268  krb5_flags ap_flags = 0;
1269  krb5glue_authenticator authenticator = NULL;
1270  char* target = NULL;
1271  char* sname = NULL;
1272  char* realm = NULL;
1273  krb5_kt_cursor cur = { 0 };
1274  krb5_keytab_entry entry = { 0 };
1275  krb5_principal principal = NULL;
1276  krb5_creds creds = { 0 };
1277 
1278  /* behave like windows SSPIs that don't want empty context */
1279  if (phContext && !phContext->dwLower && !phContext->dwUpper)
1280  return SEC_E_INVALID_HANDLE;
1281 
1282  KRB_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext);
1283  KRB_CREDENTIALS* credentials = sspi_SecureHandleGetLowerPointer(phCredential);
1284 
1285  if (pInput)
1286  input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
1287  if (pOutput)
1288  output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
1289 
1290  if (!input_buffer)
1291  return SEC_E_INVALID_TOKEN;
1292 
1293  if (!sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token))
1294  return SEC_E_INVALID_TOKEN;
1295 
1296  if (!context)
1297  {
1298  isNewContext = TRUE;
1299  context = kerberos_ContextNew(credentials);
1300  context->acceptor = TRUE;
1301 
1302  if (sspi_gss_oid_compare(&oid, &kerberos_u2u_OID))
1303  {
1304  context->u2u = TRUE;
1305  context->state = KERBEROS_STATE_TGT_REQ;
1306  }
1307  else if (sspi_gss_oid_compare(&oid, &kerberos_OID))
1308  context->state = KERBEROS_STATE_AP_REQ;
1309  else
1310  goto bad_token;
1311  }
1312  else
1313  {
1314  if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) ||
1315  (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID)))
1316  goto bad_token;
1317  }
1318 
1319  if (context->state == KERBEROS_STATE_TGT_REQ && tok_id == TOK_ID_TGT_REQ)
1320  {
1321  if (!kerberos_rd_tgt_token(&input_token, &target, NULL))
1322  goto bad_token;
1323 
1324  if (target)
1325  {
1326  if (*target != 0 && *target != '@')
1327  sname = target;
1328  realm = strchr(target, '@');
1329  if (realm)
1330  realm++;
1331  }
1332 
1333  if (krb_log_exec(krb5_parse_name_flags, credentials->ctx, sname ? sname : "",
1334  KRB5_PRINCIPAL_PARSE_NO_REALM, &principal))
1335  goto cleanup;
1336 
1337  if (realm)
1338  {
1339  if (krb_log_exec(krb5glue_set_principal_realm, credentials->ctx, principal, realm))
1340  goto cleanup;
1341  }
1342 
1343  if (krb_log_exec(krb5_kt_start_seq_get, credentials->ctx, credentials->keytab, &cur))
1344  goto cleanup;
1345 
1346  do
1347  {
1348  krb5_error_code rv = krb_log_exec(krb5_kt_next_entry, credentials->ctx,
1349  credentials->keytab, &entry, &cur);
1350  if (rv == KRB5_KT_END)
1351  break;
1352  if (rv != 0)
1353  goto cleanup;
1354 
1355  if ((!sname || krb_log_exec_bool(krb5_principal_compare_any_realm, credentials->ctx,
1356  principal, entry.principal)) &&
1357  (!realm || krb_log_exec_bool(krb5_realm_compare, credentials->ctx, principal,
1358  entry.principal)))
1359  break;
1360  if (krb_log_exec(krb5glue_free_keytab_entry_contents, credentials->ctx, &entry))
1361  goto cleanup;
1362  } while (1);
1363 
1364  if (krb_log_exec(krb5_kt_end_seq_get, credentials->ctx, credentials->keytab, &cur))
1365  goto cleanup;
1366 
1367  if (!entry.principal)
1368  goto cleanup;
1369 
1370  /* Get the TGT */
1371  if (krb_log_exec(krb5_get_init_creds_keytab, credentials->ctx, &creds, entry.principal,
1372  credentials->keytab, 0, NULL, NULL))
1373  goto cleanup;
1374 
1375  if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REP, NULL, NULL, &creds.ticket))
1376  goto cleanup;
1377 
1378  if (krb_log_exec(krb5_auth_con_init, credentials->ctx, &context->auth_ctx))
1379  goto cleanup;
1380 
1381  if (krb_log_exec(krb5glue_auth_con_setuseruserkey, credentials->ctx, context->auth_ctx,
1382  &krb5glue_creds_getkey(creds)))
1383  goto cleanup;
1384 
1385  context->state = KERBEROS_STATE_AP_REQ;
1386  }
1387  else if (context->state == KERBEROS_STATE_AP_REQ && tok_id == TOK_ID_AP_REQ)
1388  {
1389  if (krb_log_exec(krb5_rd_req, credentials->ctx, &context->auth_ctx, &input_token, NULL,
1390  credentials->keytab, &ap_flags, NULL))
1391  goto cleanup;
1392 
1393  if (krb_log_exec(krb5_auth_con_setflags, credentials->ctx, context->auth_ctx,
1394  KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY))
1395  goto cleanup;
1396 
1397  /* Retrieve and validate the checksum */
1398  if (krb_log_exec(krb5_auth_con_getauthenticator, credentials->ctx, context->auth_ctx,
1399  &authenticator))
1400  goto cleanup;
1401  if (!krb5glue_authenticator_validate_chksum(authenticator, GSS_CHECKSUM_TYPE,
1402  &context->flags))
1403  goto bad_token;
1404 
1405  if ((ap_flags & AP_OPTS_MUTUAL_REQUIRED) && (context->flags & SSPI_GSS_C_MUTUAL_FLAG))
1406  {
1407  if (!output_buffer)
1408  goto bad_token;
1409  if (krb_log_exec(krb5_mk_rep, credentials->ctx, context->auth_ctx, &output_token))
1410  goto cleanup;
1411  if (!sspi_gss_wrap_token(output_buffer,
1412  context->u2u ? &kerberos_u2u_OID : &kerberos_OID,
1413  TOK_ID_AP_REP, &output_token))
1414  goto cleanup;
1415  }
1416  else
1417  {
1418  if (output_buffer)
1419  output_buffer->cbBuffer = 0;
1420  }
1421 
1422  *pfContextAttr = (context->flags & 0x1F);
1423  if (context->flags & SSPI_GSS_C_INTEG_FLAG)
1424  *pfContextAttr |= ASC_RET_INTEGRITY;
1425 
1426  if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
1427  {
1428  if (krb_log_exec(krb5_auth_con_getlocalseqnumber, credentials->ctx, context->auth_ctx,
1429  (INT32*)&context->local_seq))
1430  goto cleanup;
1431  if (krb_log_exec(krb5_auth_con_getremoteseqnumber, credentials->ctx, context->auth_ctx,
1432  (INT32*)&context->remote_seq))
1433  goto cleanup;
1434  }
1435 
1436  if (krb_log_exec(krb5glue_update_keyset, credentials->ctx, context->auth_ctx, TRUE,
1437  &context->keyset))
1438  goto cleanup;
1439 
1440  context->state = KERBEROS_STATE_FINAL;
1441  }
1442  else
1443  goto bad_token;
1444 
1445  /* On first call allocate new context */
1446  if (context->state == KERBEROS_STATE_FINAL)
1447  status = SEC_E_OK;
1448  else
1449  status = SEC_I_CONTINUE_NEEDED;
1450 
1451 cleanup:
1452  free(target);
1453  if (output_token.data)
1454  krb5glue_free_data_contents(credentials->ctx, &output_token);
1455  if (entry.principal)
1456  krb5glue_free_keytab_entry_contents(credentials->ctx, &entry);
1457 
1458  if (isNewContext)
1459  {
1460  switch (status)
1461  {
1462  case SEC_E_OK:
1463  case SEC_I_CONTINUE_NEEDED:
1464  sspi_SecureHandleSetLowerPointer(phNewContext, context);
1465  sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME);
1466  break;
1467  default:
1468  kerberos_ContextFree(context, TRUE);
1469  break;
1470  }
1471  }
1472 
1473  return status;
1474 
1475 bad_token:
1476  status = SEC_E_INVALID_TOKEN;
1477  goto cleanup;
1478 #else
1479  return SEC_E_UNSUPPORTED_FUNCTION;
1480 #endif /* WITH_KRB5 */
1481 }
1482 
1483 #ifdef WITH_KRB5
1484 static KRB_CONTEXT* get_context(PCtxtHandle phContext)
1485 {
1486  if (!phContext)
1487  return NULL;
1488 
1489  TCHAR* name = sspi_SecureHandleGetUpperPointer(phContext);
1490  if (!name)
1491  return NULL;
1492 
1493  if (_tcsncmp(KERBEROS_SSP_NAME, name, ARRAYSIZE(KERBEROS_SSP_NAME)) != 0)
1494  return NULL;
1495  return sspi_SecureHandleGetLowerPointer(phContext);
1496 }
1497 
1498 static BOOL copy_krb5_data(krb5_data* data, PUCHAR* ptr, ULONG* psize)
1499 {
1500  WINPR_ASSERT(data);
1501  WINPR_ASSERT(ptr);
1502  WINPR_ASSERT(psize);
1503 
1504  *ptr = (PUCHAR)malloc(data->length);
1505  if (!*ptr)
1506  return FALSE;
1507 
1508  *psize = data->length;
1509  memcpy(*ptr, data->data, data->length);
1510  return TRUE;
1511 }
1512 #endif
1513 
1514 static SECURITY_STATUS SEC_ENTRY kerberos_DeleteSecurityContext(PCtxtHandle phContext)
1515 {
1516 #ifdef WITH_KRB5
1517  KRB_CONTEXT* context = get_context(phContext);
1518  if (!context)
1519  return SEC_E_INVALID_HANDLE;
1520 
1521  kerberos_ContextFree(context, TRUE);
1522 
1523  return SEC_E_OK;
1524 #else
1525  return SEC_E_UNSUPPORTED_FUNCTION;
1526 #endif
1527 }
1528 
1529 #ifdef WITH_KRB5
1530 
1531 static SECURITY_STATUS krb5_error_to_SECURITY_STATUS(krb5_error_code code)
1532 {
1533  switch (code)
1534  {
1535  case 0:
1536  return SEC_E_OK;
1537  default:
1538  return SEC_E_INTERNAL_ERROR;
1539  }
1540 }
1541 
1542 static SECURITY_STATUS kerberos_ATTR_SIZES(KRB_CONTEXT* context, KRB_CREDENTIALS* credentials,
1543  SecPkgContext_Sizes* ContextSizes)
1544 {
1545  UINT header = 0;
1546  UINT pad = 0;
1547  UINT trailer = 0;
1548  krb5glue_key key = NULL;
1549 
1550  WINPR_ASSERT(context);
1551  WINPR_ASSERT(context->auth_ctx);
1552 
1553  /* The MaxTokenSize by default is 12,000 bytes. This has been the default value
1554  * since Windows 2000 SP2 and still remains in Windows 7 and Windows 2008 R2.
1555  * For Windows Server 2012, the default value of the MaxTokenSize registry
1556  * entry is 48,000 bytes.*/
1557  ContextSizes->cbMaxToken = KERBEROS_SecPkgInfoA.cbMaxToken;
1558  ContextSizes->cbMaxSignature = 0;
1559  ContextSizes->cbBlockSize = 1;
1560  ContextSizes->cbSecurityTrailer = 0;
1561 
1562  key = get_key(&context->keyset);
1563 
1564  if (context->flags & SSPI_GSS_C_CONF_FLAG)
1565  {
1566  krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key,
1567  KRB5_CRYPTO_TYPE_HEADER, &header);
1568  if (rv)
1569  return krb5_error_to_SECURITY_STATUS(rv);
1570 
1571  rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key, KRB5_CRYPTO_TYPE_PADDING,
1572  &pad);
1573  if (rv)
1574  return krb5_error_to_SECURITY_STATUS(rv);
1575 
1576  rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key, KRB5_CRYPTO_TYPE_TRAILER,
1577  &trailer);
1578  if (rv)
1579  return krb5_error_to_SECURITY_STATUS(rv);
1580 
1581  /* GSS header (= 16 bytes) + encrypted header = 32 bytes */
1582  ContextSizes->cbSecurityTrailer = header + pad + trailer + 32;
1583  }
1584 
1585  if (context->flags & SSPI_GSS_C_INTEG_FLAG)
1586  {
1587  krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key,
1588  KRB5_CRYPTO_TYPE_CHECKSUM, &ContextSizes->cbMaxSignature);
1589  if (rv)
1590  return krb5_error_to_SECURITY_STATUS(rv);
1591 
1592  ContextSizes->cbMaxSignature += 16;
1593  }
1594 
1595  return SEC_E_OK;
1596 }
1597 
1598 static SECURITY_STATUS kerberos_ATTR_TICKET_LOGON(KRB_CONTEXT* context,
1599  KRB_CREDENTIALS* credentials,
1600  KERB_TICKET_LOGON* ticketLogon)
1601 {
1602  krb5_creds matchCred = { 0 };
1603  krb5_auth_context authContext = NULL;
1604  krb5_flags getCredsFlags = KRB5_GC_CACHED;
1605  BOOL firstRun = TRUE;
1606  krb5_creds* hostCred = NULL;
1607  SECURITY_STATUS ret = SEC_E_INSUFFICIENT_MEMORY;
1608  int rv = krb_log_exec(krb5_sname_to_principal, credentials->ctx, context->targetHost, "HOST",
1609  KRB5_NT_SRV_HST, &matchCred.server);
1610  if (rv)
1611  goto out;
1612 
1613  rv = krb_log_exec(krb5_cc_get_principal, credentials->ctx, credentials->ccache,
1614  &matchCred.client);
1615  if (rv)
1616  goto out;
1617 
1618  /* try from the cache first, and then do a new request */
1619 again:
1620  rv = krb_log_exec(krb5_get_credentials, credentials->ctx, getCredsFlags, credentials->ccache,
1621  &matchCred, &hostCred);
1622  switch (rv)
1623  {
1624  case 0:
1625  break;
1626  case KRB5_CC_NOTFOUND:
1627  getCredsFlags = 0;
1628  if (firstRun)
1629  {
1630  firstRun = FALSE;
1631  goto again;
1632  }
1633  WINPR_FALLTHROUGH
1634  default:
1635  WLog_ERR(TAG, "krb5_get_credentials(hostCreds), rv=%d", rv);
1636  goto out;
1637  }
1638 
1639  if (krb_log_exec(krb5_auth_con_init, credentials->ctx, &authContext))
1640  goto out;
1641 
1642  krb5_data derOut = { 0 };
1643  if (krb_log_exec(krb5_fwd_tgt_creds, credentials->ctx, authContext, context->targetHost,
1644  matchCred.client, matchCred.server, credentials->ccache, 1, &derOut))
1645  {
1646  ret = SEC_E_LOGON_DENIED;
1647  goto out;
1648  }
1649 
1650  ticketLogon->MessageType = KerbTicketLogon;
1651  ticketLogon->Flags = KERB_LOGON_FLAG_REDIRECTED;
1652 
1653  if (!copy_krb5_data(&hostCred->ticket, &ticketLogon->ServiceTicket,
1654  &ticketLogon->ServiceTicketLength))
1655  {
1656  krb5_free_data(credentials->ctx, &derOut);
1657  goto out;
1658  }
1659 
1660  ticketLogon->TicketGrantingTicketLength = derOut.length;
1661  ticketLogon->TicketGrantingTicket = (PUCHAR)derOut.data;
1662 
1663  ret = SEC_E_OK;
1664 out:
1665  krb5_auth_con_free(credentials->ctx, authContext);
1666  krb5_free_creds(credentials->ctx, hostCred);
1667  krb5_free_cred_contents(credentials->ctx, &matchCred);
1668  return ret;
1669 }
1670 
1671 #endif /* WITH_KRB5 */
1672 
1673 static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesA(PCtxtHandle phContext,
1674  ULONG ulAttribute, void* pBuffer)
1675 {
1676  if (!phContext)
1677  return SEC_E_INVALID_HANDLE;
1678 
1679  if (!pBuffer)
1680  return SEC_E_INVALID_PARAMETER;
1681 
1682 #ifdef WITH_KRB5
1683  KRB_CONTEXT* context = get_context(phContext);
1684  if (!context)
1685  return SEC_E_INVALID_PARAMETER;
1686 
1687  KRB_CREDENTIALS* credentials = context->credentials;
1688 
1689  switch (ulAttribute)
1690  {
1691  case SECPKG_ATTR_SIZES:
1692  return kerberos_ATTR_SIZES(context, credentials, (SecPkgContext_Sizes*)pBuffer);
1693 
1694  case SECPKG_CRED_ATTR_TICKET_LOGON:
1695  return kerberos_ATTR_TICKET_LOGON(context, credentials, (KERB_TICKET_LOGON*)pBuffer);
1696 
1697  default:
1698  WLog_ERR(TAG, "TODO: QueryContextAttributes implement ulAttribute=0x%08" PRIx32,
1699  ulAttribute);
1700  return SEC_E_UNSUPPORTED_FUNCTION;
1701  }
1702 #else
1703  return SEC_E_UNSUPPORTED_FUNCTION;
1704 #endif
1705 }
1706 
1707 static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesW(PCtxtHandle phContext,
1708  ULONG ulAttribute, void* pBuffer)
1709 {
1710  return kerberos_QueryContextAttributesA(phContext, ulAttribute, pBuffer);
1711 }
1712 
1713 static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesW(PCtxtHandle phContext,
1714  ULONG ulAttribute, void* pBuffer,
1715  ULONG cbBuffer)
1716 {
1717  return SEC_E_UNSUPPORTED_FUNCTION;
1718 }
1719 
1720 static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesA(PCtxtHandle phContext,
1721  ULONG ulAttribute, void* pBuffer,
1722  ULONG cbBuffer)
1723 {
1724  return SEC_E_UNSUPPORTED_FUNCTION;
1725 }
1726 
1727 static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesX(PCredHandle phCredential,
1728  ULONG ulAttribute,
1729  void* pBuffer, ULONG cbBuffer,
1730  BOOL unicode)
1731 {
1732 #ifdef WITH_KRB5
1733  KRB_CREDENTIALS* credentials = NULL;
1734 
1735  if (!phCredential)
1736  return SEC_E_INVALID_HANDLE;
1737 
1738  credentials = sspi_SecureHandleGetLowerPointer(phCredential);
1739 
1740  if (!credentials)
1741  return SEC_E_INVALID_HANDLE;
1742 
1743  if (!pBuffer)
1744  return SEC_E_INSUFFICIENT_MEMORY;
1745 
1746  switch (ulAttribute)
1747  {
1748  case SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS:
1749  {
1750  SecPkgCredentials_KdcProxySettingsW* kdc_settings = pBuffer;
1751 
1752  /* Sanity checks */
1753  if (cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) ||
1754  kdc_settings->Version != KDC_PROXY_SETTINGS_V1 ||
1755  kdc_settings->ProxyServerOffset < sizeof(SecPkgCredentials_KdcProxySettingsW) ||
1756  cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) +
1757  kdc_settings->ProxyServerOffset + kdc_settings->ProxyServerLength)
1758  return SEC_E_INVALID_TOKEN;
1759 
1760  if (credentials->kdc_url)
1761  {
1762  free(credentials->kdc_url);
1763  credentials->kdc_url = NULL;
1764  }
1765 
1766  if (kdc_settings->ProxyServerLength > 0)
1767  {
1768  WCHAR* proxy = (WCHAR*)((BYTE*)pBuffer + kdc_settings->ProxyServerOffset);
1769 
1770  credentials->kdc_url = ConvertWCharNToUtf8Alloc(
1771  proxy, kdc_settings->ProxyServerLength / sizeof(WCHAR), NULL);
1772  if (!credentials->kdc_url)
1773  return SEC_E_INSUFFICIENT_MEMORY;
1774  }
1775 
1776  return SEC_E_OK;
1777  }
1778  case SECPKG_CRED_ATTR_NAMES:
1779  case SECPKG_ATTR_SUPPORTED_ALGS:
1780  default:
1781  WLog_ERR(TAG, "TODO: SetCredentialsAttributesX implement ulAttribute=0x%08" PRIx32,
1782  ulAttribute);
1783  return SEC_E_UNSUPPORTED_FUNCTION;
1784  }
1785 
1786 #else
1787  return SEC_E_UNSUPPORTED_FUNCTION;
1788 #endif
1789 }
1790 
1791 static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesW(PCredHandle phCredential,
1792  ULONG ulAttribute,
1793  void* pBuffer, ULONG cbBuffer)
1794 {
1795  return kerberos_SetCredentialsAttributesX(phCredential, ulAttribute, pBuffer, cbBuffer, TRUE);
1796 }
1797 
1798 static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesA(PCredHandle phCredential,
1799  ULONG ulAttribute,
1800  void* pBuffer, ULONG cbBuffer)
1801 {
1802  return kerberos_SetCredentialsAttributesX(phCredential, ulAttribute, pBuffer, cbBuffer, FALSE);
1803 }
1804 
1805 static SECURITY_STATUS SEC_ENTRY kerberos_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
1806  PSecBufferDesc pMessage,
1807  ULONG MessageSeqNo)
1808 {
1809 #ifdef WITH_KRB5
1810  KRB_CONTEXT* context = get_context(phContext);
1811  PSecBuffer sig_buffer = NULL;
1812  PSecBuffer data_buffer = NULL;
1813  char* header = NULL;
1814  BYTE flags = 0;
1815  krb5glue_key key = NULL;
1816  krb5_keyusage usage = 0;
1817  krb5_crypto_iov encrypt_iov[] = { { KRB5_CRYPTO_TYPE_HEADER, { 0 } },
1818  { KRB5_CRYPTO_TYPE_DATA, { 0 } },
1819  { KRB5_CRYPTO_TYPE_DATA, { 0 } },
1820  { KRB5_CRYPTO_TYPE_PADDING, { 0 } },
1821  { KRB5_CRYPTO_TYPE_TRAILER, { 0 } } };
1822 
1823  if (!context)
1824  return SEC_E_INVALID_HANDLE;
1825 
1826  if (!(context->flags & SSPI_GSS_C_CONF_FLAG))
1827  return SEC_E_UNSUPPORTED_FUNCTION;
1828 
1829  KRB_CREDENTIALS* creds = context->credentials;
1830 
1831  sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
1832  data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
1833 
1834  if (!sig_buffer || !data_buffer)
1835  return SEC_E_INVALID_TOKEN;
1836 
1837  if (fQOP)
1838  return SEC_E_QOP_NOT_SUPPORTED;
1839 
1840  flags |= context->acceptor ? FLAG_SENDER_IS_ACCEPTOR : 0;
1841  flags |= FLAG_WRAP_CONFIDENTIAL;
1842 
1843  key = get_key(&context->keyset);
1844  if (!key)
1845  return SEC_E_INTERNAL_ERROR;
1846 
1847  flags |= context->keyset.acceptor_key == key ? FLAG_ACCEPTOR_SUBKEY : 0;
1848 
1849  usage = context->acceptor ? KG_USAGE_ACCEPTOR_SEAL : KG_USAGE_INITIATOR_SEAL;
1850 
1851  /* Set the lengths of the data (plaintext + header) */
1852  encrypt_iov[1].data.length = data_buffer->cbBuffer;
1853  encrypt_iov[2].data.length = 16;
1854 
1855  /* Get the lengths of the header, trailer, and padding and ensure sig_buffer is large enough */
1856  if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, encrypt_iov,
1857  ARRAYSIZE(encrypt_iov)))
1858  return SEC_E_INTERNAL_ERROR;
1859  if (sig_buffer->cbBuffer <
1860  encrypt_iov[0].data.length + encrypt_iov[3].data.length + encrypt_iov[4].data.length + 32)
1861  return SEC_E_INSUFFICIENT_MEMORY;
1862 
1863  /* Set up the iov array in sig_buffer */
1864  header = sig_buffer->pvBuffer;
1865  encrypt_iov[2].data.data = header + 16;
1866  encrypt_iov[3].data.data = encrypt_iov[2].data.data + encrypt_iov[2].data.length;
1867  encrypt_iov[4].data.data = encrypt_iov[3].data.data + encrypt_iov[3].data.length;
1868  encrypt_iov[0].data.data = encrypt_iov[4].data.data + encrypt_iov[4].data.length;
1869  encrypt_iov[1].data.data = data_buffer->pvBuffer;
1870 
1871  /* Write the GSS header with 0 in RRC */
1872  winpr_Data_Write_UINT16_BE(header, TOK_ID_WRAP);
1873  header[2] = WINPR_ASSERTING_INT_CAST(char, flags);
1874  header[3] = (char)0xFF;
1875  winpr_Data_Write_UINT32(header + 4, 0);
1876  winpr_Data_Write_UINT64_BE(header + 8, (context->local_seq + MessageSeqNo));
1877 
1878  /* Copy header to be encrypted */
1879  CopyMemory(encrypt_iov[2].data.data, header, 16);
1880 
1881  /* Set the correct RRC */
1882  const size_t len = 16 + encrypt_iov[3].data.length + encrypt_iov[4].data.length;
1883  winpr_Data_Write_UINT16_BE(header + 6, WINPR_ASSERTING_INT_CAST(UINT16, len));
1884 
1885  if (krb_log_exec(krb5glue_encrypt_iov, creds->ctx, key, usage, encrypt_iov,
1886  ARRAYSIZE(encrypt_iov)))
1887  return SEC_E_INTERNAL_ERROR;
1888 
1889  return SEC_E_OK;
1890 #else
1891  return SEC_E_UNSUPPORTED_FUNCTION;
1892 #endif
1893 }
1894 
1895 static SECURITY_STATUS SEC_ENTRY kerberos_DecryptMessage(PCtxtHandle phContext,
1896  PSecBufferDesc pMessage,
1897  ULONG MessageSeqNo, ULONG* pfQOP)
1898 {
1899 #ifdef WITH_KRB5
1900  KRB_CONTEXT* context = get_context(phContext);
1901  PSecBuffer sig_buffer = NULL;
1902  PSecBuffer data_buffer = NULL;
1903  krb5glue_key key = NULL;
1904  krb5_keyusage usage = 0;
1905  uint16_t tok_id = 0;
1906  BYTE flags = 0;
1907  uint16_t ec = 0;
1908  uint16_t rrc = 0;
1909  uint64_t seq_no = 0;
1910  krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_HEADER, { 0 } },
1911  { KRB5_CRYPTO_TYPE_DATA, { 0 } },
1912  { KRB5_CRYPTO_TYPE_DATA, { 0 } },
1913  { KRB5_CRYPTO_TYPE_PADDING, { 0 } },
1914  { KRB5_CRYPTO_TYPE_TRAILER, { 0 } } };
1915 
1916  if (!context)
1917  return SEC_E_INVALID_HANDLE;
1918 
1919  if (!(context->flags & SSPI_GSS_C_CONF_FLAG))
1920  return SEC_E_UNSUPPORTED_FUNCTION;
1921 
1922  KRB_CREDENTIALS* creds = context->credentials;
1923 
1924  sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
1925  data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
1926 
1927  if (!sig_buffer || !data_buffer || sig_buffer->cbBuffer < 16)
1928  return SEC_E_INVALID_TOKEN;
1929 
1930  /* Read in header information */
1931  BYTE* header = sig_buffer->pvBuffer;
1932  tok_id = winpr_Data_Get_UINT16_BE(header);
1933  flags = header[2];
1934  ec = winpr_Data_Get_UINT16_BE(&header[4]);
1935  rrc = winpr_Data_Get_UINT16_BE(&header[6]);
1936  seq_no = winpr_Data_Get_UINT64_BE(&header[8]);
1937 
1938  /* Check that the header is valid */
1939  if ((tok_id != TOK_ID_WRAP) || (header[3] != 0xFF))
1940  return SEC_E_INVALID_TOKEN;
1941 
1942  if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor)
1943  return SEC_E_INVALID_TOKEN;
1944 
1945  if ((context->flags & ISC_REQ_SEQUENCE_DETECT) &&
1946  (seq_no != context->remote_seq + MessageSeqNo))
1947  return SEC_E_OUT_OF_SEQUENCE;
1948 
1949  if (!(flags & FLAG_WRAP_CONFIDENTIAL))
1950  return SEC_E_INVALID_TOKEN;
1951 
1952  /* We don't expect a trailer buffer; the encrypted header must be rotated */
1953  if (rrc < 16)
1954  return SEC_E_INVALID_TOKEN;
1955 
1956  /* Find the proper key and key usage */
1957  key = get_key(&context->keyset);
1958  if (!key || ((flags & FLAG_ACCEPTOR_SUBKEY) && (context->keyset.acceptor_key != key)))
1959  return SEC_E_INTERNAL_ERROR;
1960  usage = context->acceptor ? KG_USAGE_INITIATOR_SEAL : KG_USAGE_ACCEPTOR_SEAL;
1961 
1962  /* Fill in the lengths of the iov array */
1963  iov[1].data.length = data_buffer->cbBuffer;
1964  iov[2].data.length = 16;
1965  if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov)))
1966  return SEC_E_INTERNAL_ERROR;
1967 
1968  /* We don't expect a trailer buffer; everything must be in sig_buffer */
1969  if (rrc != 16 + iov[3].data.length + iov[4].data.length)
1970  return SEC_E_INVALID_TOKEN;
1971  if (sig_buffer->cbBuffer != 16 + rrc + iov[0].data.length)
1972  return SEC_E_INVALID_TOKEN;
1973 
1974  /* Locate the parts of the message */
1975  iov[0].data.data = (char*)&header[16 + rrc + ec];
1976  iov[1].data.data = data_buffer->pvBuffer;
1977  iov[2].data.data = (char*)&header[16 + ec];
1978  char* data2 = iov[2].data.data;
1979  iov[3].data.data = &data2[iov[2].data.length];
1980 
1981  char* data3 = iov[3].data.data;
1982  iov[4].data.data = &data2[iov[3].data.length];
1983  iov[4].data.data = &data3[iov[3].data.length];
1984 
1985  if (krb_log_exec(krb5glue_decrypt_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov)))
1986  return SEC_E_INTERNAL_ERROR;
1987 
1988  /* Validate the encrypted header */
1989  winpr_Data_Write_UINT16_BE(iov[2].data.data + 4, ec);
1990  winpr_Data_Write_UINT16_BE(iov[2].data.data + 6, rrc);
1991  if (memcmp(iov[2].data.data, header, 16) != 0)
1992  return SEC_E_MESSAGE_ALTERED;
1993 
1994  *pfQOP = 0;
1995 
1996  return SEC_E_OK;
1997 #else
1998  return SEC_E_UNSUPPORTED_FUNCTION;
1999 #endif
2000 }
2001 
2002 static SECURITY_STATUS SEC_ENTRY kerberos_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
2003  PSecBufferDesc pMessage, ULONG MessageSeqNo)
2004 {
2005 #ifdef WITH_KRB5
2006  KRB_CONTEXT* context = get_context(phContext);
2007  PSecBuffer sig_buffer = NULL;
2008  PSecBuffer data_buffer = NULL;
2009  krb5glue_key key = NULL;
2010  krb5_keyusage usage = 0;
2011  BYTE flags = 0;
2012  krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_DATA, { 0 } },
2013  { KRB5_CRYPTO_TYPE_DATA, { 0 } },
2014  { KRB5_CRYPTO_TYPE_CHECKSUM, { 0 } } };
2015 
2016  if (!context)
2017  return SEC_E_INVALID_HANDLE;
2018 
2019  if (!(context->flags & SSPI_GSS_C_INTEG_FLAG))
2020  return SEC_E_UNSUPPORTED_FUNCTION;
2021 
2022  KRB_CREDENTIALS* creds = context->credentials;
2023 
2024  sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
2025  data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
2026 
2027  if (!sig_buffer || !data_buffer)
2028  return SEC_E_INVALID_TOKEN;
2029 
2030  flags |= context->acceptor ? FLAG_SENDER_IS_ACCEPTOR : 0;
2031 
2032  key = get_key(&context->keyset);
2033  if (!key)
2034  return SEC_E_INTERNAL_ERROR;
2035  usage = context->acceptor ? KG_USAGE_ACCEPTOR_SIGN : KG_USAGE_INITIATOR_SIGN;
2036 
2037  flags |= context->keyset.acceptor_key == key ? FLAG_ACCEPTOR_SUBKEY : 0;
2038 
2039  /* Fill in the lengths of the iov array */
2040  iov[0].data.length = data_buffer->cbBuffer;
2041  iov[1].data.length = 16;
2042  if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov)))
2043  return SEC_E_INTERNAL_ERROR;
2044 
2045  /* Ensure the buffer is big enough */
2046  if (sig_buffer->cbBuffer < iov[2].data.length + 16)
2047  return SEC_E_INSUFFICIENT_MEMORY;
2048 
2049  /* Write the header */
2050  char* header = sig_buffer->pvBuffer;
2051  winpr_Data_Write_UINT16_BE(header, TOK_ID_MIC);
2052  header[2] = WINPR_ASSERTING_INT_CAST(char, flags);
2053  memset(header + 3, 0xFF, 5);
2054  winpr_Data_Write_UINT64_BE(header + 8, (context->local_seq + MessageSeqNo));
2055 
2056  /* Set up the iov array */
2057  iov[0].data.data = data_buffer->pvBuffer;
2058  iov[1].data.data = header;
2059  iov[2].data.data = header + 16;
2060 
2061  if (krb_log_exec(krb5glue_make_checksum_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov)))
2062  return SEC_E_INTERNAL_ERROR;
2063 
2064  sig_buffer->cbBuffer = iov[2].data.length + 16;
2065 
2066  return SEC_E_OK;
2067 #else
2068  return SEC_E_UNSUPPORTED_FUNCTION;
2069 #endif
2070 }
2071 
2072 static SECURITY_STATUS SEC_ENTRY kerberos_VerifySignature(PCtxtHandle phContext,
2073  PSecBufferDesc pMessage,
2074  ULONG MessageSeqNo, ULONG* pfQOP)
2075 {
2076 #ifdef WITH_KRB5
2077  PSecBuffer sig_buffer = NULL;
2078  PSecBuffer data_buffer = NULL;
2079  krb5glue_key key = NULL;
2080  krb5_keyusage usage = 0;
2081  BYTE flags = 0;
2082  uint16_t tok_id = 0;
2083  uint64_t seq_no = 0;
2084  krb5_boolean is_valid = 0;
2085  krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_DATA, { 0 } },
2086  { KRB5_CRYPTO_TYPE_DATA, { 0 } },
2087  { KRB5_CRYPTO_TYPE_CHECKSUM, { 0 } } };
2088  BYTE cmp_filler[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
2089 
2090  KRB_CONTEXT* context = get_context(phContext);
2091  if (!context)
2092  return SEC_E_INVALID_HANDLE;
2093 
2094  if (!(context->flags & SSPI_GSS_C_INTEG_FLAG))
2095  return SEC_E_UNSUPPORTED_FUNCTION;
2096 
2097  sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
2098  data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
2099 
2100  if (!sig_buffer || !data_buffer || sig_buffer->cbBuffer < 16)
2101  return SEC_E_INVALID_TOKEN;
2102 
2103  /* Read in header info */
2104  BYTE* header = sig_buffer->pvBuffer;
2105  tok_id = winpr_Data_Get_UINT16_BE(header);
2106  flags = header[2];
2107  seq_no = winpr_Data_Get_UINT64_BE((header + 8));
2108 
2109  /* Validate header */
2110  if (tok_id != TOK_ID_MIC)
2111  return SEC_E_INVALID_TOKEN;
2112 
2113  if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor || flags & FLAG_WRAP_CONFIDENTIAL)
2114  return SEC_E_INVALID_TOKEN;
2115 
2116  if (memcmp(header + 3, cmp_filler, sizeof(cmp_filler)) != 0)
2117  return SEC_E_INVALID_TOKEN;
2118 
2119  if (context->flags & ISC_REQ_SEQUENCE_DETECT && seq_no != context->remote_seq + MessageSeqNo)
2120  return SEC_E_OUT_OF_SEQUENCE;
2121 
2122  /* Find the proper key and usage */
2123  key = get_key(&context->keyset);
2124  if (!key || (flags & FLAG_ACCEPTOR_SUBKEY && context->keyset.acceptor_key != key))
2125  return SEC_E_INTERNAL_ERROR;
2126  usage = context->acceptor ? KG_USAGE_INITIATOR_SIGN : KG_USAGE_ACCEPTOR_SIGN;
2127 
2128  /* Fill in the iov array lengths */
2129  KRB_CREDENTIALS* creds = context->credentials;
2130  iov[0].data.length = data_buffer->cbBuffer;
2131  iov[1].data.length = 16;
2132  if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov)))
2133  return SEC_E_INTERNAL_ERROR;
2134 
2135  if (sig_buffer->cbBuffer != iov[2].data.length + 16)
2136  return SEC_E_INTERNAL_ERROR;
2137 
2138  /* Set up the iov array */
2139  iov[0].data.data = data_buffer->pvBuffer;
2140  iov[1].data.data = (char*)header;
2141  iov[2].data.data = (char*)&header[16];
2142 
2143  if (krb_log_exec(krb5glue_verify_checksum_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov),
2144  &is_valid))
2145  return SEC_E_INTERNAL_ERROR;
2146 
2147  if (!is_valid)
2148  return SEC_E_MESSAGE_ALTERED;
2149 
2150  return SEC_E_OK;
2151 #else
2152  return SEC_E_UNSUPPORTED_FUNCTION;
2153 #endif
2154 }
2155 
2156 const SecurityFunctionTableA KERBEROS_SecurityFunctionTableA = {
2157  3, /* dwVersion */
2158  NULL, /* EnumerateSecurityPackages */
2159  kerberos_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
2160  kerberos_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
2161  kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */
2162  NULL, /* Reserved2 */
2163  kerberos_InitializeSecurityContextA, /* InitializeSecurityContext */
2164  kerberos_AcceptSecurityContext, /* AcceptSecurityContext */
2165  NULL, /* CompleteAuthToken */
2166  kerberos_DeleteSecurityContext, /* DeleteSecurityContext */
2167  NULL, /* ApplyControlToken */
2168  kerberos_QueryContextAttributesA, /* QueryContextAttributes */
2169  NULL, /* ImpersonateSecurityContext */
2170  NULL, /* RevertSecurityContext */
2171  kerberos_MakeSignature, /* MakeSignature */
2172  kerberos_VerifySignature, /* VerifySignature */
2173  NULL, /* FreeContextBuffer */
2174  NULL, /* QuerySecurityPackageInfo */
2175  NULL, /* Reserved3 */
2176  NULL, /* Reserved4 */
2177  NULL, /* ExportSecurityContext */
2178  NULL, /* ImportSecurityContext */
2179  NULL, /* AddCredentials */
2180  NULL, /* Reserved8 */
2181  NULL, /* QuerySecurityContextToken */
2182  kerberos_EncryptMessage, /* EncryptMessage */
2183  kerberos_DecryptMessage, /* DecryptMessage */
2184  kerberos_SetContextAttributesA, /* SetContextAttributes */
2185  kerberos_SetCredentialsAttributesA, /* SetCredentialsAttributes */
2186 };
2187 
2188 const SecurityFunctionTableW KERBEROS_SecurityFunctionTableW = {
2189  3, /* dwVersion */
2190  NULL, /* EnumerateSecurityPackages */
2191  kerberos_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
2192  kerberos_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
2193  kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */
2194  NULL, /* Reserved2 */
2195  kerberos_InitializeSecurityContextW, /* InitializeSecurityContext */
2196  kerberos_AcceptSecurityContext, /* AcceptSecurityContext */
2197  NULL, /* CompleteAuthToken */
2198  kerberos_DeleteSecurityContext, /* DeleteSecurityContext */
2199  NULL, /* ApplyControlToken */
2200  kerberos_QueryContextAttributesW, /* QueryContextAttributes */
2201  NULL, /* ImpersonateSecurityContext */
2202  NULL, /* RevertSecurityContext */
2203  kerberos_MakeSignature, /* MakeSignature */
2204  kerberos_VerifySignature, /* VerifySignature */
2205  NULL, /* FreeContextBuffer */
2206  NULL, /* QuerySecurityPackageInfo */
2207  NULL, /* Reserved3 */
2208  NULL, /* Reserved4 */
2209  NULL, /* ExportSecurityContext */
2210  NULL, /* ImportSecurityContext */
2211  NULL, /* AddCredentials */
2212  NULL, /* Reserved8 */
2213  NULL, /* QuerySecurityContextToken */
2214  kerberos_EncryptMessage, /* EncryptMessage */
2215  kerberos_DecryptMessage, /* DecryptMessage */
2216  kerberos_SetContextAttributesW, /* SetContextAttributes */
2217  kerberos_SetCredentialsAttributesW, /* SetCredentialsAttributes */
2218 };
2219 
2220 BOOL KERBEROS_init(void)
2221 {
2222  InitializeConstWCharFromUtf8(KERBEROS_SecPkgInfoA.Name, KERBEROS_SecPkgInfoW_NameBuffer,
2223  ARRAYSIZE(KERBEROS_SecPkgInfoW_NameBuffer));
2224  InitializeConstWCharFromUtf8(KERBEROS_SecPkgInfoA.Comment, KERBEROS_SecPkgInfoW_CommentBuffer,
2225  ARRAYSIZE(KERBEROS_SecPkgInfoW_CommentBuffer));
2226  return TRUE;
2227 }