FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
credssp_auth.c
1
22#include <ctype.h>
23
24#include <freerdp/config.h>
25#include "settings.h"
26#include <freerdp/build-config.h>
27#include <freerdp/peer.h>
28
29#include <winpr/crt.h>
30#include <winpr/wtypes.h>
31#include <winpr/assert.h>
32#include <winpr/library.h>
33#include <winpr/registry.h>
34#include <winpr/sspi.h>
35
36#include <freerdp/log.h>
37
38#include "utils.h"
39#include "credssp_auth.h"
40
41#define TAG FREERDP_TAG("core.auth")
42
43#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
44
45enum AUTH_STATE
46{
47 AUTH_STATE_INITIAL,
48 AUTH_STATE_CREDS,
49 AUTH_STATE_IN_PROGRESS,
50 AUTH_STATE_FINAL
51};
52
53struct rdp_credssp_auth
54{
55 const rdpContext* rdp_ctx;
56 SecurityFunctionTable* table;
57 SecPkgInfo* info;
58 SEC_WINNT_AUTH_IDENTITY identity;
59 SEC_WINPR_NTLM_SETTINGS ntlmSettings;
60 SEC_WINPR_KERBEROS_SETTINGS kerberosSettings;
61 CredHandle credentials;
62 BOOL server;
63 SecPkgContext_Bindings* bindings;
64 TCHAR* spn;
65 WCHAR* package_list;
66 CtxtHandle context;
67 SecBuffer input_buffer;
68 SecBuffer output_buffer;
69 ULONG flags;
71 SECURITY_STATUS sspi_error;
72 enum AUTH_STATE state;
73 char* pkgNameA;
74};
75
76static const char* credssp_auth_state_string(const rdpCredsspAuth* auth)
77{
78 WINPR_ASSERT(auth);
79 switch (auth->state)
80 {
81 case AUTH_STATE_INITIAL:
82 return "AUTH_STATE_INITIAL";
83 case AUTH_STATE_CREDS:
84 return "AUTH_STATE_CREDS";
85 case AUTH_STATE_IN_PROGRESS:
86 return "AUTH_STATE_IN_PROGRESS";
87 case AUTH_STATE_FINAL:
88 return "AUTH_STATE_FINAL";
89 default:
90 return "AUTH_STATE_UNKNOWN";
91 }
92}
93static BOOL parseKerberosDeltat(const char* value, INT32* dest, const char* message);
94static BOOL credssp_auth_setup_identity(rdpCredsspAuth* auth);
95static SecurityFunctionTable* auth_resolve_sspi_table(const rdpSettings* settings);
96
97static BOOL credssp_auth_update_name_cache(rdpCredsspAuth* auth, TCHAR* name)
98{
99 WINPR_ASSERT(auth);
100
101 free(auth->pkgNameA);
102 auth->pkgNameA = NULL;
103 if (name)
104#if defined(UNICODE)
105 auth->pkgNameA = ConvertWCharToUtf8Alloc(name, NULL);
106#else
107 auth->pkgNameA = _strdup(name);
108#endif
109 return TRUE;
110}
111rdpCredsspAuth* credssp_auth_new(const rdpContext* rdp_ctx)
112{
113 rdpCredsspAuth* auth = calloc(1, sizeof(rdpCredsspAuth));
114
115 if (auth)
116 auth->rdp_ctx = rdp_ctx;
117
118 return auth;
119}
120
121BOOL credssp_auth_init(rdpCredsspAuth* auth, TCHAR* pkg_name, SecPkgContext_Bindings* bindings)
122{
123 WINPR_ASSERT(auth);
124 WINPR_ASSERT(auth->rdp_ctx);
125
126 const rdpSettings* settings = auth->rdp_ctx->settings;
127 WINPR_ASSERT(settings);
128
129 if (!credssp_auth_update_name_cache(auth, pkg_name))
130 return FALSE;
131
132 auth->table = auth_resolve_sspi_table(settings);
133 if (!auth->table)
134 {
135 WLog_ERR(TAG, "Unable to initialize sspi table");
136 return FALSE;
137 }
138
139 /* Package name will be stored in the info structure */
140 WINPR_ASSERT(auth->table->QuerySecurityPackageInfo);
141 const SECURITY_STATUS status = auth->table->QuerySecurityPackageInfo(pkg_name, &auth->info);
142 if (status != SEC_E_OK)
143 {
144 WLog_ERR(TAG, "QuerySecurityPackageInfo (%s) failed with %s [0x%08X]",
145 credssp_auth_pkg_name(auth), GetSecurityStatusString(status), status);
146 return FALSE;
147 }
148
149 if (!credssp_auth_update_name_cache(auth, auth->info->Name))
150 return FALSE;
151
152 WLog_DBG(TAG, "Using package: %s (cbMaxToken: %u bytes)", credssp_auth_pkg_name(auth),
153 auth->info->cbMaxToken);
154
155 /* Setup common identity settings */
156 if (!credssp_auth_setup_identity(auth))
157 return FALSE;
158
159 auth->bindings = bindings;
160
161 return TRUE;
162}
163
164static BOOL credssp_auth_setup_auth_data(rdpCredsspAuth* auth,
165 const SEC_WINNT_AUTH_IDENTITY* identity,
167{
168 WINPR_ASSERT(pAuthData);
169 ZeroMemory(pAuthData, sizeof(SEC_WINNT_AUTH_IDENTITY_WINPR));
170
171 SEC_WINNT_AUTH_IDENTITY_EXW* identityEx = &pAuthData->identity;
172 identityEx->Version = SEC_WINNT_AUTH_IDENTITY_VERSION;
173 identityEx->Length = sizeof(SEC_WINNT_AUTH_IDENTITY_EX);
174 identityEx->User = identity->User;
175 identityEx->UserLength = identity->UserLength;
176 identityEx->Domain = identity->Domain;
177 identityEx->DomainLength = identity->DomainLength;
178 identityEx->Password = identity->Password;
179 identityEx->PasswordLength = identity->PasswordLength;
180 identityEx->Flags = identity->Flags;
181 identityEx->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
182 identityEx->Flags |= SEC_WINNT_AUTH_IDENTITY_EXTENDED;
183
184 if (auth->package_list)
185 {
186 const size_t len = _wcslen(auth->package_list);
187 if (len > UINT32_MAX)
188 return FALSE;
189
190 identityEx->PackageList = (UINT16*)auth->package_list;
191 identityEx->PackageListLength = (UINT32)len;
192 }
193
194 pAuthData->ntlmSettings = &auth->ntlmSettings;
195 pAuthData->kerberosSettings = &auth->kerberosSettings;
196
197 return TRUE;
198}
199
200static BOOL credssp_auth_client_init_cred_attributes(rdpCredsspAuth* auth)
201{
202 WINPR_ASSERT(auth);
203
204 if (!utils_str_is_empty(auth->kerberosSettings.kdcUrl))
205 {
206 SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
207 SSIZE_T str_size = 0;
208
209 str_size = ConvertUtf8ToWChar(auth->kerberosSettings.kdcUrl, NULL, 0);
210 if ((str_size <= 0) || (str_size >= UINT16_MAX / 2))
211 return FALSE;
212 str_size++;
213
214 const size_t buffer_size =
215 sizeof(SecPkgCredentials_KdcProxySettingsW) + (size_t)str_size * sizeof(WCHAR);
216 if (buffer_size > UINT32_MAX)
217 return FALSE;
218 SecPkgCredentials_KdcProxySettingsW* secAttr = calloc(1, buffer_size);
219 if (!secAttr)
220 return FALSE;
221
222 secAttr->Version = KDC_PROXY_SETTINGS_V1;
223 secAttr->ProxyServerLength = (UINT16)((size_t)str_size * sizeof(WCHAR));
224 secAttr->ProxyServerOffset = sizeof(SecPkgCredentials_KdcProxySettingsW);
225
226 if (ConvertUtf8ToWChar(auth->kerberosSettings.kdcUrl, (WCHAR*)(secAttr + 1),
227 (size_t)str_size) <= 0)
228 {
229 free(secAttr);
230 return FALSE;
231 }
232
233#ifdef UNICODE
234 if (auth->table->SetCredentialsAttributesW)
235 status = auth->table->SetCredentialsAttributesW(&auth->credentials,
236 SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS,
237 (void*)secAttr, (UINT32)buffer_size);
238 else
239 status = SEC_E_UNSUPPORTED_FUNCTION;
240#else
241 if (auth->table->SetCredentialsAttributesA)
242 status = auth->table->SetCredentialsAttributesA(&auth->credentials,
243 SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS,
244 (void*)secAttr, (UINT32)buffer_size);
245 else
246 status = SEC_E_UNSUPPORTED_FUNCTION;
247#endif
248
249 if (status != SEC_E_OK)
250 {
251 WLog_WARN(TAG, "Explicit Kerberos KDC URL (%s) injection is not supported",
252 auth->kerberosSettings.kdcUrl);
253 }
254
255 free(secAttr);
256 }
257
258 return TRUE;
259}
260
261BOOL credssp_auth_setup_client(rdpCredsspAuth* auth, const char* target_service,
262 const char* target_hostname, const SEC_WINNT_AUTH_IDENTITY* identity,
263 const char* pkinit)
264{
265 void* pAuthData = NULL;
266 SEC_WINNT_AUTH_IDENTITY_WINPR winprAuthData = { 0 };
267
268 WINPR_ASSERT(auth);
269 WINPR_ASSERT(auth->table);
270 WINPR_ASSERT(auth->info);
271
272 WINPR_ASSERT(auth->state == AUTH_STATE_INITIAL);
273
274 /* Construct the service principal name */
275 if (!credssp_auth_set_spn(auth, target_service, target_hostname))
276 return FALSE;
277
278 if (identity)
279 {
280 credssp_auth_setup_auth_data(auth, identity, &winprAuthData);
281
282 if (pkinit)
283 {
284 auth->kerberosSettings.pkinitX509Identity = _strdup(pkinit);
285 if (!auth->kerberosSettings.pkinitX509Identity)
286 {
287 WLog_ERR(TAG, "unable to copy pkinitArgs");
288 return FALSE;
289 }
290 }
291
292 pAuthData = (void*)&winprAuthData;
293 }
294
295 WINPR_ASSERT(auth->table->AcquireCredentialsHandle);
296 const SECURITY_STATUS status =
297 auth->table->AcquireCredentialsHandle(NULL, auth->info->Name, SECPKG_CRED_OUTBOUND, NULL,
298 pAuthData, NULL, NULL, &auth->credentials, NULL);
299
300 if (status != SEC_E_OK)
301 {
302 WLog_ERR(TAG, "AcquireCredentialsHandleA failed with %s [0x%08X]",
303 GetSecurityStatusString(status), status);
304 return FALSE;
305 }
306
307 if (!credssp_auth_client_init_cred_attributes(auth))
308 {
309 WLog_ERR(TAG, "Fatal error setting credential attributes");
310 return FALSE;
311 }
312
313 auth->state = AUTH_STATE_CREDS;
314 WLog_DBG(TAG, "Acquired client credentials");
315
316 return TRUE;
317}
318
319BOOL credssp_auth_setup_server(rdpCredsspAuth* auth)
320{
321 void* pAuthData = NULL;
322 SEC_WINNT_AUTH_IDENTITY_WINPR winprAuthData = { 0 };
323
324 WINPR_ASSERT(auth);
325 WINPR_ASSERT(auth->table);
326
327 WINPR_ASSERT(auth->state == AUTH_STATE_INITIAL);
328
329 if (auth->ntlmSettings.samFile || auth->ntlmSettings.hashCallback ||
330 auth->kerberosSettings.keytab)
331 {
332 credssp_auth_setup_auth_data(auth, &auth->identity, &winprAuthData);
333 pAuthData = &winprAuthData;
334 }
335
336 WINPR_ASSERT(auth->table->AcquireCredentialsHandle);
337 const SECURITY_STATUS status =
338 auth->table->AcquireCredentialsHandle(NULL, auth->info->Name, SECPKG_CRED_INBOUND, NULL,
339 pAuthData, NULL, NULL, &auth->credentials, NULL);
340 if (status != SEC_E_OK)
341 {
342 WLog_ERR(TAG, "AcquireCredentialsHandleA failed with %s [0x%08X]",
343 GetSecurityStatusString(status), status);
344 return FALSE;
345 }
346
347 auth->state = AUTH_STATE_CREDS;
348 WLog_DBG(TAG, "Acquired server credentials");
349
350 auth->server = TRUE;
351
352 return TRUE;
353}
354
355void credssp_auth_set_flags(rdpCredsspAuth* auth, ULONG flags)
356{
357 WINPR_ASSERT(auth);
358 auth->flags = flags;
359}
360
399int credssp_auth_authenticate(rdpCredsspAuth* auth)
400{
401 SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
402 SecBuffer input_buffers[2] = { 0 };
403 SecBufferDesc input_buffer_desc = { SECBUFFER_VERSION, 1, input_buffers };
404 CtxtHandle* context = NULL;
405
406 WINPR_ASSERT(auth);
407 WINPR_ASSERT(auth->table);
408
409 SecBufferDesc output_buffer_desc = { SECBUFFER_VERSION, 1, &auth->output_buffer };
410
411 switch (auth->state)
412 {
413 case AUTH_STATE_CREDS:
414 case AUTH_STATE_IN_PROGRESS:
415 break;
416 case AUTH_STATE_INITIAL:
417 case AUTH_STATE_FINAL:
418 WLog_ERR(TAG, "context in invalid state!");
419 return -1;
420 default:
421 break;
422 }
423
424 /* input buffer will be null on first call,
425 * context MUST be NULL on first call */
426 context = &auth->context;
427 if (!auth->context.dwLower && !auth->context.dwUpper)
428 context = NULL;
429
430 input_buffers[0] = auth->input_buffer;
431
432 if (auth->bindings)
433 {
434 input_buffer_desc.cBuffers = 2;
435
436 input_buffers[1].BufferType = SECBUFFER_CHANNEL_BINDINGS;
437 input_buffers[1].cbBuffer = auth->bindings->BindingsLength;
438 input_buffers[1].pvBuffer = auth->bindings->Bindings;
439 }
440
441 /* Free previous output buffer (presumably no longer needed) */
442 sspi_SecBufferFree(&auth->output_buffer);
443 auth->output_buffer.BufferType = SECBUFFER_TOKEN;
444 if (!sspi_SecBufferAlloc(&auth->output_buffer, auth->info->cbMaxToken))
445 return -1;
446
447 if (auth->server)
448 {
449 WINPR_ASSERT(auth->table->AcceptSecurityContext);
450 status = auth->table->AcceptSecurityContext(
451 &auth->credentials, context, &input_buffer_desc, auth->flags, SECURITY_NATIVE_DREP,
452 &auth->context, &output_buffer_desc, &auth->flags, NULL);
453 }
454 else
455 {
456 WINPR_ASSERT(auth->table->InitializeSecurityContext);
457 status = auth->table->InitializeSecurityContext(
458 &auth->credentials, context, auth->spn, auth->flags, 0, SECURITY_NATIVE_DREP,
459 &input_buffer_desc, 0, &auth->context, &output_buffer_desc, &auth->flags, NULL);
460 }
461
462 if (status == SEC_E_OK)
463 {
464 WLog_DBG(TAG, "Authentication complete (output token size: %" PRIu32 " bytes)",
465 auth->output_buffer.cbBuffer);
466 auth->state = AUTH_STATE_FINAL;
467
468 /* Not terrible if this fails, although encryption functions may run into issues down the
469 * line, still, authentication succeeded */
470 WINPR_ASSERT(auth->table->QueryContextAttributes);
471 status =
472 auth->table->QueryContextAttributes(&auth->context, SECPKG_ATTR_SIZES, &auth->sizes);
473 WLog_DBG(TAG, "QueryContextAttributes returned %s [0x%08" PRIx32 "]",
474 GetSecurityStatusString(status), status);
475 WLog_DBG(TAG, "Context sizes: cbMaxSignature=%d, cbSecurityTrailer=%d",
476 auth->sizes.cbMaxSignature, auth->sizes.cbSecurityTrailer);
477
478 return 1;
479 }
480 else if (status == SEC_I_CONTINUE_NEEDED)
481 {
482 WLog_DBG(TAG, "Authentication in progress... (output token size: %" PRIu32 ")",
483 auth->output_buffer.cbBuffer);
484 auth->state = AUTH_STATE_IN_PROGRESS;
485 return 0;
486 }
487 else
488 {
489 WLog_ERR(TAG, "%s failed with %s [0x%08X]",
490 auth->server ? "AcceptSecurityContext" : "InitializeSecurityContext",
491 GetSecurityStatusString(status), status);
492 auth->sspi_error = status;
493 return -1;
494 }
495}
496
497/* Plaintext is not modified; Output buffer MUST be freed if encryption succeeds */
498BOOL credssp_auth_encrypt(rdpCredsspAuth* auth, const SecBuffer* plaintext, SecBuffer* ciphertext,
499 size_t* signature_length, ULONG sequence)
500{
501 SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
502 SecBuffer buffers[2] = { 0 };
503 SecBufferDesc buffer_desc = { SECBUFFER_VERSION, 2, buffers };
504 BYTE* buf = NULL;
505
506 WINPR_ASSERT(auth && auth->table);
507 WINPR_ASSERT(plaintext);
508 WINPR_ASSERT(ciphertext);
509
510 switch (auth->state)
511 {
512 case AUTH_STATE_INITIAL:
513 WLog_ERR(TAG, "Invalid state %s", credssp_auth_state_string(auth));
514 return FALSE;
515 default:
516 break;
517 }
518
519 /* Allocate consecutive memory for ciphertext and signature */
520 buf = calloc(1, plaintext->cbBuffer + auth->sizes.cbSecurityTrailer);
521 if (!buf)
522 return FALSE;
523
524 buffers[0].BufferType = SECBUFFER_TOKEN;
525 buffers[0].cbBuffer = auth->sizes.cbSecurityTrailer;
526 buffers[0].pvBuffer = buf;
527
528 buffers[1].BufferType = SECBUFFER_DATA;
529 if (plaintext->BufferType & SECBUFFER_READONLY)
530 buffers[1].BufferType |= SECBUFFER_READONLY;
531 buffers[1].pvBuffer = buf + auth->sizes.cbSecurityTrailer;
532 buffers[1].cbBuffer = plaintext->cbBuffer;
533 CopyMemory(buffers[1].pvBuffer, plaintext->pvBuffer, plaintext->cbBuffer);
534
535 WINPR_ASSERT(auth->table->EncryptMessage);
536 status = auth->table->EncryptMessage(&auth->context, 0, &buffer_desc, sequence);
537 if (status != SEC_E_OK)
538 {
539 WLog_ERR(TAG, "EncryptMessage failed with %s [0x%08X]", GetSecurityStatusString(status),
540 status);
541 free(buf);
542 return FALSE;
543 }
544
545 if (buffers[0].cbBuffer < auth->sizes.cbSecurityTrailer)
546 {
547 /* The signature is smaller than cbSecurityTrailer, so shrink the excess in between */
548 MoveMemory(((BYTE*)buffers[0].pvBuffer) + buffers[0].cbBuffer, buffers[1].pvBuffer,
549 buffers[1].cbBuffer);
550 // use reported signature size as new cbSecurityTrailer value for DecryptMessage
551 auth->sizes.cbSecurityTrailer = buffers[0].cbBuffer;
552 }
553
554 ciphertext->cbBuffer = buffers[0].cbBuffer + buffers[1].cbBuffer;
555 ciphertext->pvBuffer = buf;
556
557 if (signature_length)
558 *signature_length = buffers[0].cbBuffer;
559
560 return TRUE;
561}
562
563/* Output buffer MUST be freed if decryption succeeds */
564BOOL credssp_auth_decrypt(rdpCredsspAuth* auth, const SecBuffer* ciphertext, SecBuffer* plaintext,
565 ULONG sequence)
566{
567 SecBuffer buffers[2];
568 SecBufferDesc buffer_desc = { SECBUFFER_VERSION, 2, buffers };
569 ULONG fqop = 0;
570
571 WINPR_ASSERT(auth && auth->table);
572 WINPR_ASSERT(ciphertext);
573 WINPR_ASSERT(plaintext);
574
575 switch (auth->state)
576 {
577 case AUTH_STATE_INITIAL:
578 WLog_ERR(TAG, "Invalid state %s", credssp_auth_state_string(auth));
579 return FALSE;
580 default:
581 break;
582 }
583
584 /* Sanity check: ciphertext must at least have a signature */
585 if (ciphertext->cbBuffer < auth->sizes.cbSecurityTrailer)
586 {
587 WLog_ERR(TAG, "Encrypted message buffer too small");
588 return FALSE;
589 }
590
591 /* Split the input into signature and encrypted data; we assume the signature length is equal to
592 * cbSecurityTrailer */
593 buffers[0].BufferType = SECBUFFER_TOKEN;
594 buffers[0].pvBuffer = ciphertext->pvBuffer;
595 buffers[0].cbBuffer = auth->sizes.cbSecurityTrailer;
596
597 buffers[1].BufferType = SECBUFFER_DATA;
598 if (!sspi_SecBufferAlloc(&buffers[1], ciphertext->cbBuffer - auth->sizes.cbSecurityTrailer))
599 return FALSE;
600 CopyMemory(buffers[1].pvBuffer, (BYTE*)ciphertext->pvBuffer + auth->sizes.cbSecurityTrailer,
601 buffers[1].cbBuffer);
602
603 WINPR_ASSERT(auth->table->DecryptMessage);
604 const SECURITY_STATUS status =
605 auth->table->DecryptMessage(&auth->context, &buffer_desc, sequence, &fqop);
606 if (status != SEC_E_OK)
607 {
608 WLog_ERR(TAG, "DecryptMessage failed with %s [0x%08X]", GetSecurityStatusString(status),
609 status);
610 sspi_SecBufferFree(&buffers[1]);
611 return FALSE;
612 }
613
614 *plaintext = buffers[1];
615
616 return TRUE;
617}
618
619BOOL credssp_auth_impersonate(rdpCredsspAuth* auth)
620{
621 WINPR_ASSERT(auth && auth->table);
622
623 WINPR_ASSERT(auth->table->ImpersonateSecurityContext);
624 const SECURITY_STATUS status = auth->table->ImpersonateSecurityContext(&auth->context);
625
626 if (status != SEC_E_OK)
627 {
628 WLog_ERR(TAG, "ImpersonateSecurityContext failed with %s [0x%08X]",
629 GetSecurityStatusString(status), status);
630 return FALSE;
631 }
632
633 return TRUE;
634}
635
636BOOL credssp_auth_revert_to_self(rdpCredsspAuth* auth)
637{
638 WINPR_ASSERT(auth && auth->table);
639
640 WINPR_ASSERT(auth->table->RevertSecurityContext);
641 const SECURITY_STATUS status = auth->table->RevertSecurityContext(&auth->context);
642
643 if (status != SEC_E_OK)
644 {
645 WLog_ERR(TAG, "RevertSecurityContext failed with %s [0x%08X]",
646 GetSecurityStatusString(status), status);
647 return FALSE;
648 }
649
650 return TRUE;
651}
652
653void credssp_auth_take_input_buffer(rdpCredsspAuth* auth, SecBuffer* buffer)
654{
655 WINPR_ASSERT(auth);
656 WINPR_ASSERT(buffer);
657
658 sspi_SecBufferFree(&auth->input_buffer);
659
660 auth->input_buffer = *buffer;
661 auth->input_buffer.BufferType = SECBUFFER_TOKEN;
662
663 /* Invalidate original, rdpCredsspAuth now has ownership of the buffer */
664 SecBuffer empty = { 0 };
665 *buffer = empty;
666}
667
668const SecBuffer* credssp_auth_get_output_buffer(rdpCredsspAuth* auth)
669{
670 WINPR_ASSERT(auth);
671 return &auth->output_buffer;
672}
673
674BOOL credssp_auth_have_output_token(rdpCredsspAuth* auth)
675{
676 WINPR_ASSERT(auth);
677 return auth->output_buffer.cbBuffer ? TRUE : FALSE;
678}
679
680BOOL credssp_auth_is_complete(rdpCredsspAuth* auth)
681{
682 WINPR_ASSERT(auth);
683 return auth->state == AUTH_STATE_FINAL;
684}
685
686size_t credssp_auth_trailer_size(rdpCredsspAuth* auth)
687{
688 WINPR_ASSERT(auth);
689 return auth->sizes.cbSecurityTrailer;
690}
691
692const char* credssp_auth_pkg_name(rdpCredsspAuth* auth)
693{
694 WINPR_ASSERT(auth && auth->info);
695 return auth->pkgNameA;
696}
697
698INT32 credssp_auth_sspi_error(rdpCredsspAuth* auth)
699{
700 WINPR_ASSERT(auth);
701 return auth->sspi_error;
702}
703
704void credssp_auth_tableAndContext(rdpCredsspAuth* auth, SecurityFunctionTable** ptable,
705 CtxtHandle* pcontext)
706{
707 WINPR_ASSERT(auth);
708 WINPR_ASSERT(ptable);
709 WINPR_ASSERT(pcontext);
710
711 *ptable = auth->table;
712 *pcontext = auth->context;
713}
714
715void credssp_auth_free(rdpCredsspAuth* auth)
716{
717 SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;
718 SEC_WINPR_NTLM_SETTINGS* ntlm_settings = NULL;
719
720 if (!auth)
721 return;
722
723 if (auth->table)
724 {
725 switch (auth->state)
726 {
727 case AUTH_STATE_IN_PROGRESS:
728 case AUTH_STATE_FINAL:
729 WINPR_ASSERT(auth->table->DeleteSecurityContext);
730 auth->table->DeleteSecurityContext(&auth->context);
731 /* fallthrouth */
732 WINPR_FALLTHROUGH
733 case AUTH_STATE_CREDS:
734 WINPR_ASSERT(auth->table->FreeCredentialsHandle);
735 auth->table->FreeCredentialsHandle(&auth->credentials);
736 break;
737 case AUTH_STATE_INITIAL:
738 default:
739 break;
740 }
741
742 if (auth->info)
743 {
744 WINPR_ASSERT(auth->table->FreeContextBuffer);
745 auth->table->FreeContextBuffer(auth->info);
746 }
747 }
748
749 sspi_FreeAuthIdentity(&auth->identity);
750
751 krb_settings = &auth->kerberosSettings;
752 ntlm_settings = &auth->ntlmSettings;
753
754 free(krb_settings->kdcUrl);
755 free(krb_settings->cache);
756 free(krb_settings->keytab);
757 free(krb_settings->armorCache);
758 free(krb_settings->pkinitX509Anchors);
759 free(krb_settings->pkinitX509Identity);
760 free(ntlm_settings->samFile);
761
762 free(auth->package_list);
763 free(auth->spn);
764 sspi_SecBufferFree(&auth->input_buffer);
765 sspi_SecBufferFree(&auth->output_buffer);
766 credssp_auth_update_name_cache(auth, NULL);
767 free(auth);
768}
769
770static void auth_get_sspi_module_from_reg(char** sspi_module)
771{
772 HKEY hKey = NULL;
773 DWORD dwType = 0;
774 DWORD dwSize = 0;
775
776 WINPR_ASSERT(sspi_module);
777 *sspi_module = NULL;
778
779 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey) !=
780 ERROR_SUCCESS)
781 return;
782
783 if (RegQueryValueExA(hKey, "SspiModule", NULL, &dwType, NULL, &dwSize) != ERROR_SUCCESS)
784 {
785 RegCloseKey(hKey);
786 return;
787 }
788
789 char* module = (LPSTR)calloc(dwSize + sizeof(CHAR), sizeof(char));
790 if (!module)
791 {
792 RegCloseKey(hKey);
793 return;
794 }
795
796 if (RegQueryValueExA(hKey, "SspiModule", NULL, &dwType, (BYTE*)module, &dwSize) !=
797 ERROR_SUCCESS)
798 {
799 RegCloseKey(hKey);
800 free(module);
801 return;
802 }
803
804 RegCloseKey(hKey);
805 *sspi_module = module;
806}
807
808static SecurityFunctionTable* auth_resolve_sspi_table(const rdpSettings* settings)
809{
810 char* sspi_module = NULL;
811
812 WINPR_ASSERT(settings);
813
814 if (settings->ServerMode)
815 auth_get_sspi_module_from_reg(&sspi_module);
816
817 if (sspi_module || settings->SspiModule)
818 {
819 const char* module_name = sspi_module ? sspi_module : settings->SspiModule;
820#ifdef UNICODE
821 const char* proc_name = "InitSecurityInterfaceW";
822#else
823 const char* proc_name = "InitSecurityInterfaceA";
824#endif /* UNICODE */
825
826 HMODULE hSSPI = LoadLibraryX(module_name);
827
828 if (!hSSPI)
829 {
830 WLog_ERR(TAG, "Failed to load SSPI module: %s", module_name);
831 free(sspi_module);
832 return NULL;
833 }
834
835 WLog_INFO(TAG, "Using SSPI Module: %s", module_name);
836
837 INIT_SECURITY_INTERFACE InitSecurityInterface_ptr =
838 GetProcAddressAs(hSSPI, proc_name, INIT_SECURITY_INTERFACE);
839 if (!InitSecurityInterface_ptr)
840 {
841 WLog_ERR(TAG, "Failed to load SSPI module: %s, no function %s", module_name, proc_name);
842 free(sspi_module);
843 return NULL;
844 }
845 free(sspi_module);
846 return InitSecurityInterface_ptr();
847 }
848
849 return InitSecurityInterfaceEx(0);
850}
851
852static BOOL credssp_auth_setup_identity(rdpCredsspAuth* auth)
853{
854 const rdpSettings* settings = NULL;
855 SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;
856 SEC_WINPR_NTLM_SETTINGS* ntlm_settings = NULL;
857 freerdp_peer* peer = NULL;
858
859 WINPR_ASSERT(auth);
860 WINPR_ASSERT(auth->rdp_ctx);
861
862 peer = auth->rdp_ctx->peer;
863 settings = auth->rdp_ctx->settings;
864 WINPR_ASSERT(settings);
865
866 krb_settings = &auth->kerberosSettings;
867 ntlm_settings = &auth->ntlmSettings;
868
869 if (settings->KerberosLifeTime)
870 parseKerberosDeltat(settings->KerberosLifeTime, &krb_settings->lifeTime, "lifetime");
871 if (settings->KerberosStartTime)
872 parseKerberosDeltat(settings->KerberosStartTime, &krb_settings->startTime, "starttime");
873 if (settings->KerberosRenewableLifeTime)
874 parseKerberosDeltat(settings->KerberosRenewableLifeTime, &krb_settings->renewLifeTime,
875 "renewLifeTime");
876
877 if (settings->KerberosKdcUrl)
878 {
879 krb_settings->kdcUrl = _strdup(settings->KerberosKdcUrl);
880 if (!krb_settings->kdcUrl)
881 {
882 WLog_ERR(TAG, "unable to copy kdcUrl");
883 return FALSE;
884 }
885 }
886
887 if (settings->KerberosCache)
888 {
889 krb_settings->cache = _strdup(settings->KerberosCache);
890 if (!krb_settings->cache)
891 {
892 WLog_ERR(TAG, "unable to copy cache name");
893 return FALSE;
894 }
895 }
896
897 if (settings->KerberosKeytab)
898 {
899 krb_settings->keytab = _strdup(settings->KerberosKeytab);
900 if (!krb_settings->keytab)
901 {
902 WLog_ERR(TAG, "unable to copy keytab name");
903 return FALSE;
904 }
905 }
906
907 if (settings->KerberosArmor)
908 {
909 krb_settings->armorCache = _strdup(settings->KerberosArmor);
910 if (!krb_settings->armorCache)
911 {
912 WLog_ERR(TAG, "unable to copy armorCache");
913 return FALSE;
914 }
915 }
916
917 if (settings->PkinitAnchors)
918 {
919 krb_settings->pkinitX509Anchors = _strdup(settings->PkinitAnchors);
920 if (!krb_settings->pkinitX509Anchors)
921 {
922 WLog_ERR(TAG, "unable to copy pkinitX509Anchors");
923 return FALSE;
924 }
925 }
926
927 if (settings->NtlmSamFile)
928 {
929 ntlm_settings->samFile = _strdup(settings->NtlmSamFile);
930 if (!ntlm_settings->samFile)
931 {
932 WLog_ERR(TAG, "unable to copy samFile");
933 return FALSE;
934 }
935 }
936
937 if (peer && peer->SspiNtlmHashCallback)
938 {
939 ntlm_settings->hashCallback = peer->SspiNtlmHashCallback;
940 ntlm_settings->hashCallbackArg = peer;
941 }
942
943 if (settings->AuthenticationPackageList)
944 {
945 auth->package_list = ConvertUtf8ToWCharAlloc(settings->AuthenticationPackageList, NULL);
946 if (!auth->package_list)
947 return FALSE;
948 }
949
950 auth->identity.Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
951 auth->identity.Flags |= SEC_WINNT_AUTH_IDENTITY_EXTENDED;
952
953 return TRUE;
954}
955
956BOOL credssp_auth_set_spn(rdpCredsspAuth* auth, const char* service, const char* hostname)
957{
958 size_t length = 0;
959 char* spn = NULL;
960
961 WINPR_ASSERT(auth);
962
963 if (!hostname)
964 return FALSE;
965
966 if (!service)
967 spn = _strdup(hostname);
968 else
969 {
970 length = strlen(service) + strlen(hostname) + 2;
971 spn = calloc(length + 1, sizeof(char));
972 if (!spn)
973 return FALSE;
974
975 (void)sprintf_s(spn, length, "%s/%s", service, hostname);
976 }
977 if (!spn)
978 return FALSE;
979
980#if defined(UNICODE)
981 auth->spn = ConvertUtf8ToWCharAlloc(spn, NULL);
982 free(spn);
983#else
984 auth->spn = spn;
985#endif
986
987 return TRUE;
988}
989
990static const char* parseInt(const char* v, INT32* r)
991{
992 *r = 0;
993
994 /* check that we have at least a digit */
995 if (!*v || !((*v >= '0') && (*v <= '9')))
996 return NULL;
997
998 for (; *v && (*v >= '0') && (*v <= '9'); v++)
999 {
1000 *r = (*r * 10) + (*v - '0');
1001 }
1002
1003 return v;
1004}
1005
1006static BOOL parseKerberosDeltat(const char* value, INT32* dest, const char* message)
1007{
1008 INT32 v = 0;
1009 const char* ptr = NULL;
1010
1011 WINPR_ASSERT(value);
1012 WINPR_ASSERT(dest);
1013 WINPR_ASSERT(message);
1014
1015 /* determine the format :
1016 * h:m[:s] (3:00:02) deltat in hours/minutes
1017 * <n>d<n>h<n>m<n>s 1d4h deltat in day/hours/minutes/seconds
1018 * <n> deltat in seconds
1019 */
1020 ptr = strchr(value, ':');
1021 if (ptr)
1022 {
1023 /* format like h:m[:s] */
1024 *dest = 0;
1025 value = parseInt(value, &v);
1026 if (!value || *value != ':')
1027 {
1028 WLog_ERR(TAG, "Invalid value for %s", message);
1029 return FALSE;
1030 }
1031
1032 *dest = v * 3600;
1033
1034 value = parseInt(value + 1, &v);
1035 if (!value || (*value != 0 && *value != ':') || (v > 60))
1036 {
1037 WLog_ERR(TAG, "Invalid value for %s", message);
1038 return FALSE;
1039 }
1040 *dest += v * 60;
1041
1042 if (*value == ':')
1043 {
1044 /* have optional seconds */
1045 value = parseInt(value + 1, &v);
1046 if (!value || (*value != 0) || (v > 60))
1047 {
1048 WLog_ERR(TAG, "Invalid value for %s", message);
1049 return FALSE;
1050 }
1051 *dest += v;
1052 }
1053 return TRUE;
1054 }
1055
1056 /* <n> or <n>d<n>h<n>m<n>s format */
1057 value = parseInt(value, &v);
1058 if (!value)
1059 {
1060 WLog_ERR(TAG, "Invalid value for %s", message);
1061 return FALSE;
1062 }
1063
1064 if (!*value || isspace(*value))
1065 {
1066 /* interpret that as a value in seconds */
1067 *dest = v;
1068 return TRUE;
1069 }
1070
1071 *dest = 0;
1072 do
1073 {
1074 INT32 factor = 0;
1075 INT32 maxValue = 0;
1076
1077 switch (*value)
1078 {
1079 case 'd':
1080 factor = 3600 * 24;
1081 maxValue = 0;
1082 break;
1083 case 'h':
1084 factor = 3600;
1085 maxValue = 0;
1086 break;
1087 case 'm':
1088 factor = 60;
1089 maxValue = 60;
1090 break;
1091 case 's':
1092 factor = 1;
1093 maxValue = 60;
1094 break;
1095 default:
1096 WLog_ERR(TAG, "invalid value for unit %c when parsing %s", *value, message);
1097 return FALSE;
1098 }
1099
1100 if ((maxValue > 0) && (v > maxValue))
1101 {
1102 WLog_ERR(TAG, "invalid value for unit %c when parsing %s", *value, message);
1103 return FALSE;
1104 }
1105
1106 *dest += (v * factor);
1107 value++;
1108 if (!*value)
1109 return TRUE;
1110
1111 value = parseInt(value, &v);
1112 if (!value || !*value)
1113 {
1114 WLog_ERR(TAG, "Invalid value for %s", message);
1115 return FALSE;
1116 }
1117
1118 } while (TRUE);
1119
1120 return TRUE;
1121}