summaryrefslogtreecommitdiffstats
path: root/libfreerdp/core/credssp_auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'libfreerdp/core/credssp_auth.c')
-rw-r--r--libfreerdp/core/credssp_auth.c1094
1 files changed, 1094 insertions, 0 deletions
diff --git a/libfreerdp/core/credssp_auth.c b/libfreerdp/core/credssp_auth.c
new file mode 100644
index 0000000..c14dbe1
--- /dev/null
+++ b/libfreerdp/core/credssp_auth.c
@@ -0,0 +1,1094 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2022 David Fort <contact@hardening-consulting.com>
+ * Copyright 2022 Isaac Klein <fifthdegree@protonmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+
+#include <freerdp/config.h>
+#include "settings.h"
+#include <freerdp/build-config.h>
+#include <freerdp/peer.h>
+
+#include <winpr/crt.h>
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+#include <winpr/library.h>
+#include <winpr/registry.h>
+
+#include <freerdp/log.h>
+
+#include "credssp_auth.h"
+
+#define TAG FREERDP_TAG("core.auth")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
+
+enum AUTH_STATE
+{
+ AUTH_STATE_INITIAL,
+ AUTH_STATE_CREDS,
+ AUTH_STATE_IN_PROGRESS,
+ AUTH_STATE_FINAL
+};
+
+struct rdp_credssp_auth
+{
+ const rdpContext* rdp_ctx;
+ SecurityFunctionTable* table;
+ SecPkgInfo* info;
+ SEC_WINNT_AUTH_IDENTITY identity;
+ SEC_WINPR_NTLM_SETTINGS ntlmSettings;
+ SEC_WINPR_KERBEROS_SETTINGS kerberosSettings;
+ CredHandle credentials;
+ BOOL server;
+ SecPkgContext_Bindings* bindings;
+ TCHAR* spn;
+ WCHAR* package_list;
+ CtxtHandle context;
+ SecBuffer input_buffer;
+ SecBuffer output_buffer;
+ ULONG flags;
+ SecPkgContext_Sizes sizes;
+ SECURITY_STATUS sspi_error;
+ enum AUTH_STATE state;
+ char* pkgNameA;
+};
+
+static const char* credssp_auth_state_string(const rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ switch (auth->state)
+ {
+ case AUTH_STATE_INITIAL:
+ return "AUTH_STATE_INITIAL";
+ case AUTH_STATE_CREDS:
+ return "AUTH_STATE_CREDS";
+ case AUTH_STATE_IN_PROGRESS:
+ return "AUTH_STATE_IN_PROGRESS";
+ case AUTH_STATE_FINAL:
+ return "AUTH_STATE_FINAL";
+ default:
+ return "AUTH_STATE_UNKNOWN";
+ }
+}
+static BOOL parseKerberosDeltat(const char* value, INT32* dest, const char* message);
+static BOOL credssp_auth_setup_identity(rdpCredsspAuth* auth);
+static SecurityFunctionTable* auth_resolve_sspi_table(const rdpSettings* settings);
+
+static BOOL credssp_auth_update_name_cache(rdpCredsspAuth* auth, TCHAR* name)
+{
+ WINPR_ASSERT(auth);
+
+ free(auth->pkgNameA);
+ auth->pkgNameA = NULL;
+ if (name)
+#if defined(UNICODE)
+ auth->pkgNameA = ConvertWCharToUtf8Alloc(name, NULL);
+#else
+ auth->pkgNameA = _strdup(name);
+#endif
+ return TRUE;
+}
+rdpCredsspAuth* credssp_auth_new(const rdpContext* rdp_ctx)
+{
+ rdpCredsspAuth* auth = calloc(1, sizeof(rdpCredsspAuth));
+
+ if (auth)
+ auth->rdp_ctx = rdp_ctx;
+
+ return auth;
+}
+
+BOOL credssp_auth_init(rdpCredsspAuth* auth, TCHAR* pkg_name, SecPkgContext_Bindings* bindings)
+{
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->rdp_ctx);
+
+ const rdpSettings* settings = auth->rdp_ctx->settings;
+ WINPR_ASSERT(settings);
+
+ if (!credssp_auth_update_name_cache(auth, pkg_name))
+ return FALSE;
+
+ auth->table = auth_resolve_sspi_table(settings);
+ if (!auth->table)
+ {
+ WLog_ERR(TAG, "Unable to initialize sspi table");
+ return FALSE;
+ }
+
+ /* Package name will be stored in the info structure */
+ WINPR_ASSERT(auth->table->QuerySecurityPackageInfo);
+ const SECURITY_STATUS status = auth->table->QuerySecurityPackageInfo(pkg_name, &auth->info);
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "QuerySecurityPackageInfo (%s) failed with %s [0x%08X]",
+ credssp_auth_pkg_name(auth), GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ if (!credssp_auth_update_name_cache(auth, auth->info->Name))
+ return FALSE;
+
+ WLog_DBG(TAG, "Using package: %s (cbMaxToken: %u bytes)", credssp_auth_pkg_name(auth),
+ auth->info->cbMaxToken);
+
+ /* Setup common identity settings */
+ if (!credssp_auth_setup_identity(auth))
+ return FALSE;
+
+ auth->bindings = bindings;
+
+ return TRUE;
+}
+
+static BOOL credssp_auth_setup_auth_data(rdpCredsspAuth* auth,
+ const SEC_WINNT_AUTH_IDENTITY* identity,
+ SEC_WINNT_AUTH_IDENTITY_WINPR* pAuthData)
+{
+ SEC_WINNT_AUTH_IDENTITY_EXW* identityEx = NULL;
+
+ WINPR_ASSERT(pAuthData);
+ ZeroMemory(pAuthData, sizeof(SEC_WINNT_AUTH_IDENTITY_WINPR));
+
+ identityEx = &pAuthData->identity;
+ identityEx->Version = SEC_WINNT_AUTH_IDENTITY_VERSION;
+ identityEx->Length = sizeof(SEC_WINNT_AUTH_IDENTITY_EX);
+ identityEx->User = identity->User;
+ identityEx->UserLength = identity->UserLength;
+ identityEx->Domain = identity->Domain;
+ identityEx->DomainLength = identity->DomainLength;
+ identityEx->Password = identity->Password;
+ identityEx->PasswordLength = identity->PasswordLength;
+ identityEx->Flags = identity->Flags;
+ identityEx->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
+ identityEx->Flags |= SEC_WINNT_AUTH_IDENTITY_EXTENDED;
+
+ if (auth->package_list)
+ {
+ identityEx->PackageList = (UINT16*)auth->package_list;
+ identityEx->PackageListLength = _wcslen(auth->package_list);
+ }
+
+ pAuthData->ntlmSettings = &auth->ntlmSettings;
+ pAuthData->kerberosSettings = &auth->kerberosSettings;
+
+ return TRUE;
+}
+
+static BOOL credssp_auth_client_init_cred_attributes(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+
+ if (auth->kerberosSettings.kdcUrl)
+ {
+ SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
+ SecPkgCredentials_KdcProxySettingsW* secAttr = NULL;
+ SSIZE_T str_size = 0;
+ ULONG buffer_size = 0;
+
+ str_size = ConvertUtf8ToWChar(auth->kerberosSettings.kdcUrl, NULL, 0);
+ if (str_size <= 0)
+ return FALSE;
+ str_size++;
+
+ buffer_size = sizeof(SecPkgCredentials_KdcProxySettingsW) + str_size * sizeof(WCHAR);
+ secAttr = calloc(1, buffer_size);
+ if (!secAttr)
+ return FALSE;
+
+ secAttr->Version = KDC_PROXY_SETTINGS_V1;
+ secAttr->ProxyServerLength = str_size * sizeof(WCHAR);
+ secAttr->ProxyServerOffset = sizeof(SecPkgCredentials_KdcProxySettingsW);
+
+ if (ConvertUtf8ToWChar(auth->kerberosSettings.kdcUrl, (WCHAR*)(secAttr + 1), str_size) <= 0)
+ {
+ free(secAttr);
+ return FALSE;
+ }
+
+#ifdef UNICODE
+ if (auth->table->SetCredentialsAttributesW)
+ status = auth->table->SetCredentialsAttributesW(&auth->credentials,
+ SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS,
+ (void*)secAttr, buffer_size);
+ else
+ status = SEC_E_UNSUPPORTED_FUNCTION;
+#else
+ if (auth->table->SetCredentialsAttributesA)
+ status = auth->table->SetCredentialsAttributesA(&auth->credentials,
+ SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS,
+ (void*)secAttr, buffer_size);
+ else
+ status = SEC_E_UNSUPPORTED_FUNCTION;
+#endif
+
+ if (status != SEC_E_OK)
+ {
+ WLog_WARN(TAG, "Explicit Kerberos KDC URL (%s) injection is not supported",
+ auth->kerberosSettings.kdcUrl);
+ }
+
+ free(secAttr);
+ }
+
+ return TRUE;
+}
+
+BOOL credssp_auth_setup_client(rdpCredsspAuth* auth, const char* target_service,
+ const char* target_hostname, const SEC_WINNT_AUTH_IDENTITY* identity,
+ const char* pkinit)
+{
+ void* pAuthData = NULL;
+ SEC_WINNT_AUTH_IDENTITY_WINPR winprAuthData = { 0 };
+
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->table);
+ WINPR_ASSERT(auth->info);
+
+ WINPR_ASSERT(auth->state == AUTH_STATE_INITIAL);
+
+ /* Construct the service principal name */
+ if (!credssp_auth_set_spn(auth, target_service, target_hostname))
+ return FALSE;
+
+ if (identity)
+ {
+ credssp_auth_setup_auth_data(auth, identity, &winprAuthData);
+
+ if (pkinit)
+ {
+ auth->kerberosSettings.pkinitX509Identity = _strdup(pkinit);
+ if (!auth->kerberosSettings.pkinitX509Identity)
+ {
+ WLog_ERR(TAG, "unable to copy pkinitArgs");
+ return FALSE;
+ }
+ }
+
+ pAuthData = (void*)&winprAuthData;
+ }
+
+ WINPR_ASSERT(auth->table->AcquireCredentialsHandle);
+ const SECURITY_STATUS status =
+ auth->table->AcquireCredentialsHandle(NULL, auth->info->Name, SECPKG_CRED_OUTBOUND, NULL,
+ pAuthData, NULL, NULL, &auth->credentials, NULL);
+
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "AcquireCredentialsHandleA failed with %s [0x%08X]",
+ GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ if (!credssp_auth_client_init_cred_attributes(auth))
+ {
+ WLog_ERR(TAG, "Fatal error setting credential attributes");
+ return FALSE;
+ }
+
+ auth->state = AUTH_STATE_CREDS;
+ WLog_DBG(TAG, "Acquired client credentials");
+
+ return TRUE;
+}
+
+BOOL credssp_auth_setup_server(rdpCredsspAuth* auth)
+{
+ void* pAuthData = NULL;
+ SEC_WINNT_AUTH_IDENTITY_WINPR winprAuthData = { 0 };
+
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->table);
+
+ WINPR_ASSERT(auth->state == AUTH_STATE_INITIAL);
+
+ if (auth->ntlmSettings.samFile || auth->ntlmSettings.hashCallback ||
+ auth->kerberosSettings.keytab)
+ {
+ credssp_auth_setup_auth_data(auth, &auth->identity, &winprAuthData);
+ pAuthData = &winprAuthData;
+ }
+
+ WINPR_ASSERT(auth->table->AcquireCredentialsHandle);
+ const SECURITY_STATUS status =
+ auth->table->AcquireCredentialsHandle(NULL, auth->info->Name, SECPKG_CRED_INBOUND, NULL,
+ pAuthData, NULL, NULL, &auth->credentials, NULL);
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "AcquireCredentialsHandleA failed with %s [0x%08X]",
+ GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ auth->state = AUTH_STATE_CREDS;
+ WLog_DBG(TAG, "Acquired server credentials");
+
+ auth->server = TRUE;
+
+ return TRUE;
+}
+
+void credssp_auth_set_flags(rdpCredsspAuth* auth, ULONG flags)
+{
+ WINPR_ASSERT(auth);
+ auth->flags = flags;
+}
+
+/**
+ * SSPI Client Ceremony
+ *
+ * --------------
+ * ( Client Begin )
+ * --------------
+ * |
+ * |
+ * \|/
+ * -----------+--------------
+ * | AcquireCredentialsHandle |
+ * --------------------------
+ * |
+ * |
+ * \|/
+ * -------------+--------------
+ * +---------------> / InitializeSecurityContext /
+ * | ----------------------------
+ * | |
+ * | |
+ * | \|/
+ * --------------------------- ---------+------------- ----------------------
+ * / Receive blob from server / < Received security blob? > --Yes-> / Send blob to server /
+ * -------------+------------- ----------------------- ----------------------
+ * /|\ | |
+ * | No |
+ * Yes \|/ |
+ * | ------------+----------- |
+ * +---------------- < Received Continue Needed > <-----------------+
+ * ------------------------
+ * |
+ * No
+ * \|/
+ * ------+-------
+ * ( Client End )
+ * --------------
+ */
+
+int credssp_auth_authenticate(rdpCredsspAuth* auth)
+{
+ SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
+ SecBuffer input_buffers[2] = { 0 };
+ SecBufferDesc input_buffer_desc = { SECBUFFER_VERSION, 1, input_buffers };
+ CtxtHandle* context = NULL;
+
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->table);
+
+ SecBufferDesc output_buffer_desc = { SECBUFFER_VERSION, 1, &auth->output_buffer };
+
+ switch (auth->state)
+ {
+ case AUTH_STATE_CREDS:
+ case AUTH_STATE_IN_PROGRESS:
+ break;
+ case AUTH_STATE_INITIAL:
+ case AUTH_STATE_FINAL:
+ WLog_ERR(TAG, "context in invalid state!");
+ return -1;
+ }
+
+ /* input buffer will be null on first call,
+ * context MUST be NULL on first call */
+ context = &auth->context;
+ if (!auth->context.dwLower && !auth->context.dwUpper)
+ context = NULL;
+
+ input_buffers[0] = auth->input_buffer;
+
+ if (auth->bindings)
+ {
+ input_buffer_desc.cBuffers = 2;
+
+ input_buffers[1].BufferType = SECBUFFER_CHANNEL_BINDINGS;
+ input_buffers[1].cbBuffer = auth->bindings->BindingsLength;
+ input_buffers[1].pvBuffer = auth->bindings->Bindings;
+ }
+
+ /* Free previous output buffer (presumably no longer needed) */
+ sspi_SecBufferFree(&auth->output_buffer);
+ auth->output_buffer.BufferType = SECBUFFER_TOKEN;
+ if (!sspi_SecBufferAlloc(&auth->output_buffer, auth->info->cbMaxToken))
+ return -1;
+
+ if (auth->server)
+ {
+ WINPR_ASSERT(auth->table->AcceptSecurityContext);
+ status = auth->table->AcceptSecurityContext(
+ &auth->credentials, context, &input_buffer_desc, auth->flags, SECURITY_NATIVE_DREP,
+ &auth->context, &output_buffer_desc, &auth->flags, NULL);
+ }
+ else
+ {
+ WINPR_ASSERT(auth->table->InitializeSecurityContext);
+ status = auth->table->InitializeSecurityContext(
+ &auth->credentials, context, auth->spn, auth->flags, 0, SECURITY_NATIVE_DREP,
+ &input_buffer_desc, 0, &auth->context, &output_buffer_desc, &auth->flags, NULL);
+ }
+
+ if (status == SEC_E_OK)
+ {
+ WLog_DBG(TAG, "Authentication complete (output token size: %" PRIu32 " bytes)",
+ auth->output_buffer.cbBuffer);
+ auth->state = AUTH_STATE_FINAL;
+
+ /* Not terrible if this fails, although encryption functions may run into issues down the
+ * line, still, authentication succeeded */
+ WINPR_ASSERT(auth->table->QueryContextAttributes);
+ status =
+ auth->table->QueryContextAttributes(&auth->context, SECPKG_ATTR_SIZES, &auth->sizes);
+ WLog_DBG(TAG, "Context sizes: cbMaxSignature=%d, cbSecurityTrailer=%d",
+ auth->sizes.cbMaxSignature, auth->sizes.cbSecurityTrailer);
+
+ return 1;
+ }
+ else if (status == SEC_I_CONTINUE_NEEDED)
+ {
+ WLog_DBG(TAG, "Authentication in progress... (output token size: %" PRIu32 ")",
+ auth->output_buffer.cbBuffer);
+ auth->state = AUTH_STATE_IN_PROGRESS;
+ return 0;
+ }
+ else
+ {
+ WLog_ERR(TAG, "%s failed with %s [0x%08X]",
+ auth->server ? "AcceptSecurityContext" : "InitializeSecurityContext",
+ GetSecurityStatusString(status), status);
+ auth->sspi_error = status;
+ return -1;
+ }
+}
+
+/* Plaintext is not modified; Output buffer MUST be freed if encryption succeeds */
+BOOL credssp_auth_encrypt(rdpCredsspAuth* auth, const SecBuffer* plaintext, SecBuffer* ciphertext,
+ size_t* signature_length, ULONG sequence)
+{
+ SECURITY_STATUS status = ERROR_INTERNAL_ERROR;
+ SecBuffer buffers[2] = { 0 };
+ SecBufferDesc buffer_desc = { SECBUFFER_VERSION, 2, buffers };
+ BYTE* buf = NULL;
+
+ WINPR_ASSERT(auth && auth->table);
+ WINPR_ASSERT(plaintext);
+ WINPR_ASSERT(ciphertext);
+
+ switch (auth->state)
+ {
+ case AUTH_STATE_INITIAL:
+ WLog_ERR(TAG, "Invalid state %s", credssp_auth_state_string(auth));
+ return FALSE;
+ default:
+ break;
+ }
+
+ /* Allocate consecutive memory for ciphertext and signature */
+ buf = calloc(1, plaintext->cbBuffer + auth->sizes.cbSecurityTrailer);
+ if (!buf)
+ return FALSE;
+
+ buffers[0].BufferType = SECBUFFER_TOKEN;
+ buffers[0].cbBuffer = auth->sizes.cbSecurityTrailer;
+ buffers[0].pvBuffer = buf;
+
+ buffers[1].BufferType = SECBUFFER_DATA;
+ if (plaintext->BufferType & SECBUFFER_READONLY)
+ buffers[1].BufferType |= SECBUFFER_READONLY;
+ buffers[1].pvBuffer = buf + auth->sizes.cbSecurityTrailer;
+ buffers[1].cbBuffer = plaintext->cbBuffer;
+ CopyMemory(buffers[1].pvBuffer, plaintext->pvBuffer, plaintext->cbBuffer);
+
+ WINPR_ASSERT(auth->table->EncryptMessage);
+ status = auth->table->EncryptMessage(&auth->context, 0, &buffer_desc, sequence);
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "EncryptMessage failed with %s [0x%08X]", GetSecurityStatusString(status),
+ status);
+ free(buf);
+ return FALSE;
+ }
+
+ if (buffers[0].cbBuffer < auth->sizes.cbSecurityTrailer)
+ {
+ /* The signature is smaller than cbSecurityTrailer, so shrink the excess in between */
+ MoveMemory(((BYTE*)buffers[0].pvBuffer) + buffers[0].cbBuffer, buffers[1].pvBuffer,
+ buffers[1].cbBuffer);
+ // use reported signature size as new cbSecurityTrailer value for DecryptMessage
+ auth->sizes.cbSecurityTrailer = buffers[0].cbBuffer;
+ }
+
+ ciphertext->cbBuffer = buffers[0].cbBuffer + buffers[1].cbBuffer;
+ ciphertext->pvBuffer = buf;
+
+ if (signature_length)
+ *signature_length = buffers[0].cbBuffer;
+
+ return TRUE;
+}
+
+/* Output buffer MUST be freed if decryption succeeds */
+BOOL credssp_auth_decrypt(rdpCredsspAuth* auth, const SecBuffer* ciphertext, SecBuffer* plaintext,
+ ULONG sequence)
+{
+ SecBuffer buffers[2];
+ SecBufferDesc buffer_desc = { SECBUFFER_VERSION, 2, buffers };
+ ULONG fqop = 0;
+
+ WINPR_ASSERT(auth && auth->table);
+ WINPR_ASSERT(ciphertext);
+ WINPR_ASSERT(plaintext);
+
+ switch (auth->state)
+ {
+ case AUTH_STATE_INITIAL:
+ WLog_ERR(TAG, "Invalid state %s", credssp_auth_state_string(auth));
+ return FALSE;
+ default:
+ break;
+ }
+
+ /* Sanity check: ciphertext must at least have a signature */
+ if (ciphertext->cbBuffer < auth->sizes.cbSecurityTrailer)
+ {
+ WLog_ERR(TAG, "Encrypted message buffer too small");
+ return FALSE;
+ }
+
+ /* Split the input into signature and encrypted data; we assume the signature length is equal to
+ * cbSecurityTrailer */
+ buffers[0].BufferType = SECBUFFER_TOKEN;
+ buffers[0].pvBuffer = ciphertext->pvBuffer;
+ buffers[0].cbBuffer = auth->sizes.cbSecurityTrailer;
+
+ buffers[1].BufferType = SECBUFFER_DATA;
+ if (!sspi_SecBufferAlloc(&buffers[1], ciphertext->cbBuffer - auth->sizes.cbSecurityTrailer))
+ return FALSE;
+ CopyMemory(buffers[1].pvBuffer, (BYTE*)ciphertext->pvBuffer + auth->sizes.cbSecurityTrailer,
+ buffers[1].cbBuffer);
+
+ WINPR_ASSERT(auth->table->DecryptMessage);
+ const SECURITY_STATUS status =
+ auth->table->DecryptMessage(&auth->context, &buffer_desc, sequence, &fqop);
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "DecryptMessage failed with %s [0x%08X]", GetSecurityStatusString(status),
+ status);
+ sspi_SecBufferFree(&buffers[1]);
+ return FALSE;
+ }
+
+ *plaintext = buffers[1];
+
+ return TRUE;
+}
+
+BOOL credssp_auth_impersonate(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth && auth->table);
+
+ WINPR_ASSERT(auth->table->ImpersonateSecurityContext);
+ const SECURITY_STATUS status = auth->table->ImpersonateSecurityContext(&auth->context);
+
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "ImpersonateSecurityContext failed with %s [0x%08X]",
+ GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL credssp_auth_revert_to_self(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth && auth->table);
+
+ WINPR_ASSERT(auth->table->RevertSecurityContext);
+ const SECURITY_STATUS status = auth->table->RevertSecurityContext(&auth->context);
+
+ if (status != SEC_E_OK)
+ {
+ WLog_ERR(TAG, "RevertSecurityContext failed with %s [0x%08X]",
+ GetSecurityStatusString(status), status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void credssp_auth_take_input_buffer(rdpCredsspAuth* auth, SecBuffer* buffer)
+{
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(buffer);
+
+ sspi_SecBufferFree(&auth->input_buffer);
+
+ auth->input_buffer = *buffer;
+ auth->input_buffer.BufferType = SECBUFFER_TOKEN;
+
+ /* Invalidate original, rdpCredsspAuth now has ownership of the buffer */
+ SecBuffer empty = { 0 };
+ *buffer = empty;
+}
+
+const SecBuffer* credssp_auth_get_output_buffer(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return &auth->output_buffer;
+}
+
+BOOL credssp_auth_have_output_token(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return auth->output_buffer.cbBuffer ? TRUE : FALSE;
+}
+
+BOOL credssp_auth_is_complete(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return auth->state == AUTH_STATE_FINAL;
+}
+
+size_t credssp_auth_trailer_size(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return auth->sizes.cbSecurityTrailer;
+}
+
+const char* credssp_auth_pkg_name(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth && auth->info);
+ return auth->pkgNameA;
+}
+
+UINT32 credssp_auth_sspi_error(rdpCredsspAuth* auth)
+{
+ WINPR_ASSERT(auth);
+ return (UINT32)auth->sspi_error;
+}
+
+void credssp_auth_free(rdpCredsspAuth* auth)
+{
+ SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;
+ SEC_WINPR_NTLM_SETTINGS* ntlm_settings = NULL;
+
+ if (!auth)
+ return;
+
+ if (auth->table)
+ {
+ switch (auth->state)
+ {
+ case AUTH_STATE_IN_PROGRESS:
+ case AUTH_STATE_FINAL:
+ WINPR_ASSERT(auth->table->DeleteSecurityContext);
+ auth->table->DeleteSecurityContext(&auth->context);
+ /* fallthrouth */
+ WINPR_FALLTHROUGH
+ case AUTH_STATE_CREDS:
+ WINPR_ASSERT(auth->table->FreeCredentialsHandle);
+ auth->table->FreeCredentialsHandle(&auth->credentials);
+ break;
+ case AUTH_STATE_INITIAL:
+ default:
+ break;
+ }
+
+ if (auth->info)
+ {
+ WINPR_ASSERT(auth->table->FreeContextBuffer);
+ auth->table->FreeContextBuffer(auth->info);
+ }
+ }
+
+ sspi_FreeAuthIdentity(&auth->identity);
+
+ krb_settings = &auth->kerberosSettings;
+ ntlm_settings = &auth->ntlmSettings;
+
+ free(krb_settings->kdcUrl);
+ free(krb_settings->cache);
+ free(krb_settings->keytab);
+ free(krb_settings->armorCache);
+ free(krb_settings->pkinitX509Anchors);
+ free(krb_settings->pkinitX509Identity);
+ free(ntlm_settings->samFile);
+
+ free(auth->package_list);
+ free(auth->spn);
+ sspi_SecBufferFree(&auth->input_buffer);
+ sspi_SecBufferFree(&auth->output_buffer);
+ credssp_auth_update_name_cache(auth, NULL);
+ free(auth);
+}
+
+static void auth_get_sspi_module_from_reg(char** sspi_module)
+{
+ HKEY hKey = NULL;
+ DWORD dwType = 0;
+ DWORD dwSize = 0;
+
+ WINPR_ASSERT(sspi_module);
+ *sspi_module = NULL;
+
+ if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey) !=
+ ERROR_SUCCESS)
+ return;
+
+ if (RegQueryValueExA(hKey, "SspiModule", NULL, &dwType, NULL, &dwSize) != ERROR_SUCCESS)
+ {
+ RegCloseKey(hKey);
+ return;
+ }
+
+ char* module = (LPSTR)calloc(dwSize + sizeof(CHAR), sizeof(char));
+ if (!module)
+ {
+ RegCloseKey(hKey);
+ return;
+ }
+
+ if (RegQueryValueExA(hKey, "SspiModule", NULL, &dwType, (BYTE*)module, &dwSize) !=
+ ERROR_SUCCESS)
+ {
+ RegCloseKey(hKey);
+ free(module);
+ return;
+ }
+
+ RegCloseKey(hKey);
+ *sspi_module = module;
+}
+
+static SecurityFunctionTable* auth_resolve_sspi_table(const rdpSettings* settings)
+{
+ char* sspi_module = NULL;
+
+ WINPR_ASSERT(settings);
+
+ if (settings->ServerMode)
+ auth_get_sspi_module_from_reg(&sspi_module);
+
+ if (sspi_module || settings->SspiModule)
+ {
+ INIT_SECURITY_INTERFACE InitSecurityInterface_ptr = NULL;
+ const char* module_name = sspi_module ? sspi_module : settings->SspiModule;
+#ifdef UNICODE
+ const char* proc_name = "InitSecurityInterfaceW";
+#else
+ const char* proc_name = "InitSecurityInterfaceA";
+#endif /* UNICODE */
+
+ HMODULE hSSPI = LoadLibraryX(module_name);
+
+ if (!hSSPI)
+ {
+ WLog_ERR(TAG, "Failed to load SSPI module: %s", module_name);
+ return FALSE;
+ }
+
+ WLog_INFO(TAG, "Using SSPI Module: %s", module_name);
+
+ InitSecurityInterface_ptr = (INIT_SECURITY_INTERFACE)GetProcAddress(hSSPI, proc_name);
+
+ free(sspi_module);
+ return InitSecurityInterface_ptr();
+ }
+
+ return InitSecurityInterfaceEx(0);
+}
+
+static BOOL credssp_auth_setup_identity(rdpCredsspAuth* auth)
+{
+ const rdpSettings* settings = NULL;
+ SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;
+ SEC_WINPR_NTLM_SETTINGS* ntlm_settings = NULL;
+ freerdp_peer* peer = NULL;
+
+ WINPR_ASSERT(auth);
+ WINPR_ASSERT(auth->rdp_ctx);
+
+ peer = auth->rdp_ctx->peer;
+ settings = auth->rdp_ctx->settings;
+ WINPR_ASSERT(settings);
+
+ krb_settings = &auth->kerberosSettings;
+ ntlm_settings = &auth->ntlmSettings;
+
+ if (settings->KerberosLifeTime)
+ parseKerberosDeltat(settings->KerberosLifeTime, &krb_settings->lifeTime, "lifetime");
+ if (settings->KerberosStartTime)
+ parseKerberosDeltat(settings->KerberosStartTime, &krb_settings->startTime, "starttime");
+ if (settings->KerberosRenewableLifeTime)
+ parseKerberosDeltat(settings->KerberosRenewableLifeTime, &krb_settings->renewLifeTime,
+ "renewLifeTime");
+
+ if (settings->KerberosKdcUrl)
+ {
+ krb_settings->kdcUrl = _strdup(settings->KerberosKdcUrl);
+ if (!krb_settings->kdcUrl)
+ {
+ WLog_ERR(TAG, "unable to copy kdcUrl");
+ return FALSE;
+ }
+ }
+
+ if (settings->KerberosCache)
+ {
+ krb_settings->cache = _strdup(settings->KerberosCache);
+ if (!krb_settings->cache)
+ {
+ WLog_ERR(TAG, "unable to copy cache name");
+ return FALSE;
+ }
+ }
+
+ if (settings->KerberosKeytab)
+ {
+ krb_settings->keytab = _strdup(settings->KerberosKeytab);
+ if (!krb_settings->keytab)
+ {
+ WLog_ERR(TAG, "unable to copy keytab name");
+ return FALSE;
+ }
+ }
+
+ if (settings->KerberosArmor)
+ {
+ krb_settings->armorCache = _strdup(settings->KerberosArmor);
+ if (!krb_settings->armorCache)
+ {
+ WLog_ERR(TAG, "unable to copy armorCache");
+ return FALSE;
+ }
+ }
+
+ if (settings->PkinitAnchors)
+ {
+ krb_settings->pkinitX509Anchors = _strdup(settings->PkinitAnchors);
+ if (!krb_settings->pkinitX509Anchors)
+ {
+ WLog_ERR(TAG, "unable to copy pkinitX509Anchors");
+ return FALSE;
+ }
+ }
+
+ if (settings->NtlmSamFile)
+ {
+ ntlm_settings->samFile = _strdup(settings->NtlmSamFile);
+ if (!ntlm_settings->samFile)
+ {
+ WLog_ERR(TAG, "unable to copy samFile");
+ return FALSE;
+ }
+ }
+
+ if (peer && peer->SspiNtlmHashCallback)
+ {
+ ntlm_settings->hashCallback = peer->SspiNtlmHashCallback;
+ ntlm_settings->hashCallbackArg = peer;
+ }
+
+ if (settings->AuthenticationPackageList)
+ {
+ auth->package_list = ConvertUtf8ToWCharAlloc(settings->AuthenticationPackageList, NULL);
+ if (!auth->package_list)
+ return FALSE;
+ }
+
+ auth->identity.Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE;
+ auth->identity.Flags |= SEC_WINNT_AUTH_IDENTITY_EXTENDED;
+
+ return TRUE;
+}
+
+BOOL credssp_auth_set_spn(rdpCredsspAuth* auth, const char* service, const char* hostname)
+{
+ size_t length = 0;
+ char* spn = NULL;
+
+ WINPR_ASSERT(auth);
+
+ if (!hostname)
+ return FALSE;
+
+ if (!service)
+ spn = _strdup(hostname);
+ else
+ {
+ length = strlen(service) + strlen(hostname) + 2;
+ spn = calloc(length + 1, sizeof(char));
+ if (!spn)
+ return FALSE;
+
+ sprintf_s(spn, length, "%s/%s", service, hostname);
+ }
+ if (!spn)
+ return FALSE;
+
+#if defined(UNICODE)
+ auth->spn = ConvertUtf8ToWCharAlloc(spn, NULL);
+ free(spn);
+#else
+ auth->spn = spn;
+#endif
+
+ return TRUE;
+}
+
+static const char* parseInt(const char* v, INT32* r)
+{
+ *r = 0;
+
+ /* check that we have at least a digit */
+ if (!*v || !((*v >= '0') && (*v <= '9')))
+ return NULL;
+
+ for (; *v && (*v >= '0') && (*v <= '9'); v++)
+ {
+ *r = (*r * 10) + (*v - '0');
+ }
+
+ return v;
+}
+
+static BOOL parseKerberosDeltat(const char* value, INT32* dest, const char* message)
+{
+ INT32 v = 0;
+ const char* ptr = NULL;
+
+ WINPR_ASSERT(value);
+ WINPR_ASSERT(dest);
+ WINPR_ASSERT(message);
+
+ /* determine the format :
+ * h:m[:s] (3:00:02) deltat in hours/minutes
+ * <n>d<n>h<n>m<n>s 1d4h deltat in day/hours/minutes/seconds
+ * <n> deltat in seconds
+ */
+ ptr = strchr(value, ':');
+ if (ptr)
+ {
+ /* format like h:m[:s] */
+ *dest = 0;
+ value = parseInt(value, &v);
+ if (!value || *value != ':')
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+
+ *dest = v * 3600;
+
+ value = parseInt(value + 1, &v);
+ if (!value || (*value != 0 && *value != ':') || (v > 60))
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+ *dest += v * 60;
+
+ if (*value == ':')
+ {
+ /* have optional seconds */
+ value = parseInt(value + 1, &v);
+ if (!value || (*value != 0) || (v > 60))
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+ *dest += v;
+ }
+ return TRUE;
+ }
+
+ /* <n> or <n>d<n>h<n>m<n>s format */
+ value = parseInt(value, &v);
+ if (!value)
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+
+ if (!*value || isspace(*value))
+ {
+ /* interpret that as a value in seconds */
+ *dest = v;
+ return TRUE;
+ }
+
+ *dest = 0;
+ do
+ {
+ INT32 factor = 0;
+ INT32 maxValue = 0;
+
+ switch (*value)
+ {
+ case 'd':
+ factor = 3600 * 24;
+ maxValue = 0;
+ break;
+ case 'h':
+ factor = 3600;
+ maxValue = 0;
+ break;
+ case 'm':
+ factor = 60;
+ maxValue = 60;
+ break;
+ case 's':
+ factor = 1;
+ maxValue = 60;
+ break;
+ default:
+ WLog_ERR(TAG, "invalid value for unit %c when parsing %s", *value, message);
+ return FALSE;
+ }
+
+ if ((maxValue > 0) && (v > maxValue))
+ {
+ WLog_ERR(TAG, "invalid value for unit %c when parsing %s", *value, message);
+ return FALSE;
+ }
+
+ *dest += (v * factor);
+ value++;
+ if (!*value)
+ return TRUE;
+
+ value = parseInt(value, &v);
+ if (!value || !*value)
+ {
+ WLog_ERR(TAG, "Invalid value for %s", message);
+ return FALSE;
+ }
+
+ } while (TRUE);
+
+ return TRUE;
+}