FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
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_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, "%s (%s [%d])",
151 what, msg, code);
152 krb5_free_error_message(ctx, msg);
153 }
154 }
155 break;
156 }
157 return code;
158}
159
160static void credentials_unref(KRB_CREDENTIALS* credentials);
161
162static void kerberos_ContextFree(KRB_CONTEXT* ctx, BOOL allocated)
163{
164 if (!ctx)
165 return;
166
167 free(ctx->targetHost);
168 ctx->targetHost = NULL;
169
170 if (ctx->credentials)
171 {
172 krb5_context krbctx = ctx->credentials->ctx;
173 if (krbctx)
174 {
175 if (ctx->auth_ctx)
176 krb5_auth_con_free(krbctx, ctx->auth_ctx);
177
178 krb5glue_keys_free(krbctx, &ctx->keyset);
179 }
180
181 credentials_unref(ctx->credentials);
182 }
183
184 if (allocated)
185 free(ctx);
186}
187
188static KRB_CONTEXT* kerberos_ContextNew(KRB_CREDENTIALS* credentials)
189{
190 KRB_CONTEXT* context = NULL;
191
192 context = (KRB_CONTEXT*)calloc(1, sizeof(KRB_CONTEXT));
193 if (!context)
194 return NULL;
195
196 context->credentials = credentials;
197 InterlockedIncrement(&credentials->refCount);
198 return context;
199}
200
201static krb5_error_code krb5_prompter(krb5_context context, void* data,
202 WINPR_ATTR_UNUSED const char* name,
203 WINPR_ATTR_UNUSED const char* banner, int num_prompts,
204 krb5_prompt prompts[])
205{
206 for (int i = 0; i < num_prompts; i++)
207 {
208 krb5_prompt_type type = krb5glue_get_prompt_type(context, prompts, i);
209 if (type && (type == KRB5_PROMPT_TYPE_PREAUTH || type == KRB5_PROMPT_TYPE_PASSWORD) && data)
210 {
211 prompts[i].reply->data = _strdup((const char*)data);
212
213 const size_t len = strlen((const char*)data);
214 if (len > UINT32_MAX)
215 return KRB5KRB_ERR_GENERIC;
216 prompts[i].reply->length = (UINT32)len;
217 }
218 }
219 return 0;
220}
221
222static INLINE krb5glue_key get_key(struct krb5glue_keyset* keyset)
223{
224 return keyset->acceptor_key ? keyset->acceptor_key
225 : keyset->initiator_key ? keyset->initiator_key
226 : keyset->session_key;
227}
228
229static BOOL isValidIPv4(const char* ipAddress)
230{
231 struct sockaddr_in sa = { 0 };
232 int result = inet_pton(AF_INET, ipAddress, &(sa.sin_addr));
233 return result != 0;
234}
235
236static BOOL isValidIPv6(const char* ipAddress)
237{
238 struct sockaddr_in6 sa = { 0 };
239 int result = inet_pton(AF_INET6, ipAddress, &(sa.sin6_addr));
240 return result != 0;
241}
242
243static BOOL isValidIP(const char* ipAddress)
244{
245 return isValidIPv4(ipAddress) || isValidIPv6(ipAddress);
246}
247
248static int build_krbtgt(krb5_context ctx, krb5_data* realm, krb5_principal* ptarget)
249{
250 /* "krbtgt/" + realm + "@" + realm */
251 size_t len = 0;
252 char* name = NULL;
253 krb5_error_code rv = KRB5_CC_NOMEM;
254
255 (void)winpr_asprintf(&name, &len, "krbtgt/%s@%s", realm->data, realm->data);
256 if (!name || (len == 0))
257 goto fail;
258
259 krb5_principal target = { 0 };
260 rv = krb5_parse_name(ctx, name, &target);
261 *ptarget = target;
262fail:
263 free(name);
264 return rv;
265}
266
267#endif /* WITH_KRB5 */
268
269static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA(
270 SEC_CHAR* pszPrincipal, WINPR_ATTR_UNUSED SEC_CHAR* pszPackage, ULONG fCredentialUse,
271 WINPR_ATTR_UNUSED void* pvLogonID, void* pAuthData, WINPR_ATTR_UNUSED SEC_GET_KEY_FN pGetKeyFn,
272 WINPR_ATTR_UNUSED void* pvGetKeyArgument, PCredHandle phCredential,
273 WINPR_ATTR_UNUSED 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
441cleanup:
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
481static 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
507fail:
508 free(principal);
509 free(package);
510
511 return status;
512}
513
514#ifdef WITH_KRB5
515static 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
539static 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
555static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesW(
556 WINPR_ATTR_UNUSED PCredHandle phCredential, ULONG ulAttribute, WINPR_ATTR_UNUSED void* pBuffer)
557{
558#ifdef WITH_KRB5
559 switch (ulAttribute)
560 {
561 case SECPKG_CRED_ATTR_NAMES:
562 return SEC_E_OK;
563 default:
564 WLog_ERR(TAG, "TODO: QueryCredentialsAttributesW, implement ulAttribute=%08" PRIx32,
565 ulAttribute);
566 return SEC_E_UNSUPPORTED_FUNCTION;
567 }
568
569#else
570 return SEC_E_UNSUPPORTED_FUNCTION;
571#endif
572}
573
574static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesA(PCredHandle phCredential,
575 ULONG ulAttribute,
576 void* pBuffer)
577{
578 return kerberos_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
579}
580
581#ifdef WITH_KRB5
582
583static BOOL kerberos_mk_tgt_token(SecBuffer* buf, int msg_type, char* sname, char* host,
584 const krb5_data* ticket)
585{
586 WinPrAsn1Encoder* enc = NULL;
588 wStream s;
589 size_t len = 0;
590 sspi_gss_data token;
591 BOOL ret = FALSE;
592
593 WINPR_ASSERT(buf);
594
595 if (msg_type != KRB_TGT_REQ && msg_type != KRB_TGT_REP)
596 return FALSE;
597 if (msg_type == KRB_TGT_REP && !ticket)
598 return FALSE;
599
600 enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
601 if (!enc)
602 return FALSE;
603
604 /* KERB-TGT-REQUEST (SEQUENCE) */
605 if (!WinPrAsn1EncSeqContainer(enc))
606 goto cleanup;
607
608 /* pvno [0] INTEGER */
609 if (!WinPrAsn1EncContextualInteger(enc, 0, 5))
610 goto cleanup;
611
612 /* msg-type [1] INTEGER */
613 if (!WinPrAsn1EncContextualInteger(enc, 1, msg_type))
614 goto cleanup;
615
616 if (msg_type == KRB_TGT_REQ && sname)
617 {
618 /* server-name [2] PrincipalName (SEQUENCE) */
619 if (!WinPrAsn1EncContextualSeqContainer(enc, 2))
620 goto cleanup;
621
622 /* name-type [0] INTEGER */
623 if (!WinPrAsn1EncContextualInteger(enc, 0, KRB5_NT_SRV_HST))
624 goto cleanup;
625
626 /* name-string [1] SEQUENCE OF GeneralString */
627 if (!WinPrAsn1EncContextualSeqContainer(enc, 1))
628 goto cleanup;
629
630 if (!WinPrAsn1EncGeneralString(enc, sname))
631 goto cleanup;
632
633 if (host && !WinPrAsn1EncGeneralString(enc, host))
634 goto cleanup;
635
636 if (!WinPrAsn1EncEndContainer(enc) || !WinPrAsn1EncEndContainer(enc))
637 goto cleanup;
638 }
639 else if (msg_type == KRB_TGT_REP)
640 {
641 /* ticket [2] Ticket */
642 data.data = (BYTE*)ticket->data;
643 data.len = ticket->length;
644 if (!WinPrAsn1EncContextualRawContent(enc, 2, &data))
645 goto cleanup;
646 }
647
648 if (!WinPrAsn1EncEndContainer(enc))
649 goto cleanup;
650
651 if (!WinPrAsn1EncStreamSize(enc, &len) || len > buf->cbBuffer)
652 goto cleanup;
653
654 Stream_StaticInit(&s, buf->pvBuffer, len);
655 if (!WinPrAsn1EncToStream(enc, &s))
656 goto cleanup;
657
658 token.data = buf->pvBuffer;
659 token.length = (UINT)len;
660 if (sspi_gss_wrap_token(buf, &kerberos_u2u_OID,
661 msg_type == KRB_TGT_REQ ? TOK_ID_TGT_REQ : TOK_ID_TGT_REP, &token))
662 ret = TRUE;
663
664cleanup:
665 WinPrAsn1Encoder_Free(&enc);
666 return ret;
667}
668
669static BOOL append(char* dst, size_t dstSize, const char* src)
670{
671 const size_t dlen = strnlen(dst, dstSize);
672 const size_t slen = strlen(src);
673 if (dlen + slen >= dstSize)
674 return FALSE;
675 if (!strncat(dst, src, dstSize - dlen))
676 return FALSE;
677 return TRUE;
678}
679
680static BOOL kerberos_rd_tgt_req_tag2(WinPrAsn1Decoder* dec, char* buf, size_t len)
681{
682 BOOL rc = FALSE;
683 WinPrAsn1Decoder seq = { 0 };
684
685 /* server-name [2] PrincipalName (SEQUENCE) */
686 if (!WinPrAsn1DecReadSequence(dec, &seq))
687 goto end;
688
689 /* name-type [0] INTEGER */
690 BOOL error = FALSE;
691 WinPrAsn1_INTEGER val = 0;
692 if (!WinPrAsn1DecReadContextualInteger(&seq, 0, &error, &val))
693 goto end;
694
695 /* name-string [1] SEQUENCE OF GeneralString */
696 if (!WinPrAsn1DecReadContextualSequence(&seq, 1, &error, dec))
697 goto end;
698
699 WinPrAsn1_tag tag = 0;
700 BOOL first = TRUE;
701 while (WinPrAsn1DecPeekTag(dec, &tag))
702 {
703 BOOL success = FALSE;
704 char* lstr = NULL;
705 if (!WinPrAsn1DecReadGeneralString(dec, &lstr))
706 goto fail;
707
708 if (!first)
709 {
710 if (!append(buf, len, "/"))
711 goto fail;
712 }
713 first = FALSE;
714
715 if (!append(buf, len, lstr))
716 goto fail;
717
718 success = TRUE;
719 fail:
720 free(lstr);
721 if (!success)
722 goto end;
723 }
724
725 rc = TRUE;
726end:
727 return rc;
728}
729
730static BOOL kerberos_rd_tgt_req_tag3(WinPrAsn1Decoder* dec, char* buf, size_t len)
731{
732 /* realm [3] Realm */
733 BOOL rc = FALSE;
734 WinPrAsn1_STRING str = NULL;
735 if (!WinPrAsn1DecReadGeneralString(dec, &str))
736 goto end;
737
738 if (!append(buf, len, "@"))
739 goto end;
740 if (!append(buf, len, str))
741 goto end;
742
743 rc = TRUE;
744end:
745 free(str);
746 return rc;
747}
748
749static BOOL kerberos_rd_tgt_req(WinPrAsn1Decoder* dec, char** target)
750{
751 BOOL rc = FALSE;
752
753 if (!target)
754 return FALSE;
755 *target = NULL;
756
757 wStream s = WinPrAsn1DecGetStream(dec);
758 const size_t len = Stream_Length(&s);
759 if (len == 0)
760 return TRUE;
761
762 WinPrAsn1Decoder dec2 = { 0 };
763 WinPrAsn1_tagId tag = 0;
764 if (WinPrAsn1DecReadContextualTag(dec, &tag, &dec2) == 0)
765 return FALSE;
766
767 char* buf = calloc(len + 1, sizeof(char));
768 if (!buf)
769 return FALSE;
770
771 /* We expect ASN1 context tag values 2 or 3.
772 *
773 * In case we got value 2 an (optional) context tag value 3 might follow.
774 */
775 BOOL checkForTag3 = TRUE;
776 if (tag == 2)
777 {
778 rc = kerberos_rd_tgt_req_tag2(&dec2, buf, len);
779 if (rc)
780 {
781 const size_t res = WinPrAsn1DecReadContextualTag(dec, &tag, dec);
782 if (res == 0)
783 checkForTag3 = FALSE;
784 }
785 }
786
787 if (checkForTag3)
788 {
789 if (tag == 3)
790 rc = kerberos_rd_tgt_req_tag3(&dec2, buf, len);
791 else
792 rc = FALSE;
793 }
794
795 if (rc)
796 *target = buf;
797 else
798 free(buf);
799 return rc;
800}
801
802static BOOL kerberos_rd_tgt_rep(WinPrAsn1Decoder* dec, krb5_data* ticket)
803{
804 if (!ticket)
805 return FALSE;
806
807 /* ticket [2] Ticket */
808 WinPrAsn1Decoder asnTicket = { 0 };
809 WinPrAsn1_tagId tag = 0;
810 if (WinPrAsn1DecReadContextualTag(dec, &tag, &asnTicket) == 0)
811 return FALSE;
812
813 if (tag != 2)
814 return FALSE;
815
816 wStream s = WinPrAsn1DecGetStream(&asnTicket);
817 ticket->data = Stream_BufferAs(&s, char);
818
819 const size_t len = Stream_Length(&s);
820 if (len > UINT32_MAX)
821 return FALSE;
822 ticket->length = (UINT32)len;
823 return TRUE;
824}
825
826static BOOL kerberos_rd_tgt_token(const sspi_gss_data* token, char** target, krb5_data* ticket)
827{
828 BOOL error = 0;
829 WinPrAsn1_INTEGER val = 0;
830
831 WINPR_ASSERT(token);
832
833 if (target)
834 *target = NULL;
835
836 WinPrAsn1Decoder der = { 0 };
837 WinPrAsn1Decoder_InitMem(&der, WINPR_ASN1_DER, (BYTE*)token->data, token->length);
838
839 /* KERB-TGT-REQUEST (SEQUENCE) */
840 WinPrAsn1Decoder seq = { 0 };
841 if (!WinPrAsn1DecReadSequence(&der, &seq))
842 return FALSE;
843
844 /* pvno [0] INTEGER */
845 if (!WinPrAsn1DecReadContextualInteger(&seq, 0, &error, &val) || val != 5)
846 return FALSE;
847
848 /* msg-type [1] INTEGER */
849 if (!WinPrAsn1DecReadContextualInteger(&seq, 1, &error, &val))
850 return FALSE;
851
852 switch (val)
853 {
854 case KRB_TGT_REQ:
855 return kerberos_rd_tgt_req(&seq, target);
856 case KRB_TGT_REP:
857 return kerberos_rd_tgt_rep(&seq, ticket);
858 default:
859 break;
860 }
861 return FALSE;
862}
863
864#endif /* WITH_KRB5 */
865
866static BOOL kerberos_hash_channel_bindings(WINPR_DIGEST_CTX* md5, SEC_CHANNEL_BINDINGS* bindings)
867{
868 BYTE buf[4];
869
870 winpr_Data_Write_UINT32(buf, bindings->dwInitiatorAddrType);
871 if (!winpr_Digest_Update(md5, buf, 4))
872 return FALSE;
873
874 winpr_Data_Write_UINT32(buf, bindings->cbInitiatorLength);
875 if (!winpr_Digest_Update(md5, buf, 4))
876 return FALSE;
877
878 if (bindings->cbInitiatorLength &&
879 !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwInitiatorOffset,
880 bindings->cbInitiatorLength))
881 return FALSE;
882
883 winpr_Data_Write_UINT32(buf, bindings->dwAcceptorAddrType);
884 if (!winpr_Digest_Update(md5, buf, 4))
885 return FALSE;
886
887 winpr_Data_Write_UINT32(buf, bindings->cbAcceptorLength);
888 if (!winpr_Digest_Update(md5, buf, 4))
889 return FALSE;
890
891 if (bindings->cbAcceptorLength &&
892 !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwAcceptorOffset,
893 bindings->cbAcceptorLength))
894 return FALSE;
895
896 winpr_Data_Write_UINT32(buf, bindings->cbApplicationDataLength);
897 if (!winpr_Digest_Update(md5, buf, 4))
898 return FALSE;
899
900 if (bindings->cbApplicationDataLength &&
901 !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwApplicationDataOffset,
902 bindings->cbApplicationDataLength))
903 return FALSE;
904
905 return TRUE;
906}
907
908static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA(
909 PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
910 WINPR_ATTR_UNUSED ULONG Reserved1, WINPR_ATTR_UNUSED ULONG TargetDataRep, PSecBufferDesc pInput,
911 WINPR_ATTR_UNUSED ULONG Reserved2, PCtxtHandle phNewContext, PSecBufferDesc pOutput,
912 WINPR_ATTR_UNUSED ULONG* pfContextAttr, WINPR_ATTR_UNUSED 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
1188cleanup:
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
1221bad_token:
1222 status = SEC_E_INVALID_TOKEN;
1223 goto cleanup;
1224#else
1225 return SEC_E_UNSUPPORTED_FUNCTION;
1226#endif /* WITH_KRB5 */
1227}
1228
1229static 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
1254static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext(
1255 PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput,
1256 WINPR_ATTR_UNUSED ULONG fContextReq, WINPR_ATTR_UNUSED ULONG TargetDataRep,
1257 PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr,
1258 WINPR_ATTR_UNUSED PTimeStamp ptsExpity)
1259{
1260#ifdef WITH_KRB5
1261 BOOL isNewContext = FALSE;
1262 PSecBuffer input_buffer = NULL;
1263 PSecBuffer output_buffer = NULL;
1264 WinPrAsn1_OID oid = { 0 };
1265 uint16_t tok_id = 0;
1266 krb5_data input_token = { 0 };
1267 krb5_data output_token = { 0 };
1268 SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
1269 krb5_flags ap_flags = 0;
1270 krb5glue_authenticator authenticator = NULL;
1271 char* target = NULL;
1272 char* sname = NULL;
1273 char* realm = NULL;
1274 krb5_kt_cursor cur = { 0 };
1275 krb5_keytab_entry entry = { 0 };
1276 krb5_principal principal = NULL;
1277 krb5_creds creds = { 0 };
1278
1279 /* behave like windows SSPIs that don't want empty context */
1280 if (phContext && !phContext->dwLower && !phContext->dwUpper)
1281 return SEC_E_INVALID_HANDLE;
1282
1283 KRB_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext);
1284 KRB_CREDENTIALS* credentials = sspi_SecureHandleGetLowerPointer(phCredential);
1285
1286 if (pInput)
1287 input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
1288 if (pOutput)
1289 output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
1290
1291 if (!input_buffer)
1292 return SEC_E_INVALID_TOKEN;
1293
1294 if (!sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token))
1295 return SEC_E_INVALID_TOKEN;
1296
1297 if (!context)
1298 {
1299 isNewContext = TRUE;
1300 context = kerberos_ContextNew(credentials);
1301 context->acceptor = TRUE;
1302
1303 if (sspi_gss_oid_compare(&oid, &kerberos_u2u_OID))
1304 {
1305 context->u2u = TRUE;
1306 context->state = KERBEROS_STATE_TGT_REQ;
1307 }
1308 else if (sspi_gss_oid_compare(&oid, &kerberos_OID))
1309 context->state = KERBEROS_STATE_AP_REQ;
1310 else
1311 goto bad_token;
1312 }
1313 else
1314 {
1315 if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) ||
1316 (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID)))
1317 goto bad_token;
1318 }
1319
1320 if (context->state == KERBEROS_STATE_TGT_REQ && tok_id == TOK_ID_TGT_REQ)
1321 {
1322 if (!kerberos_rd_tgt_token(&input_token, &target, NULL))
1323 goto bad_token;
1324
1325 /*
1326 * we're requested with target="TERMSRV/<host>@<REALM>" but we're gonna look
1327 * at <host>$@<REALM> in the keytab (notice the $), so we build a new "target"
1328 * string containing
1329 *
1330 * sname realm
1331 * | |
1332 * v v
1333 * <host>$@<REALM>
1334 *
1335 */
1336 if (target)
1337 {
1338 sname = strchr(target, '/');
1339 if (!sname)
1340 goto cleanup;
1341 sname++;
1342
1343 /* target goes from TERMSRV/<host>[@<REALM>] to <host>[@<REALM>] */
1344 sname = memmove(target, sname, strlen(sname) + 1);
1345
1346 realm = strchr(target, '@');
1347 if (realm)
1348 {
1349 *realm = '$';
1350 realm++;
1351
1352 size_t len = strlen(realm);
1353 memmove(realm + 1, realm, len + 1);
1354
1355 *realm = '@';
1356 realm++;
1357 }
1358 else
1359 {
1360 size_t len = strlen(sname);
1361 target[len] = '$';
1362 target[len + 1] = 0;
1363 }
1364 }
1365
1366 if (krb_log_exec(krb5_parse_name_flags, credentials->ctx, sname ? sname : "",
1367 KRB5_PRINCIPAL_PARSE_NO_REALM, &principal))
1368 goto cleanup;
1369
1370 if (realm)
1371 {
1372 if (krb_log_exec(krb5glue_set_principal_realm, credentials->ctx, principal, realm))
1373 goto cleanup;
1374 }
1375
1376 if (krb_log_exec(krb5_kt_start_seq_get, credentials->ctx, credentials->keytab, &cur))
1377 goto cleanup;
1378
1379 do
1380 {
1381 krb5_error_code rv = krb_log_exec(krb5_kt_next_entry, credentials->ctx,
1382 credentials->keytab, &entry, &cur);
1383 if (rv == KRB5_KT_END)
1384 break;
1385 if (rv != 0)
1386 goto cleanup;
1387
1388 if ((!sname ||
1389 krb5_principal_compare_any_realm(credentials->ctx, principal, entry.principal)) &&
1390 (!realm || krb5_realm_compare(credentials->ctx, principal, entry.principal)))
1391 break;
1392 const krb5_error_code res =
1393 krb_log_exec(krb5glue_free_keytab_entry_contents, credentials->ctx, &entry);
1394 memset(&entry, 0, sizeof(entry));
1395 if (res != 0)
1396 goto cleanup;
1397 } while (1);
1398
1399 if (krb_log_exec(krb5_kt_end_seq_get, credentials->ctx, credentials->keytab, &cur))
1400 goto cleanup;
1401
1402 if (!entry.principal)
1403 goto cleanup;
1404
1405 /* Get the TGT */
1406 if (krb_log_exec(krb5_get_init_creds_keytab, credentials->ctx, &creds, entry.principal,
1407 credentials->keytab, 0, NULL, NULL))
1408 goto cleanup;
1409
1410 if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REP, NULL, NULL, &creds.ticket))
1411 goto cleanup;
1412
1413 if (krb_log_exec(krb5_auth_con_init, credentials->ctx, &context->auth_ctx))
1414 goto cleanup;
1415
1416 if (krb_log_exec(krb5glue_auth_con_setuseruserkey, credentials->ctx, context->auth_ctx,
1417 &krb5glue_creds_getkey(creds)))
1418 goto cleanup;
1419
1420 context->state = KERBEROS_STATE_AP_REQ;
1421 }
1422 else if (context->state == KERBEROS_STATE_AP_REQ && tok_id == TOK_ID_AP_REQ)
1423 {
1424 if (krb_log_exec(krb5_rd_req, credentials->ctx, &context->auth_ctx, &input_token, NULL,
1425 credentials->keytab, &ap_flags, NULL))
1426 goto cleanup;
1427
1428 if (krb_log_exec(krb5_auth_con_setflags, credentials->ctx, context->auth_ctx,
1429 KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY))
1430 goto cleanup;
1431
1432 /* Retrieve and validate the checksum */
1433 if (krb_log_exec(krb5_auth_con_getauthenticator, credentials->ctx, context->auth_ctx,
1434 &authenticator))
1435 goto cleanup;
1436 if (!krb5glue_authenticator_validate_chksum(authenticator, GSS_CHECKSUM_TYPE,
1437 &context->flags))
1438 goto bad_token;
1439
1440 if ((ap_flags & AP_OPTS_MUTUAL_REQUIRED) && (context->flags & SSPI_GSS_C_MUTUAL_FLAG))
1441 {
1442 if (!output_buffer)
1443 goto bad_token;
1444 if (krb_log_exec(krb5_mk_rep, credentials->ctx, context->auth_ctx, &output_token))
1445 goto cleanup;
1446 if (!sspi_gss_wrap_token(output_buffer,
1447 context->u2u ? &kerberos_u2u_OID : &kerberos_OID,
1448 TOK_ID_AP_REP, &output_token))
1449 goto cleanup;
1450 }
1451 else
1452 {
1453 if (output_buffer)
1454 output_buffer->cbBuffer = 0;
1455 }
1456
1457 *pfContextAttr = (context->flags & 0x1F);
1458 if (context->flags & SSPI_GSS_C_INTEG_FLAG)
1459 *pfContextAttr |= ASC_RET_INTEGRITY;
1460
1461 if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG)
1462 {
1463 if (krb_log_exec(krb5_auth_con_getlocalseqnumber, credentials->ctx, context->auth_ctx,
1464 (INT32*)&context->local_seq))
1465 goto cleanup;
1466 if (krb_log_exec(krb5_auth_con_getremoteseqnumber, credentials->ctx, context->auth_ctx,
1467 (INT32*)&context->remote_seq))
1468 goto cleanup;
1469 }
1470
1471 if (krb_log_exec(krb5glue_update_keyset, credentials->ctx, context->auth_ctx, TRUE,
1472 &context->keyset))
1473 goto cleanup;
1474
1475 context->state = KERBEROS_STATE_FINAL;
1476 }
1477 else
1478 goto bad_token;
1479
1480 /* On first call allocate new context */
1481 if (context->state == KERBEROS_STATE_FINAL)
1482 status = SEC_E_OK;
1483 else
1484 status = SEC_I_CONTINUE_NEEDED;
1485
1486cleanup:
1487 free(target);
1488 if (output_token.data)
1489 krb5glue_free_data_contents(credentials->ctx, &output_token);
1490 if (entry.principal)
1491 krb5glue_free_keytab_entry_contents(credentials->ctx, &entry);
1492
1493 if (isNewContext)
1494 {
1495 switch (status)
1496 {
1497 case SEC_E_OK:
1498 case SEC_I_CONTINUE_NEEDED:
1499 sspi_SecureHandleSetLowerPointer(phNewContext, context);
1500 sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME);
1501 break;
1502 default:
1503 kerberos_ContextFree(context, TRUE);
1504 break;
1505 }
1506 }
1507
1508 return status;
1509
1510bad_token:
1511 status = SEC_E_INVALID_TOKEN;
1512 goto cleanup;
1513#else
1514 return SEC_E_UNSUPPORTED_FUNCTION;
1515#endif /* WITH_KRB5 */
1516}
1517
1518#ifdef WITH_KRB5
1519static KRB_CONTEXT* get_context(PCtxtHandle phContext)
1520{
1521 if (!phContext)
1522 return NULL;
1523
1524 TCHAR* name = sspi_SecureHandleGetUpperPointer(phContext);
1525 if (!name)
1526 return NULL;
1527
1528 if (_tcsncmp(KERBEROS_SSP_NAME, name, ARRAYSIZE(KERBEROS_SSP_NAME)) != 0)
1529 return NULL;
1530 return sspi_SecureHandleGetLowerPointer(phContext);
1531}
1532
1533static BOOL copy_krb5_data(krb5_data* data, PUCHAR* ptr, ULONG* psize)
1534{
1535 WINPR_ASSERT(data);
1536 WINPR_ASSERT(ptr);
1537 WINPR_ASSERT(psize);
1538
1539 *ptr = (PUCHAR)malloc(data->length);
1540 if (!*ptr)
1541 return FALSE;
1542
1543 *psize = data->length;
1544 memcpy(*ptr, data->data, data->length);
1545 return TRUE;
1546}
1547#endif
1548
1549static SECURITY_STATUS SEC_ENTRY kerberos_DeleteSecurityContext(PCtxtHandle phContext)
1550{
1551#ifdef WITH_KRB5
1552 KRB_CONTEXT* context = get_context(phContext);
1553 if (!context)
1554 return SEC_E_INVALID_HANDLE;
1555
1556 kerberos_ContextFree(context, TRUE);
1557
1558 return SEC_E_OK;
1559#else
1560 return SEC_E_UNSUPPORTED_FUNCTION;
1561#endif
1562}
1563
1564#ifdef WITH_KRB5
1565
1566static SECURITY_STATUS krb5_error_to_SECURITY_STATUS(krb5_error_code code)
1567{
1568 switch (code)
1569 {
1570 case 0:
1571 return SEC_E_OK;
1572 default:
1573 return SEC_E_INTERNAL_ERROR;
1574 }
1575}
1576
1577static SECURITY_STATUS kerberos_ATTR_SIZES(KRB_CONTEXT* context, KRB_CREDENTIALS* credentials,
1578 SecPkgContext_Sizes* ContextSizes)
1579{
1580 UINT header = 0;
1581 UINT pad = 0;
1582 UINT trailer = 0;
1583 krb5glue_key key = NULL;
1584
1585 WINPR_ASSERT(context);
1586 WINPR_ASSERT(context->auth_ctx);
1587
1588 /* The MaxTokenSize by default is 12,000 bytes. This has been the default value
1589 * since Windows 2000 SP2 and still remains in Windows 7 and Windows 2008 R2.
1590 * For Windows Server 2012, the default value of the MaxTokenSize registry
1591 * entry is 48,000 bytes.*/
1592 ContextSizes->cbMaxToken = KERBEROS_SecPkgInfoA.cbMaxToken;
1593 ContextSizes->cbMaxSignature = 0;
1594 ContextSizes->cbBlockSize = 1;
1595 ContextSizes->cbSecurityTrailer = 0;
1596
1597 key = get_key(&context->keyset);
1598
1599 if (context->flags & SSPI_GSS_C_CONF_FLAG)
1600 {
1601 krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key,
1602 KRB5_CRYPTO_TYPE_HEADER, &header);
1603 if (rv)
1604 return krb5_error_to_SECURITY_STATUS(rv);
1605
1606 rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key, KRB5_CRYPTO_TYPE_PADDING,
1607 &pad);
1608 if (rv)
1609 return krb5_error_to_SECURITY_STATUS(rv);
1610
1611 rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key, KRB5_CRYPTO_TYPE_TRAILER,
1612 &trailer);
1613 if (rv)
1614 return krb5_error_to_SECURITY_STATUS(rv);
1615
1616 /* GSS header (= 16 bytes) + encrypted header = 32 bytes */
1617 ContextSizes->cbSecurityTrailer = header + pad + trailer + 32;
1618 }
1619
1620 if (context->flags & SSPI_GSS_C_INTEG_FLAG)
1621 {
1622 krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key,
1623 KRB5_CRYPTO_TYPE_CHECKSUM, &ContextSizes->cbMaxSignature);
1624 if (rv)
1625 return krb5_error_to_SECURITY_STATUS(rv);
1626
1627 ContextSizes->cbMaxSignature += 16;
1628 }
1629
1630 return SEC_E_OK;
1631}
1632
1633static SECURITY_STATUS kerberos_ATTR_TICKET_LOGON(KRB_CONTEXT* context,
1634 KRB_CREDENTIALS* credentials,
1635 KERB_TICKET_LOGON* ticketLogon)
1636{
1637 krb5_creds matchCred = { 0 };
1638 krb5_auth_context authContext = NULL;
1639 krb5_flags getCredsFlags = KRB5_GC_CACHED;
1640 BOOL firstRun = TRUE;
1641 krb5_creds* hostCred = NULL;
1642 SECURITY_STATUS ret = SEC_E_INSUFFICIENT_MEMORY;
1643 int rv = krb_log_exec(krb5_sname_to_principal, credentials->ctx, context->targetHost, "HOST",
1644 KRB5_NT_SRV_HST, &matchCred.server);
1645 if (rv)
1646 goto out;
1647
1648 rv = krb_log_exec(krb5_cc_get_principal, credentials->ctx, credentials->ccache,
1649 &matchCred.client);
1650 if (rv)
1651 goto out;
1652
1653 /* try from the cache first, and then do a new request */
1654again:
1655 rv = krb_log_exec(krb5_get_credentials, credentials->ctx, getCredsFlags, credentials->ccache,
1656 &matchCred, &hostCred);
1657 switch (rv)
1658 {
1659 case 0:
1660 break;
1661 case KRB5_CC_NOTFOUND:
1662 getCredsFlags = 0;
1663 if (firstRun)
1664 {
1665 firstRun = FALSE;
1666 goto again;
1667 }
1668 WINPR_FALLTHROUGH
1669 default:
1670 WLog_ERR(TAG, "krb5_get_credentials(hostCreds), rv=%d", rv);
1671 goto out;
1672 }
1673
1674 if (krb_log_exec(krb5_auth_con_init, credentials->ctx, &authContext))
1675 goto out;
1676
1677 krb5_data derOut = { 0 };
1678 if (krb_log_exec(krb5_fwd_tgt_creds, credentials->ctx, authContext, context->targetHost,
1679 matchCred.client, matchCred.server, credentials->ccache, 1, &derOut))
1680 {
1681 ret = SEC_E_LOGON_DENIED;
1682 goto out;
1683 }
1684
1685 ticketLogon->MessageType = KerbTicketLogon;
1686 ticketLogon->Flags = KERB_LOGON_FLAG_REDIRECTED;
1687
1688 if (!copy_krb5_data(&hostCred->ticket, &ticketLogon->ServiceTicket,
1689 &ticketLogon->ServiceTicketLength))
1690 {
1691 krb5_free_data(credentials->ctx, &derOut);
1692 goto out;
1693 }
1694
1695 ticketLogon->TicketGrantingTicketLength = derOut.length;
1696 ticketLogon->TicketGrantingTicket = (PUCHAR)derOut.data;
1697
1698 ret = SEC_E_OK;
1699out:
1700 krb5_auth_con_free(credentials->ctx, authContext);
1701 krb5_free_creds(credentials->ctx, hostCred);
1702 krb5_free_cred_contents(credentials->ctx, &matchCred);
1703 return ret;
1704}
1705
1706#endif /* WITH_KRB5 */
1707
1708static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesA(PCtxtHandle phContext,
1709 ULONG ulAttribute, void* pBuffer)
1710{
1711 if (!phContext)
1712 return SEC_E_INVALID_HANDLE;
1713
1714 if (!pBuffer)
1715 return SEC_E_INVALID_PARAMETER;
1716
1717#ifdef WITH_KRB5
1718 KRB_CONTEXT* context = get_context(phContext);
1719 if (!context)
1720 return SEC_E_INVALID_PARAMETER;
1721
1722 KRB_CREDENTIALS* credentials = context->credentials;
1723
1724 switch (ulAttribute)
1725 {
1726 case SECPKG_ATTR_SIZES:
1727 return kerberos_ATTR_SIZES(context, credentials, (SecPkgContext_Sizes*)pBuffer);
1728
1729 case SECPKG_CRED_ATTR_TICKET_LOGON:
1730 return kerberos_ATTR_TICKET_LOGON(context, credentials, (KERB_TICKET_LOGON*)pBuffer);
1731
1732 default:
1733 WLog_ERR(TAG, "TODO: QueryContextAttributes implement ulAttribute=0x%08" PRIx32,
1734 ulAttribute);
1735 return SEC_E_UNSUPPORTED_FUNCTION;
1736 }
1737#else
1738 return SEC_E_UNSUPPORTED_FUNCTION;
1739#endif
1740}
1741
1742static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesW(PCtxtHandle phContext,
1743 ULONG ulAttribute, void* pBuffer)
1744{
1745 return kerberos_QueryContextAttributesA(phContext, ulAttribute, pBuffer);
1746}
1747
1748static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesW(
1749 WINPR_ATTR_UNUSED PCtxtHandle phContext, WINPR_ATTR_UNUSED ULONG ulAttribute,
1750 WINPR_ATTR_UNUSED void* pBuffer, WINPR_ATTR_UNUSED ULONG cbBuffer)
1751{
1752 return SEC_E_UNSUPPORTED_FUNCTION;
1753}
1754
1755static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesA(
1756 WINPR_ATTR_UNUSED PCtxtHandle phContext, WINPR_ATTR_UNUSED ULONG ulAttribute,
1757 WINPR_ATTR_UNUSED void* pBuffer, WINPR_ATTR_UNUSED ULONG cbBuffer)
1758{
1759 return SEC_E_UNSUPPORTED_FUNCTION;
1760}
1761
1762static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesX(PCredHandle phCredential,
1763 ULONG ulAttribute,
1764 void* pBuffer, ULONG cbBuffer,
1765 WINPR_ATTR_UNUSED BOOL unicode)
1766{
1767#ifdef WITH_KRB5
1768 KRB_CREDENTIALS* credentials = NULL;
1769
1770 if (!phCredential)
1771 return SEC_E_INVALID_HANDLE;
1772
1773 credentials = sspi_SecureHandleGetLowerPointer(phCredential);
1774
1775 if (!credentials)
1776 return SEC_E_INVALID_HANDLE;
1777
1778 if (!pBuffer)
1779 return SEC_E_INSUFFICIENT_MEMORY;
1780
1781 switch (ulAttribute)
1782 {
1783 case SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS:
1784 {
1785 SecPkgCredentials_KdcProxySettingsW* kdc_settings = pBuffer;
1786
1787 /* Sanity checks */
1788 if (cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) ||
1789 kdc_settings->Version != KDC_PROXY_SETTINGS_V1 ||
1790 kdc_settings->ProxyServerOffset < sizeof(SecPkgCredentials_KdcProxySettingsW) ||
1791 cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) +
1792 kdc_settings->ProxyServerOffset + kdc_settings->ProxyServerLength)
1793 return SEC_E_INVALID_TOKEN;
1794
1795 if (credentials->kdc_url)
1796 {
1797 free(credentials->kdc_url);
1798 credentials->kdc_url = NULL;
1799 }
1800
1801 if (kdc_settings->ProxyServerLength > 0)
1802 {
1803 WCHAR* proxy = (WCHAR*)((BYTE*)pBuffer + kdc_settings->ProxyServerOffset);
1804
1805 credentials->kdc_url = ConvertWCharNToUtf8Alloc(
1806 proxy, kdc_settings->ProxyServerLength / sizeof(WCHAR), NULL);
1807 if (!credentials->kdc_url)
1808 return SEC_E_INSUFFICIENT_MEMORY;
1809 }
1810
1811 return SEC_E_OK;
1812 }
1813 case SECPKG_CRED_ATTR_NAMES:
1814 case SECPKG_ATTR_SUPPORTED_ALGS:
1815 default:
1816 WLog_ERR(TAG, "TODO: SetCredentialsAttributesX implement ulAttribute=0x%08" PRIx32,
1817 ulAttribute);
1818 return SEC_E_UNSUPPORTED_FUNCTION;
1819 }
1820
1821#else
1822 return SEC_E_UNSUPPORTED_FUNCTION;
1823#endif
1824}
1825
1826static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesW(PCredHandle phCredential,
1827 ULONG ulAttribute,
1828 void* pBuffer, ULONG cbBuffer)
1829{
1830 return kerberos_SetCredentialsAttributesX(phCredential, ulAttribute, pBuffer, cbBuffer, TRUE);
1831}
1832
1833static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesA(PCredHandle phCredential,
1834 ULONG ulAttribute,
1835 void* pBuffer, ULONG cbBuffer)
1836{
1837 return kerberos_SetCredentialsAttributesX(phCredential, ulAttribute, pBuffer, cbBuffer, FALSE);
1838}
1839
1840static SECURITY_STATUS SEC_ENTRY kerberos_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
1841 PSecBufferDesc pMessage,
1842 ULONG MessageSeqNo)
1843{
1844#ifdef WITH_KRB5
1845 KRB_CONTEXT* context = get_context(phContext);
1846 PSecBuffer sig_buffer = NULL;
1847 PSecBuffer data_buffer = NULL;
1848 char* header = NULL;
1849 BYTE flags = 0;
1850 krb5glue_key key = NULL;
1851 krb5_keyusage usage = 0;
1852 krb5_crypto_iov encrypt_iov[] = { { KRB5_CRYPTO_TYPE_HEADER, { 0 } },
1853 { KRB5_CRYPTO_TYPE_DATA, { 0 } },
1854 { KRB5_CRYPTO_TYPE_DATA, { 0 } },
1855 { KRB5_CRYPTO_TYPE_PADDING, { 0 } },
1856 { KRB5_CRYPTO_TYPE_TRAILER, { 0 } } };
1857
1858 if (!context)
1859 return SEC_E_INVALID_HANDLE;
1860
1861 if (!(context->flags & SSPI_GSS_C_CONF_FLAG))
1862 return SEC_E_UNSUPPORTED_FUNCTION;
1863
1864 KRB_CREDENTIALS* creds = context->credentials;
1865
1866 sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
1867 data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
1868
1869 if (!sig_buffer || !data_buffer)
1870 return SEC_E_INVALID_TOKEN;
1871
1872 if (fQOP)
1873 return SEC_E_QOP_NOT_SUPPORTED;
1874
1875 flags |= context->acceptor ? FLAG_SENDER_IS_ACCEPTOR : 0;
1876 flags |= FLAG_WRAP_CONFIDENTIAL;
1877
1878 key = get_key(&context->keyset);
1879 if (!key)
1880 return SEC_E_INTERNAL_ERROR;
1881
1882 flags |= context->keyset.acceptor_key == key ? FLAG_ACCEPTOR_SUBKEY : 0;
1883
1884 usage = context->acceptor ? KG_USAGE_ACCEPTOR_SEAL : KG_USAGE_INITIATOR_SEAL;
1885
1886 /* Set the lengths of the data (plaintext + header) */
1887 encrypt_iov[1].data.length = data_buffer->cbBuffer;
1888 encrypt_iov[2].data.length = 16;
1889
1890 /* Get the lengths of the header, trailer, and padding and ensure sig_buffer is large enough */
1891 if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, encrypt_iov,
1892 ARRAYSIZE(encrypt_iov)))
1893 return SEC_E_INTERNAL_ERROR;
1894 if (sig_buffer->cbBuffer <
1895 encrypt_iov[0].data.length + encrypt_iov[3].data.length + encrypt_iov[4].data.length + 32)
1896 return SEC_E_INSUFFICIENT_MEMORY;
1897
1898 /* Set up the iov array in sig_buffer */
1899 header = sig_buffer->pvBuffer;
1900 encrypt_iov[2].data.data = header + 16;
1901 encrypt_iov[3].data.data = encrypt_iov[2].data.data + encrypt_iov[2].data.length;
1902 encrypt_iov[4].data.data = encrypt_iov[3].data.data + encrypt_iov[3].data.length;
1903 encrypt_iov[0].data.data = encrypt_iov[4].data.data + encrypt_iov[4].data.length;
1904 encrypt_iov[1].data.data = data_buffer->pvBuffer;
1905
1906 /* Write the GSS header with 0 in RRC */
1907 winpr_Data_Write_UINT16_BE(header, TOK_ID_WRAP);
1908 header[2] = WINPR_ASSERTING_INT_CAST(char, flags);
1909 header[3] = (char)0xFF;
1910 winpr_Data_Write_UINT32(header + 4, 0);
1911 winpr_Data_Write_UINT64_BE(header + 8, (context->local_seq + MessageSeqNo));
1912
1913 /* Copy header to be encrypted */
1914 CopyMemory(encrypt_iov[2].data.data, header, 16);
1915
1916 /* Set the correct RRC */
1917 const size_t len = 16 + encrypt_iov[3].data.length + encrypt_iov[4].data.length;
1918 winpr_Data_Write_UINT16_BE(header + 6, WINPR_ASSERTING_INT_CAST(UINT16, len));
1919
1920 if (krb_log_exec(krb5glue_encrypt_iov, creds->ctx, key, usage, encrypt_iov,
1921 ARRAYSIZE(encrypt_iov)))
1922 return SEC_E_INTERNAL_ERROR;
1923
1924 return SEC_E_OK;
1925#else
1926 return SEC_E_UNSUPPORTED_FUNCTION;
1927#endif
1928}
1929
1930static SECURITY_STATUS SEC_ENTRY kerberos_DecryptMessage(PCtxtHandle phContext,
1931 PSecBufferDesc pMessage,
1932 ULONG MessageSeqNo, ULONG* pfQOP)
1933{
1934#ifdef WITH_KRB5
1935 KRB_CONTEXT* context = get_context(phContext);
1936 PSecBuffer sig_buffer = NULL;
1937 PSecBuffer data_buffer = NULL;
1938 krb5glue_key key = NULL;
1939 krb5_keyusage usage = 0;
1940 uint16_t tok_id = 0;
1941 BYTE flags = 0;
1942 uint16_t ec = 0;
1943 uint16_t rrc = 0;
1944 uint64_t seq_no = 0;
1945 krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_HEADER, { 0 } },
1946 { KRB5_CRYPTO_TYPE_DATA, { 0 } },
1947 { KRB5_CRYPTO_TYPE_DATA, { 0 } },
1948 { KRB5_CRYPTO_TYPE_PADDING, { 0 } },
1949 { KRB5_CRYPTO_TYPE_TRAILER, { 0 } } };
1950
1951 if (!context)
1952 return SEC_E_INVALID_HANDLE;
1953
1954 if (!(context->flags & SSPI_GSS_C_CONF_FLAG))
1955 return SEC_E_UNSUPPORTED_FUNCTION;
1956
1957 KRB_CREDENTIALS* creds = context->credentials;
1958
1959 sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
1960 data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
1961
1962 if (!sig_buffer || !data_buffer || sig_buffer->cbBuffer < 16)
1963 return SEC_E_INVALID_TOKEN;
1964
1965 /* Read in header information */
1966 BYTE* header = sig_buffer->pvBuffer;
1967 tok_id = winpr_Data_Get_UINT16_BE(header);
1968 flags = header[2];
1969 ec = winpr_Data_Get_UINT16_BE(&header[4]);
1970 rrc = winpr_Data_Get_UINT16_BE(&header[6]);
1971 seq_no = winpr_Data_Get_UINT64_BE(&header[8]);
1972
1973 /* Check that the header is valid */
1974 if ((tok_id != TOK_ID_WRAP) || (header[3] != 0xFF))
1975 return SEC_E_INVALID_TOKEN;
1976
1977 if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor)
1978 return SEC_E_INVALID_TOKEN;
1979
1980 if ((context->flags & ISC_REQ_SEQUENCE_DETECT) &&
1981 (seq_no != context->remote_seq + MessageSeqNo))
1982 return SEC_E_OUT_OF_SEQUENCE;
1983
1984 if (!(flags & FLAG_WRAP_CONFIDENTIAL))
1985 return SEC_E_INVALID_TOKEN;
1986
1987 /* We don't expect a trailer buffer; the encrypted header must be rotated */
1988 if (rrc < 16)
1989 return SEC_E_INVALID_TOKEN;
1990
1991 /* Find the proper key and key usage */
1992 key = get_key(&context->keyset);
1993 if (!key || ((flags & FLAG_ACCEPTOR_SUBKEY) && (context->keyset.acceptor_key != key)))
1994 return SEC_E_INTERNAL_ERROR;
1995 usage = context->acceptor ? KG_USAGE_INITIATOR_SEAL : KG_USAGE_ACCEPTOR_SEAL;
1996
1997 /* Fill in the lengths of the iov array */
1998 iov[1].data.length = data_buffer->cbBuffer;
1999 iov[2].data.length = 16;
2000 if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov)))
2001 return SEC_E_INTERNAL_ERROR;
2002
2003 /* We don't expect a trailer buffer; everything must be in sig_buffer */
2004 if (rrc != 16 + iov[3].data.length + iov[4].data.length)
2005 return SEC_E_INVALID_TOKEN;
2006 if (sig_buffer->cbBuffer != 16 + rrc + iov[0].data.length)
2007 return SEC_E_INVALID_TOKEN;
2008
2009 /* Locate the parts of the message */
2010 iov[0].data.data = (char*)&header[16 + rrc + ec];
2011 iov[1].data.data = data_buffer->pvBuffer;
2012 iov[2].data.data = (char*)&header[16 + ec];
2013 char* data2 = iov[2].data.data;
2014 iov[3].data.data = &data2[iov[2].data.length];
2015
2016 char* data3 = iov[3].data.data;
2017 iov[4].data.data = &data3[iov[3].data.length];
2018
2019 if (krb_log_exec(krb5glue_decrypt_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov)))
2020 return SEC_E_INTERNAL_ERROR;
2021
2022 /* Validate the encrypted header */
2023 winpr_Data_Write_UINT16_BE(iov[2].data.data + 4, ec);
2024 winpr_Data_Write_UINT16_BE(iov[2].data.data + 6, rrc);
2025 if (memcmp(iov[2].data.data, header, 16) != 0)
2026 return SEC_E_MESSAGE_ALTERED;
2027
2028 *pfQOP = 0;
2029
2030 return SEC_E_OK;
2031#else
2032 return SEC_E_UNSUPPORTED_FUNCTION;
2033#endif
2034}
2035
2036static SECURITY_STATUS SEC_ENTRY kerberos_MakeSignature(PCtxtHandle phContext,
2037 WINPR_ATTR_UNUSED ULONG fQOP,
2038 PSecBufferDesc pMessage, ULONG MessageSeqNo)
2039{
2040#ifdef WITH_KRB5
2041 KRB_CONTEXT* context = get_context(phContext);
2042 PSecBuffer sig_buffer = NULL;
2043 PSecBuffer data_buffer = NULL;
2044 krb5glue_key key = NULL;
2045 krb5_keyusage usage = 0;
2046 BYTE flags = 0;
2047 krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_DATA, { 0 } },
2048 { KRB5_CRYPTO_TYPE_DATA, { 0 } },
2049 { KRB5_CRYPTO_TYPE_CHECKSUM, { 0 } } };
2050
2051 if (!context)
2052 return SEC_E_INVALID_HANDLE;
2053
2054 if (!(context->flags & SSPI_GSS_C_INTEG_FLAG))
2055 return SEC_E_UNSUPPORTED_FUNCTION;
2056
2057 KRB_CREDENTIALS* creds = context->credentials;
2058
2059 sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
2060 data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
2061
2062 if (!sig_buffer || !data_buffer)
2063 return SEC_E_INVALID_TOKEN;
2064
2065 flags |= context->acceptor ? FLAG_SENDER_IS_ACCEPTOR : 0;
2066
2067 key = get_key(&context->keyset);
2068 if (!key)
2069 return SEC_E_INTERNAL_ERROR;
2070 usage = context->acceptor ? KG_USAGE_ACCEPTOR_SIGN : KG_USAGE_INITIATOR_SIGN;
2071
2072 flags |= context->keyset.acceptor_key == key ? FLAG_ACCEPTOR_SUBKEY : 0;
2073
2074 /* Fill in the lengths of the iov array */
2075 iov[0].data.length = data_buffer->cbBuffer;
2076 iov[1].data.length = 16;
2077 if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov)))
2078 return SEC_E_INTERNAL_ERROR;
2079
2080 /* Ensure the buffer is big enough */
2081 if (sig_buffer->cbBuffer < iov[2].data.length + 16)
2082 return SEC_E_INSUFFICIENT_MEMORY;
2083
2084 /* Write the header */
2085 char* header = sig_buffer->pvBuffer;
2086 winpr_Data_Write_UINT16_BE(header, TOK_ID_MIC);
2087 header[2] = WINPR_ASSERTING_INT_CAST(char, flags);
2088 memset(header + 3, 0xFF, 5);
2089 winpr_Data_Write_UINT64_BE(header + 8, (context->local_seq + MessageSeqNo));
2090
2091 /* Set up the iov array */
2092 iov[0].data.data = data_buffer->pvBuffer;
2093 iov[1].data.data = header;
2094 iov[2].data.data = header + 16;
2095
2096 if (krb_log_exec(krb5glue_make_checksum_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov)))
2097 return SEC_E_INTERNAL_ERROR;
2098
2099 sig_buffer->cbBuffer = iov[2].data.length + 16;
2100
2101 return SEC_E_OK;
2102#else
2103 return SEC_E_UNSUPPORTED_FUNCTION;
2104#endif
2105}
2106
2107static SECURITY_STATUS SEC_ENTRY kerberos_VerifySignature(PCtxtHandle phContext,
2108 PSecBufferDesc pMessage,
2109 ULONG MessageSeqNo,
2110 WINPR_ATTR_UNUSED ULONG* pfQOP)
2111{
2112#ifdef WITH_KRB5
2113 PSecBuffer sig_buffer = NULL;
2114 PSecBuffer data_buffer = NULL;
2115 krb5glue_key key = NULL;
2116 krb5_keyusage usage = 0;
2117 BYTE flags = 0;
2118 uint16_t tok_id = 0;
2119 uint64_t seq_no = 0;
2120 krb5_boolean is_valid = 0;
2121 krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_DATA, { 0 } },
2122 { KRB5_CRYPTO_TYPE_DATA, { 0 } },
2123 { KRB5_CRYPTO_TYPE_CHECKSUM, { 0 } } };
2124 BYTE cmp_filler[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
2125
2126 KRB_CONTEXT* context = get_context(phContext);
2127 if (!context)
2128 return SEC_E_INVALID_HANDLE;
2129
2130 if (!(context->flags & SSPI_GSS_C_INTEG_FLAG))
2131 return SEC_E_UNSUPPORTED_FUNCTION;
2132
2133 sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN);
2134 data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
2135
2136 if (!sig_buffer || !data_buffer || sig_buffer->cbBuffer < 16)
2137 return SEC_E_INVALID_TOKEN;
2138
2139 /* Read in header info */
2140 BYTE* header = sig_buffer->pvBuffer;
2141 tok_id = winpr_Data_Get_UINT16_BE(header);
2142 flags = header[2];
2143 seq_no = winpr_Data_Get_UINT64_BE((header + 8));
2144
2145 /* Validate header */
2146 if (tok_id != TOK_ID_MIC)
2147 return SEC_E_INVALID_TOKEN;
2148
2149 if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor || flags & FLAG_WRAP_CONFIDENTIAL)
2150 return SEC_E_INVALID_TOKEN;
2151
2152 if (memcmp(header + 3, cmp_filler, sizeof(cmp_filler)) != 0)
2153 return SEC_E_INVALID_TOKEN;
2154
2155 if (context->flags & ISC_REQ_SEQUENCE_DETECT && seq_no != context->remote_seq + MessageSeqNo)
2156 return SEC_E_OUT_OF_SEQUENCE;
2157
2158 /* Find the proper key and usage */
2159 key = get_key(&context->keyset);
2160 if (!key || (flags & FLAG_ACCEPTOR_SUBKEY && context->keyset.acceptor_key != key))
2161 return SEC_E_INTERNAL_ERROR;
2162 usage = context->acceptor ? KG_USAGE_INITIATOR_SIGN : KG_USAGE_ACCEPTOR_SIGN;
2163
2164 /* Fill in the iov array lengths */
2165 KRB_CREDENTIALS* creds = context->credentials;
2166 iov[0].data.length = data_buffer->cbBuffer;
2167 iov[1].data.length = 16;
2168 if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov)))
2169 return SEC_E_INTERNAL_ERROR;
2170
2171 if (sig_buffer->cbBuffer != iov[2].data.length + 16)
2172 return SEC_E_INTERNAL_ERROR;
2173
2174 /* Set up the iov array */
2175 iov[0].data.data = data_buffer->pvBuffer;
2176 iov[1].data.data = (char*)header;
2177 iov[2].data.data = (char*)&header[16];
2178
2179 if (krb_log_exec(krb5glue_verify_checksum_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov),
2180 &is_valid))
2181 return SEC_E_INTERNAL_ERROR;
2182
2183 if (!is_valid)
2184 return SEC_E_MESSAGE_ALTERED;
2185
2186 return SEC_E_OK;
2187#else
2188 return SEC_E_UNSUPPORTED_FUNCTION;
2189#endif
2190}
2191
2192const SecurityFunctionTableA KERBEROS_SecurityFunctionTableA = {
2193 3, /* dwVersion */
2194 NULL, /* EnumerateSecurityPackages */
2195 kerberos_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
2196 kerberos_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
2197 kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */
2198 NULL, /* Reserved2 */
2199 kerberos_InitializeSecurityContextA, /* InitializeSecurityContext */
2200 kerberos_AcceptSecurityContext, /* AcceptSecurityContext */
2201 NULL, /* CompleteAuthToken */
2202 kerberos_DeleteSecurityContext, /* DeleteSecurityContext */
2203 NULL, /* ApplyControlToken */
2204 kerberos_QueryContextAttributesA, /* QueryContextAttributes */
2205 NULL, /* ImpersonateSecurityContext */
2206 NULL, /* RevertSecurityContext */
2207 kerberos_MakeSignature, /* MakeSignature */
2208 kerberos_VerifySignature, /* VerifySignature */
2209 NULL, /* FreeContextBuffer */
2210 NULL, /* QuerySecurityPackageInfo */
2211 NULL, /* Reserved3 */
2212 NULL, /* Reserved4 */
2213 NULL, /* ExportSecurityContext */
2214 NULL, /* ImportSecurityContext */
2215 NULL, /* AddCredentials */
2216 NULL, /* Reserved8 */
2217 NULL, /* QuerySecurityContextToken */
2218 kerberos_EncryptMessage, /* EncryptMessage */
2219 kerberos_DecryptMessage, /* DecryptMessage */
2220 kerberos_SetContextAttributesA, /* SetContextAttributes */
2221 kerberos_SetCredentialsAttributesA, /* SetCredentialsAttributes */
2222};
2223
2224const SecurityFunctionTableW KERBEROS_SecurityFunctionTableW = {
2225 3, /* dwVersion */
2226 NULL, /* EnumerateSecurityPackages */
2227 kerberos_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
2228 kerberos_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
2229 kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */
2230 NULL, /* Reserved2 */
2231 kerberos_InitializeSecurityContextW, /* InitializeSecurityContext */
2232 kerberos_AcceptSecurityContext, /* AcceptSecurityContext */
2233 NULL, /* CompleteAuthToken */
2234 kerberos_DeleteSecurityContext, /* DeleteSecurityContext */
2235 NULL, /* ApplyControlToken */
2236 kerberos_QueryContextAttributesW, /* QueryContextAttributes */
2237 NULL, /* ImpersonateSecurityContext */
2238 NULL, /* RevertSecurityContext */
2239 kerberos_MakeSignature, /* MakeSignature */
2240 kerberos_VerifySignature, /* VerifySignature */
2241 NULL, /* FreeContextBuffer */
2242 NULL, /* QuerySecurityPackageInfo */
2243 NULL, /* Reserved3 */
2244 NULL, /* Reserved4 */
2245 NULL, /* ExportSecurityContext */
2246 NULL, /* ImportSecurityContext */
2247 NULL, /* AddCredentials */
2248 NULL, /* Reserved8 */
2249 NULL, /* QuerySecurityContextToken */
2250 kerberos_EncryptMessage, /* EncryptMessage */
2251 kerberos_DecryptMessage, /* DecryptMessage */
2252 kerberos_SetContextAttributesW, /* SetContextAttributes */
2253 kerberos_SetCredentialsAttributesW, /* SetCredentialsAttributes */
2254};
2255
2256BOOL KERBEROS_init(void)
2257{
2258 InitializeConstWCharFromUtf8(KERBEROS_SecPkgInfoA.Name, KERBEROS_SecPkgInfoW_NameBuffer,
2259 ARRAYSIZE(KERBEROS_SecPkgInfoW_NameBuffer));
2260 InitializeConstWCharFromUtf8(KERBEROS_SecPkgInfoA.Comment, KERBEROS_SecPkgInfoW_CommentBuffer,
2261 ARRAYSIZE(KERBEROS_SecPkgInfoW_CommentBuffer));
2262 return TRUE;
2263}