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