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