diff --git a/.ci/Scripts/buildFreeRdp.bat b/.ci/Scripts/buildFreeRdp.bat index ebf96990c2f6..2c5842104f4e 100644 --- a/.ci/Scripts/buildFreeRdp.bat +++ b/.ci/Scripts/buildFreeRdp.bat @@ -15,9 +15,7 @@ cmd /c cmake . -B"./Build/x64" -G"Visual Studio 17 2022"^ -DWITH_CLIENT_INTERFACE=ON^ -DCHANNEL_URBDRC=OFF^ -DWITH_MEDIA_FOUNDATION=OFF^ - -DZLIB_USE_STATIC_LIBS=ON^ - -DZLIB_LIBRARY="%zlibDir%/Install/lib/zlib.lib"^ - -DZLIB_INCLUDE_DIR="%zlibDir%/Install/include"^ + -DWITH_SMARTCARD_EMULATE=OFF^ -DWITH_FFMPEG=OFF^ -DWITH_VIDEO_FFMPEG=OFF^ -DWITH_DSP_FFMPEG=OFF^ diff --git a/UiPath.FreeRdpClient/UiPath.FreeRdpClient.Tests/RdpClientTests.cs b/UiPath.FreeRdpClient/UiPath.FreeRdpClient.Tests/RdpClientTests.cs index 4a8ca277d728..b915218ab449 100644 --- a/UiPath.FreeRdpClient/UiPath.FreeRdpClient.Tests/RdpClientTests.cs +++ b/UiPath.FreeRdpClient/UiPath.FreeRdpClient.Tests/RdpClientTests.cs @@ -220,4 +220,25 @@ public async Task WrongPassword_ShouldFail() var exception = await Connect(connectionSettings).ShouldThrowAsync(); exception.Message.Contains("Logon Failed", StringComparison.InvariantCultureIgnoreCase); } + + //[Fact] // can't run everywhere, so leaving it commented for now + public async Task SmartCard() + { + var username = "user1"; + var domain = "test.lab"; + var password = "12345678"; + + var settings = new RdpConnectionSettings(username, domain, password) + { + IsSmartCardLogon = true, + //ReaderName = "Microsoft Virtual Smart Card 0", + //CspName = "Microsoft Base Smart Card Crypto Provider", + //ContainerName = "te-SmartcardV4-025548ff-3a4a-4c89-31544", + }; + + await using var _ = await Connect(settings); + _output.WriteLine("Connected, waiting 10s for session to be created..."); + await Task.Delay(10_000); + _output.WriteLine("Done"); + } } \ No newline at end of file diff --git a/UiPath.FreeRdpClient/UiPath.FreeRdpClient/Internals/FreeRdpClient.cs b/UiPath.FreeRdpClient/UiPath.FreeRdpClient/Internals/FreeRdpClient.cs index bf0516c110ef..0bd0d3d7cb86 100644 --- a/UiPath.FreeRdpClient/UiPath.FreeRdpClient/Internals/FreeRdpClient.cs +++ b/UiPath.FreeRdpClient/UiPath.FreeRdpClient/Internals/FreeRdpClient.cs @@ -46,7 +46,15 @@ public async Task Connect(RdpConnectionSettings connectionSett ScopeName = connectionSettings.ScopeName, ClientName = connectionSettings.ClientName, HostName = connectionSettings.HostName, - Port = connectionSettings.Port ?? default + Port = connectionSettings.Port ?? default, + + SmartcardSettings = new() + { + IsSmartCardLogon = connectionSettings.IsSmartCardLogon, + ReaderName = connectionSettings.ReaderName, + CspName = connectionSettings.CspName, + ContainerName = connectionSettings.ContainerName + }, }; using (await _initLock.LockAsync()) diff --git a/UiPath.FreeRdpClient/UiPath.FreeRdpClient/Internals/NativeInterface.cs b/UiPath.FreeRdpClient/UiPath.FreeRdpClient/Internals/NativeInterface.cs index 9355bb6260f4..52717c409338 100644 --- a/UiPath.FreeRdpClient/UiPath.FreeRdpClient/Internals/NativeInterface.cs +++ b/UiPath.FreeRdpClient/UiPath.FreeRdpClient/Internals/NativeInterface.cs @@ -27,6 +27,20 @@ public struct ConnectOptions [MarshalAs(UnmanagedType.BStr)] public string HostName; public int Port; + + public SmartcardSettings SmartcardSettings; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct SmartcardSettings + { + public bool IsSmartCardLogon; + [MarshalAs(UnmanagedType.BStr)] + public string? ReaderName; + [MarshalAs(UnmanagedType.BStr)] + public string? ContainerName; + [MarshalAs(UnmanagedType.BStr)] + public string? CspName; } [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] diff --git a/UiPath.FreeRdpClient/UiPath.FreeRdpClient/RdpConnectionSettings.cs b/UiPath.FreeRdpClient/UiPath.FreeRdpClient/RdpConnectionSettings.cs index 3ca4cb23d87b..a27849794f4b 100644 --- a/UiPath.FreeRdpClient/UiPath.FreeRdpClient/RdpConnectionSettings.cs +++ b/UiPath.FreeRdpClient/UiPath.FreeRdpClient/RdpConnectionSettings.cs @@ -23,6 +23,11 @@ public class RdpConnectionSettings [MaxLength(15, ErrorMessage = "Sometimes :) Windows returns only first 15 chars for a session ClientName")] public string? ClientName { get; set; } + public bool IsSmartCardLogon { get; set; } + public string? ReaderName { get; set; } + public string? CspName { get; set; } + public string? ContainerName { get; set; } + public DisconnectCallback? DisconnectCallback { get; set; } public RdpConnectionSettings(string username, string domain, string password) diff --git a/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/FreeRdpWrapper.cpp b/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/FreeRdpWrapper.cpp index 36e416207b51..10e62ca3ceb0 100644 --- a/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/FreeRdpWrapper.cpp +++ b/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/FreeRdpWrapper.cpp @@ -5,6 +5,10 @@ #pragma warning(disable : 4324 4201 4245 4996) #include #include +#include +#include +#include +#include #pragma warning(default : 4324 4201 4245 4996) #pragma once using namespace Logging; @@ -20,6 +24,21 @@ namespace FreeRdpClient return _strdup(convToUTF8.to_bytes(source).c_str()); } + wchar_t* ConvToUtf16(const char* source) + { + if (!source) // Handle null input + { + return nullptr; + } + + std::wstring_convert> converter; + std::wstring wideString = converter.from_bytes(source); + + wchar_t* wstr = new wchar_t[wideString.size() + 1]; + std::wmemcpy(wstr, wideString.c_str(), wideString.size() + 1); + return wstr; + } + class instance_data { public: @@ -98,7 +117,110 @@ namespace FreeRdpClient return instance; } - void PrepareRdpContext(rdpContext* context, const ConnectOptions* rdpOptions) + static BOOL LoadStaticChannelAddin(rdpChannels* channels, + rdpSettings* settings, const char* name, + void* data) + { + PVIRTUALCHANNELENTRY entry = NULL; + PVIRTUALCHANNELENTRY pvce = freerdp_load_channel_addin_entry( + name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX); + PVIRTUALCHANNELENTRYEX pvceex = WINPR_FUNC_PTR_CAST(pvce, PVIRTUALCHANNELENTRYEX); + + if (!pvceex) + entry = + freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); + + if (pvceex) + { + if (freerdp_channels_client_load_ex(channels, settings, pvceex, data) == 0) + { + DT_TRACE(L"loading channelEx %s", name); + return TRUE; + } + } + else if (entry) + { + if (freerdp_channels_client_load(channels, settings, entry, data) == 0) + { + DT_TRACE(L"loading channel %s", name); + return TRUE; + } + } + + return FALSE; + } + + BOOL AddStaticChannel(rdpSettings* settings, size_t count, const char* const* params) + { + ADDIN_ARGV* _args = NULL; + + if (!settings || !params || !params[0] || (count > INT_MAX)) + return FALSE; + + if (freerdp_static_channel_collection_find(settings, params[0])) + return TRUE; + + _args = freerdp_addin_argv_new(count, params); + + if (!_args) + return FALSE; + + if (!freerdp_static_channel_collection_add(settings, _args)) + { + freerdp_addin_argv_free(_args); + return FALSE; + } + + return TRUE; + } + + static BOOL LoadChannels(rdpChannels* channels, rdpSettings* settings) + { + if (!LoadStaticChannelAddin(channels, settings, RDPDR_SVC_CHANNEL_NAME, + settings)) + return FALSE; + + if (!freerdp_static_channel_collection_find(settings, RDPSND_CHANNEL_NAME)) + { + const char* const params[] = { RDPSND_CHANNEL_NAME, "sys:fake" }; + + if (!AddStaticChannel(settings, ARRAYSIZE(params), params)) + return FALSE; + } + + if (freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards)) + { + if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD)) + { + RDPDR_DEVICE* smartcard = freerdp_device_new(RDPDR_DTYP_SMARTCARD, 0, NULL); + + if (!smartcard) + return FALSE; + + if (!freerdp_device_collection_add(settings, smartcard)) + { + freerdp_device_free(smartcard); + return FALSE; + } + } + } + + for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount); i++) + { + ADDIN_ARGV* _args = static_cast(freerdp_settings_get_pointer_array_writable(settings, + FreeRDP_StaticChannelArray, i)); + + if (!LoadStaticChannelAddin(channels, settings, _args->argv[0], _args)) + return FALSE; + } + } + + BOOL LoadChannelsCore(freerdp* instance) + { + return LoadChannels(instance->context->channels, instance->context->settings); + } + + BOOL PrepareRdpContext(rdpContext* context, const ConnectOptions* rdpOptions) { context->settings->ServerHostname = ConvToUtf8(rdpOptions->HostName); @@ -140,6 +262,51 @@ namespace FreeRdpClient context->settings->ColorDepth = rdpOptions->Depth; context->settings->AllowFontSmoothing = rdpOptions->FontSmoothing; + + if (rdpOptions->smartcardSettings.IsSmartCardLogon) + { + context->settings->SmartcardLogon = TRUE; + context->settings->PasswordIsSmartcardPin = TRUE; + context->settings->RedirectSmartCards = TRUE; + context->settings->DeviceRedirection = TRUE; + context->settings->KeySpec = 1; + + context->settings->ReaderName = ConvToUtf8(rdpOptions->smartcardSettings.ReaderName); + context->settings->CspName = ConvToUtf8(rdpOptions->smartcardSettings.CspName); + context->settings->ContainerName = ConvToUtf8(rdpOptions->smartcardSettings.ContainerName); + + context->instance->LoadChannels = LoadChannelsCore; + + // Only called if multiple certificates are available for the same user + context->instance->ChooseSmartcard = [](freerdp* instance, + SmartcardCertInfo** cert_list, DWORD count, + DWORD* choice, BOOL gateway) -> BOOL + { + auto res = false; + auto containerName = ConvToUtf16(instance->context->settings->ContainerName); + + for (DWORD idx = 0; idx < count; idx++) + { + if (wcscmp(cert_list[idx]->containerName, containerName) != 0) + continue; + + *choice = idx; + res = true; + break; + } + + delete[] containerName; + return res; + }; + + if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) != CHANNEL_RC_OK) + { + DT_ERROR(L"Failed to register addin provider"); + return FALSE; + } + } + + return TRUE; } DWORD ReleaseAll(instance_data* instanceData) @@ -276,10 +443,9 @@ namespace FreeRdpClient return E_OUTOFMEMORY; rdpContext* context = instance->context; - PrepareRdpContext(context, rdpOptions); - auto connectResult = freerdp_connect(instance); - if (connectResult) + if (PrepareRdpContext(context, rdpOptions) + && freerdp_connect(instance)) { _bstr_t eventName; if (transport_start(context, rdpOptions, eventName)) diff --git a/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/FreeRdpWrapper.h b/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/FreeRdpWrapper.h index 5ecb9ac3bb59..e637f36a632f 100644 --- a/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/FreeRdpWrapper.h +++ b/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/FreeRdpWrapper.h @@ -4,6 +4,14 @@ namespace FreeRdpClient { + typedef struct + { + BOOL IsSmartCardLogon; + BSTR ReaderName; + BSTR ContainerName; + BSTR CspName; + } SmartcardSettings; + typedef struct { long Width; @@ -17,6 +25,8 @@ namespace FreeRdpClient BSTR ClientName; BSTR HostName; long Port; + + SmartcardSettings smartcardSettings; } ConnectOptions; using pFreeRdpDisconnectedCallback = void (*)(BSTR); diff --git a/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/UiPath.FreeRdpWrapper.vcxproj b/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/UiPath.FreeRdpWrapper.vcxproj index 7548d55f673e..653ecdfc4570 100644 --- a/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/UiPath.FreeRdpWrapper.vcxproj +++ b/UiPath.FreeRdpClient/UiPath.FreeRdpWrapper/UiPath.FreeRdpWrapper.vcxproj @@ -73,8 +73,8 @@ Windows true false - comsuppw.lib;comsupp.lib;freerdp3.lib;winpr3.lib;libcrypto.lib;libssl.lib;Dbghelp.lib;Secur32.lib;ntdsapi.lib;Rpcrt4.lib;Crypt32.lib;ncrypt.lib;Userenv.lib;ws2_32.lib;Shlwapi.lib;ntdll.lib;%(AdditionalDependencies) - ..\..\..\OpenSSL-VC-$(PlatformArchitecture)\lib;..\..\Build\$(PlatformTarget)\libfreerdp\$(Configuration);..\..\Build\$(PlatformTarget)\winpr\libwinpr\$(Configuration) + comsuppw.lib;comsupp.lib;freerdp3.lib;freerdp-client3.lib;winpr3.lib;remdesk-common.lib;winmm.lib;libcrypto.lib;libssl.lib;Dbghelp.lib;Secur32.lib;ntdsapi.lib;Rpcrt4.lib;Crypt32.lib;ncrypt.lib;Userenv.lib;ws2_32.lib;Shlwapi.lib;ntdll.lib;%(AdditionalDependencies) + ..\..\..\OpenSSL-VC-$(PlatformArchitecture)\lib;..\..\Build\$(PlatformTarget)\libfreerdp\$(Configuration);..\..\Build\$(PlatformTarget)\winpr\libwinpr\$(Configuration);..\..\Build\$(PlatformTarget)\client\common\$(Configuration);..\..\Build\$(PlatformTarget)\channels\remdesk\common\$(Configuration) @@ -102,8 +102,8 @@ true true false - comsuppw.lib;comsupp.lib;freerdp3.lib;winpr3.lib;libcrypto.lib;libssl.lib;Dbghelp.lib;Secur32.lib;ntdsapi.lib;Rpcrt4.lib;Crypt32.lib;ncrypt.lib;Userenv.lib;ws2_32.lib;Shlwapi.lib;ntdll.lib;%(AdditionalDependencies) - ..\..\..\OpenSSL-VC-$(PlatformArchitecture)\lib;..\..\Build\$(PlatformTarget)\libfreerdp\$(Configuration);..\..\Build\$(PlatformTarget)\winpr\libwinpr\$(Configuration) + comsuppw.lib;comsupp.lib;freerdp3.lib;freerdp-client3.lib;winpr3.lib;remdesk-common.lib;winmm.lib;libcrypto.lib;libssl.lib;Dbghelp.lib;Secur32.lib;ntdsapi.lib;Rpcrt4.lib;Crypt32.lib;ncrypt.lib;Userenv.lib;ws2_32.lib;Shlwapi.lib;ntdll.lib;%(AdditionalDependencies) + ..\..\..\OpenSSL-VC-$(PlatformArchitecture)\lib;..\..\Build\$(PlatformTarget)\libfreerdp\$(Configuration);..\..\Build\$(PlatformTarget)\winpr\libwinpr\$(Configuration);..\..\Build\$(PlatformTarget)\client\common\$(Configuration);..\..\Build\$(PlatformTarget)\channels\remdesk\common\$(Configuration)