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