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