From f1fa40f6fd025c80770f3efa76726917feccbd0b Mon Sep 17 00:00:00 2001 From: Jure Purgar Date: Tue, 18 Jul 2023 16:07:06 +0200 Subject: [PATCH 1/2] Initial implementation of gatewayaccesstoken support --- .../Connection/AbstractConnectionRecord.cs | 12 ++ .../RDP/RDGatewayUseConnectionCredentials.cs | 5 +- .../RDP/RdGatewayAccessTokenHelper.cs | 124 ++++++++++++++++++ .../Connection/Protocol/RDP/RdpProtocol.cs | 15 ++- .../Connection/Protocol/RDP/RdpProtocol7.cs | 9 ++ mRemoteNG/Language/Language.Designer.cs | 44 ++++++- mRemoteNG/Language/Language.resx | 9 ++ .../ConnectionInfoPropertyGrid.cs | 11 ++ .../ConnectionInfoPropertyGrid.resx | 120 +++++++++++++++++ 9 files changed, 341 insertions(+), 8 deletions(-) create mode 100644 mRemoteNG/Connection/Protocol/RDP/RdGatewayAccessTokenHelper.cs create mode 100644 mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.resx diff --git a/mRemoteNG/Connection/AbstractConnectionRecord.cs b/mRemoteNG/Connection/AbstractConnectionRecord.cs index 4ce1faf3..7d3dd58e 100644 --- a/mRemoteNG/Connection/AbstractConnectionRecord.cs +++ b/mRemoteNG/Connection/AbstractConnectionRecord.cs @@ -58,6 +58,7 @@ namespace mRemoteNG.Connection private string _rdGatewayUsername; private string _rdGatewayPassword; private string _rdGatewayDomain; + private string _rdGatewayAccessToken; private ExternalCredentialProvider _rdGatewayExternalCredentialProvider; private string _rdGatewayUserViaAPI = ""; @@ -530,6 +531,17 @@ namespace mRemoteNG.Connection set => SetField(ref _rdGatewayPassword, value, "RDGatewayPassword"); } + [LocalizedAttributes.LocalizedCategory(nameof(Language.RDPGateway), 4), + LocalizedAttributes.LocalizedDisplayName(nameof(Language.RdpGatewayAccessToken)), + LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionRdpGatewayAccessToken)), + PasswordPropertyText(true), + AttributeUsedInProtocol(ProtocolType.RDP)] + public string RDGatewayAccessToken + { + get => GetPropertyValue("RDGatewayAccessToken", _rdGatewayAccessToken); + set => SetField(ref _rdGatewayAccessToken, value, "RDGatewayAccessToken"); + } + [LocalizedAttributes.LocalizedCategory(nameof(Language.RDPGateway), 4), LocalizedAttributes.LocalizedDisplayName(nameof(Language.RdpGatewayDomain)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionRDGatewayDomain)), diff --git a/mRemoteNG/Connection/Protocol/RDP/RDGatewayUseConnectionCredentials.cs b/mRemoteNG/Connection/Protocol/RDP/RDGatewayUseConnectionCredentials.cs index e46d3535..5ef2e315 100644 --- a/mRemoteNG/Connection/Protocol/RDP/RDGatewayUseConnectionCredentials.cs +++ b/mRemoteNG/Connection/Protocol/RDP/RDGatewayUseConnectionCredentials.cs @@ -15,6 +15,9 @@ namespace mRemoteNG.Connection.Protocol.RDP SmartCard = 2, [LocalizedAttributes.LocalizedDescription(nameof(Language.UseExternalCredentialProvider))] - ExternalCredentialProvider = 3 + ExternalCredentialProvider = 3, + + [LocalizedAttributes.LocalizedDescription(nameof(Language.UseAccessToken))] + AccessToken = 4 } } \ No newline at end of file diff --git a/mRemoteNG/Connection/Protocol/RDP/RdGatewayAccessTokenHelper.cs b/mRemoteNG/Connection/Protocol/RDP/RdGatewayAccessTokenHelper.cs new file mode 100644 index 00000000..4af75a58 --- /dev/null +++ b/mRemoteNG/Connection/Protocol/RDP/RdGatewayAccessTokenHelper.cs @@ -0,0 +1,124 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + + +namespace mRemoteNG.Connection.Protocol.RDP +{ + internal static class RdGatewayAccessTokenHelper + { + public static string EncryptAuthCookieString(string cookieString) + { + byte[] cookieBytes = TsCryptEncryptString(cookieString); + + if (cookieBytes != null) + { + return Convert.ToBase64String(cookieBytes); + } + + return null; + } + + public static string DecryptAuthCookieString(string cookieString) //TODO: decrypt is newer use, should we remove it? + { + return TsCryptDecryptString(Convert.FromBase64String(cookieString)); + } + + + [StructLayout(LayoutKind.Sequential)] + struct CryptProtectPromptStruct + { + public int Size; + public int Flags; + public IntPtr Window; + public string Message; + } + + [StructLayout(LayoutKind.Sequential)] + struct DataBlob + { + public int Size; + public IntPtr Data; + } + + private const int CRYPTPROTECT_LOCAL_MACHINE = 0x00000004; + private const int CRYPTPROTECT_UI_FORBIDDEN = 0x00000001; + private const int CRYPTPROTECT_AUDIT = 0x00000010; + + [DllImport("crypt32.dll", CharSet = CharSet.Unicode)] + private static extern bool CryptProtectData( + ref DataBlob dataIn, + IntPtr description, + IntPtr optionalEntropy, + IntPtr reserved, + IntPtr promptStruct, + int flags, + out DataBlob dataOut); + + [DllImport("crypt32.dll", CharSet = CharSet.Unicode)] //TODO: decrypt is newer use, should we remove it? + private static extern bool CryptUnprotectData( + ref DataBlob dataIn, + IntPtr description, + IntPtr optionalEntropy, + IntPtr reserved, + IntPtr promptStruct, + int flags, + out DataBlob dataOut); + + private static byte[] TsCryptEncryptString(string inputString) + { + DataBlob inputBlob; + DataBlob outputBlob; + byte[] outputData = null; + + byte[] stringBytes = Encoding.Unicode.GetBytes(inputString); + byte[] inputData = new byte[stringBytes.Length + 2]; + Buffer.BlockCopy(stringBytes, 0, inputData, 0, stringBytes.Length); + + inputBlob.Size = inputData.Length; + inputBlob.Data = Marshal.AllocHGlobal(inputData.Length); + Marshal.Copy(inputData, 0, inputBlob.Data, inputBlob.Size); + + if (CryptProtectData(ref inputBlob, IntPtr.Zero, IntPtr.Zero, + IntPtr.Zero, IntPtr.Zero, CRYPTPROTECT_UI_FORBIDDEN, out outputBlob)) + { + outputData = new byte[outputBlob.Size]; + Marshal.Copy(outputBlob.Data, outputData, 0, outputBlob.Size); + } + + Marshal.FreeHGlobal(inputBlob.Data); + Marshal.FreeHGlobal(outputBlob.Data); + + return outputData; + } + + private static string TsCryptDecryptString(byte[] inputBytes) //TODO: decrypt is newer use, should we remove it? + { + DataBlob inputBlob; + DataBlob outputBlob; + byte[] outputData = null; + + inputBlob.Size = inputBytes.Length; + inputBlob.Data = Marshal.AllocHGlobal(inputBytes.Length); + Marshal.Copy(inputBytes, 0, inputBlob.Data, inputBlob.Size); + + if (CryptUnprotectData(ref inputBlob, IntPtr.Zero, IntPtr.Zero, + IntPtr.Zero, IntPtr.Zero, CRYPTPROTECT_UI_FORBIDDEN, out outputBlob)) + { + outputData = new byte[outputBlob.Size]; + Marshal.Copy(outputBlob.Data, outputData, 0, outputBlob.Size); + } + + Marshal.FreeHGlobal(inputBlob.Data); + Marshal.FreeHGlobal(outputBlob.Data); + + if (outputData != null) + { + return Encoding.Unicode.GetString(outputData).TrimEnd((Char)0); + } + + return null; + } + + } +} diff --git a/mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs b/mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs index 144695aa..f4eb3953 100644 --- a/mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs +++ b/mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs @@ -428,9 +428,18 @@ namespace mRemoteNG.Connection.Protocol.RDP } } - _rdpClient.TransportSettings2.GatewayUsername = gwu; - _rdpClient.TransportSettings2.GatewayPassword = gwp; - _rdpClient.TransportSettings2.GatewayDomain = gwd; + + if (connectionInfo.RDGatewayUseConnectionCredentials != RDGatewayUseConnectionCredentials.AccessToken) + { + _rdpClient.TransportSettings2.GatewayUsername = gwu; + _rdpClient.TransportSettings2.GatewayPassword = gwp; + _rdpClient.TransportSettings2.GatewayDomain = gwd; + } + else + { + //TODO: should we check client version and throw if it is less than 7 + } + break; } } diff --git a/mRemoteNG/Connection/Protocol/RDP/RdpProtocol7.cs b/mRemoteNG/Connection/Protocol/RDP/RdpProtocol7.cs index 9ff0ba0b..d08c1326 100644 --- a/mRemoteNG/Connection/Protocol/RDP/RdpProtocol7.cs +++ b/mRemoteNG/Connection/Protocol/RDP/RdpProtocol7.cs @@ -37,6 +37,15 @@ namespace mRemoteNG.Connection.Protocol.RDP if (connectionInfo.UseEnhancedMode) RdpClient7.AdvancedSettings7.PCB += ";EnhancedMode=1"; } + + if (connectionInfo.RDGatewayUseConnectionCredentials == RDGatewayUseConnectionCredentials.AccessToken) + { + var authToken = connectionInfo.RDGatewayAccessToken; + var encryptedAuthToken = RdGatewayAccessTokenHelper.EncryptAuthCookieString(authToken); + RdpClient7.TransportSettings3.GatewayEncryptedAuthCookie = encryptedAuthToken; + RdpClient7.TransportSettings3.GatewayEncryptedAuthCookieSize = (uint)encryptedAuthToken.Length; + RdpClient7.TransportSettings3.GatewayCredsSource = 5; + } } catch (Exception ex) { diff --git a/mRemoteNG/Language/Language.Designer.cs b/mRemoteNG/Language/Language.Designer.cs index 8733b2f0..bbca400d 100644 --- a/mRemoteNG/Language/Language.Designer.cs +++ b/mRemoteNG/Language/Language.Designer.cs @@ -646,7 +646,7 @@ namespace mRemoteNG.Resources.Language { } /// - /// Looks up a localized string similar to Automatically try to reconnect when disconnected from server (RDP && ICA only). + /// Looks up a localized string similar to Display reconnection dialog when disconnected from server (RDP && ICA only). /// internal static string CheckboxAutomaticReconnect { get { @@ -663,6 +663,15 @@ namespace mRemoteNG.Resources.Language { } } + /// + /// Looks up a localized string similar to Automatically try to reconnect when disconnected from server (RDP && ICA only). + /// + internal static string CheckboxNoReconnect { + get { + return ResourceManager.GetString("CheckboxNoReconnect", resourceCulture); + } + } + /// /// Looks up a localized string similar to This proxy server requires authentication. /// @@ -1547,7 +1556,7 @@ namespace mRemoteNG.Resources.Language { } /// - /// Looks up a localized string similar to Disable Cursor blinking. + /// Looks up a localized string similar to Disable Cursor Blinking. /// internal static string DisableCursorBlinking { get { @@ -4053,6 +4062,15 @@ namespace mRemoteNG.Resources.Language { } } + /// + /// Looks up a localized string similar to Specifies the access token for Remote Desktop Gateway server.. + /// + internal static string PropertyDescriptionRdpGatewayAccessToken { + get { + return ResourceManager.GetString("PropertyDescriptionRdpGatewayAccessToken", resourceCulture); + } + } + /// /// Looks up a localized string similar to Specifies the password of the Remote Desktop Gateway server.. /// @@ -4133,8 +4151,8 @@ namespace mRemoteNG.Resources.Language { return ResourceManager.GetString("PropertyDescriptionRedirectDiskDrivesCustom", resourceCulture); } } - - /// + + /// /// Looks up a localized string similar to Select whether local disk drives should be shown on the remote host.. /// internal static string PropertyDescriptionRedirectDrives { @@ -4909,6 +4927,15 @@ namespace mRemoteNG.Resources.Language { } } + /// + /// Looks up a localized string similar to RDP-Gateway Access Token. + /// + internal static string RdpGatewayAccessToken { + get { + return ResourceManager.GetString("RdpGatewayAccessToken", resourceCulture); + } + } + /// /// Looks up a localized string similar to RDP-Gateway Domain. /// @@ -6334,6 +6361,15 @@ namespace mRemoteNG.Resources.Language { } } + /// + /// Looks up a localized string similar to Use RD Gateway access token. + /// + internal static string UseAccessToken { + get { + return ResourceManager.GetString("UseAccessToken", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use Console Session. /// diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx index 6e9c76e8..9c6c99bd 100644 --- a/mRemoteNG/Language/Language.resx +++ b/mRemoteNG/Language/Language.resx @@ -2334,4 +2334,13 @@ Nightly Channel includes Alphas, Betas & Release Candidates. Custom Drives + + Specifies the access token for Remote Desktop Gateway server. + + + RDP-Gateway Access Token + + + Use RD Gateway access token + \ No newline at end of file diff --git a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs index 3757bf14..2a1d1faf 100644 --- a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs +++ b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs @@ -267,6 +267,7 @@ namespace mRemoteNG.UI.Controls.ConnectionInfoPropertyGrid strHide.Add(nameof(AbstractConnectionRecord.RDGatewayPassword)); strHide.Add(nameof(AbstractConnectionRecord.RDGatewayUseConnectionCredentials)); strHide.Add(nameof(AbstractConnectionRecord.RDGatewayUsername)); + strHide.Add(nameof(AbstractConnectionRecord.RDGatewayAccessToken)); } else if (SelectedConnectionInfo.RDGatewayUseConnectionCredentials == RDGatewayUseConnectionCredentials.Yes || SelectedConnectionInfo.RDGatewayUseConnectionCredentials == RDGatewayUseConnectionCredentials.SmartCard) @@ -276,12 +277,22 @@ namespace mRemoteNG.UI.Controls.ConnectionInfoPropertyGrid strHide.Add(nameof(AbstractConnectionRecord.RDGatewayUsername)); strHide.Add(nameof(AbstractConnectionRecord.RDGatewayExternalCredentialProvider)); strHide.Add(nameof(AbstractConnectionRecord.RDGatewayUserViaAPI)); + strHide.Add(nameof(AbstractConnectionRecord.RDGatewayAccessToken)); } else if (SelectedConnectionInfo.RDGatewayUseConnectionCredentials == RDGatewayUseConnectionCredentials.ExternalCredentialProvider) { strHide.Add(nameof(AbstractConnectionRecord.RDGatewayDomain)); strHide.Add(nameof(AbstractConnectionRecord.RDGatewayPassword)); strHide.Add(nameof(AbstractConnectionRecord.RDGatewayUsername)); + strHide.Add(nameof(AbstractConnectionRecord.RDGatewayAccessToken)); + } + else if (SelectedConnectionInfo.RDGatewayUseConnectionCredentials == RDGatewayUseConnectionCredentials.AccessToken) + { + strHide.Add(nameof(AbstractConnectionRecord.RDGatewayDomain)); + strHide.Add(nameof(AbstractConnectionRecord.RDGatewayPassword)); + strHide.Add(nameof(AbstractConnectionRecord.RDGatewayUsername)); + strHide.Add(nameof(AbstractConnectionRecord.RDGatewayExternalCredentialProvider)); + strHide.Add(nameof(AbstractConnectionRecord.RDGatewayUserViaAPI)); } if (!(SelectedConnectionInfo.Resolution == RDPResolutions.FitToWindow || diff --git a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.resx b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file From ff8044b09b575c60460629bb649ec6232d1f9628 Mon Sep 17 00:00:00 2001 From: Jure Purgar Date: Tue, 18 Jul 2023 16:23:48 +0200 Subject: [PATCH 2/2] Delete resx --- .../ConnectionInfoPropertyGrid.resx | 120 ------------------ 1 file changed, 120 deletions(-) delete mode 100644 mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.resx diff --git a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.resx b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.resx deleted file mode 100644 index 1af7de15..00000000 --- a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file