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