diff --git a/mRemoteNG/Connection/AbstractConnectionRecord.cs b/mRemoteNG/Connection/AbstractConnectionRecord.cs index 4ce1faf3b..7d3dd58e5 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 e46d35352..5ef2e315a 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 000000000..4af75a581 --- /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 144695aa3..f4eb39537 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 9ff0ba0ba..d08c13267 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 8733b2f01..bbca400d4 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 6e9c76e84..9c6c99bdd 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 3757bf14d..2a1d1faf3 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 ||