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