mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 14:07:46 +08:00
Initial implementation of gatewayaccesstoken support
This commit is contained in:
@@ -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)),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
124
mRemoteNG/Connection/Protocol/RDP/RdGatewayAccessTokenHelper.cs
Normal file
124
mRemoteNG/Connection/Protocol/RDP/RdGatewayAccessTokenHelper.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
44
mRemoteNG/Language/Language.Designer.cs
generated
44
mRemoteNG/Language/Language.Designer.cs
generated
@@ -646,7 +646,7 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
internal static string CheckboxAutomaticReconnect {
|
||||
get {
|
||||
@@ -663,6 +663,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Automatically try to reconnect when disconnected from server (RDP && ICA only).
|
||||
/// </summary>
|
||||
internal static string CheckboxNoReconnect {
|
||||
get {
|
||||
return ResourceManager.GetString("CheckboxNoReconnect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This proxy server requires authentication.
|
||||
/// </summary>
|
||||
@@ -1547,7 +1556,7 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Disable Cursor blinking.
|
||||
/// Looks up a localized string similar to Disable Cursor Blinking.
|
||||
/// </summary>
|
||||
internal static string DisableCursorBlinking {
|
||||
get {
|
||||
@@ -4053,6 +4062,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the access token for Remote Desktop Gateway server..
|
||||
/// </summary>
|
||||
internal static string PropertyDescriptionRdpGatewayAccessToken {
|
||||
get {
|
||||
return ResourceManager.GetString("PropertyDescriptionRdpGatewayAccessToken", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the password of the Remote Desktop Gateway server..
|
||||
/// </summary>
|
||||
@@ -4133,8 +4151,8 @@ namespace mRemoteNG.Resources.Language {
|
||||
return ResourceManager.GetString("PropertyDescriptionRedirectDiskDrivesCustom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Select whether local disk drives should be shown on the remote host..
|
||||
/// </summary>
|
||||
internal static string PropertyDescriptionRedirectDrives {
|
||||
@@ -4909,6 +4927,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to RDP-Gateway Access Token.
|
||||
/// </summary>
|
||||
internal static string RdpGatewayAccessToken {
|
||||
get {
|
||||
return ResourceManager.GetString("RdpGatewayAccessToken", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to RDP-Gateway Domain.
|
||||
/// </summary>
|
||||
@@ -6334,6 +6361,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use RD Gateway access token.
|
||||
/// </summary>
|
||||
internal static string UseAccessToken {
|
||||
get {
|
||||
return ResourceManager.GetString("UseAccessToken", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use Console Session.
|
||||
/// </summary>
|
||||
|
||||
@@ -2334,4 +2334,13 @@ Nightly Channel includes Alphas, Betas & Release Candidates.</value>
|
||||
<data name="RedirectDiskDrivesCustom" xml:space="preserve">
|
||||
<value>Custom Drives</value>
|
||||
</data>
|
||||
<data name="PropertyDescriptionRdpGatewayAccessToken" xml:space="preserve">
|
||||
<value>Specifies the access token for Remote Desktop Gateway server.</value>
|
||||
</data>
|
||||
<data name="RdpGatewayAccessToken" xml:space="preserve">
|
||||
<value>RDP-Gateway Access Token</value>
|
||||
</data>
|
||||
<data name="UseAccessToken" xml:space="preserve">
|
||||
<value>Use RD Gateway access token</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -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 ||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
Reference in New Issue
Block a user