diff options
author | tecxx <tecxx@rrs.at> | 2022-09-07 15:08:29 +0300 |
---|---|---|
committer | tecxx <tecxx@rrs.at> | 2022-09-07 15:08:29 +0300 |
commit | d9c01148b7330cdb016d308f8c3c3cfd7e3211bb (patch) | |
tree | d728a38b0df7b21031f77f0497470cbb16a81728 | |
parent | 2b3cfd992fcb4c0dd1628cc5eb0baa2e06a2a305 (diff) |
support extraction of SSH private keys from external credential provider (DSS)
supported formats: rsa, rsa with passphrase, putty private key
-rw-r--r-- | ExternalConnectors/DSS/SecretServerInterface.cs | 114 | ||||
-rw-r--r-- | ExternalConnectors/DSS/SecretServerRestClient.cs | 11 | ||||
-rw-r--r-- | ExternalConnectors/ExternalConnectors.csproj | 1 | ||||
-rw-r--r-- | ExternalConnectors/PuttyKeyFileGenerator.cs | 109 | ||||
-rw-r--r-- | mRemoteNG/Connection/Protocol/PuttyBase.cs | 53 | ||||
-rw-r--r-- | mRemoteNG/Connection/Protocol/RDP/RdpProtocol6.cs | 10 | ||||
-rw-r--r-- | mRemoteNG/Language/Language.resx | 4 | ||||
-rw-r--r-- | mRemoteNG/Properties/OptionsCredentialsPage.Designer.cs | 25 | ||||
-rw-r--r-- | mRemoteNG/UI/Forms/OptionsPages/CredentialsPage.cs | 4 |
9 files changed, 296 insertions, 35 deletions
diff --git a/ExternalConnectors/DSS/SecretServerInterface.cs b/ExternalConnectors/DSS/SecretServerInterface.cs index 767ec4de..1a6f3e37 100644 --- a/ExternalConnectors/DSS/SecretServerInterface.cs +++ b/ExternalConnectors/DSS/SecretServerInterface.cs @@ -1,6 +1,11 @@ using Microsoft.Win32; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Security; using SecretServerAuthentication.DSS; using SecretServerRestClient.DSS; +using System.Security.Cryptography; namespace ExternalConnectors.DSS { @@ -114,25 +119,22 @@ namespace ExternalConnectors.DSS } } - private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain) + private static SecretsServiceClient ConstructSecretsServiceClient() { string baseURL = SSConnectionData.ssUrl; - - SecretModel secret; if (SSConnectionData.ssSSO) { // REQUIRES IIS CONFIG! https://docs.thycotic.com/ss/11.0.0/api-scripting/webservice-iwa-powershell var handler = new HttpClientHandler() { UseDefaultCredentials = true }; - using (var httpClient = new HttpClient(handler)) + var httpClient = new HttpClient(handler); { // Call REST API: - var client = new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient); - secret = client.GetSecretAsync(false, true, secretID, null).Result; + return new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient); } } else { - using (var httpClient = new HttpClient()) + var httpClient = new HttpClient(); { var token = GetToken(); @@ -140,15 +142,22 @@ namespace ExternalConnectors.DSS httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); // Call REST API: - var client = new SecretsServiceClient($"{baseURL}/api", httpClient); - secret = client.GetSecretAsync(false, true, secretID, null).Result; + return new SecretsServiceClient($"{baseURL}/api", httpClient); } } + } + private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey) + { + var client = ConstructSecretsServiceClient(); + SecretModel secret = client.GetSecretAsync(false, true, secretID, null).Result; + // clear return variables secretDomain = ""; secretUsername = ""; secretPassword = ""; + privatekey = ""; + string privatekeypassphrase = ""; // parse data and extract what we need foreach (var item in secret.Items) @@ -159,10 +168,91 @@ namespace ExternalConnectors.DSS secretUsername = item.ItemValue; else if (item.FieldName.ToLower().Equals("password")) secretPassword = item.ItemValue; + else if (item.FieldName.ToLower().Equals("private key")) + { + client.ReadResponseNoJSONConvert = true; + privatekey = client.GetFieldAsync(false, false, secretID, "private-key").Result; + client.ReadResponseNoJSONConvert = false; + } + else if (item.FieldName.ToLower().Equals("private key passphrase")) + privatekeypassphrase = item.ItemValue; } + // need to decode the private key? + if (!string.IsNullOrEmpty(privatekeypassphrase)) + { + try + { + var key = DecodePrivateKey(privatekey, privatekeypassphrase); + privatekey = key; + } + catch(Exception) + { + + } + } + + // conversion to putty format necessary? + if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2")) + { + try + { + RSACryptoServiceProvider key = ImportPrivateKey(privatekey); + privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key); + } + catch (Exception) + { + + } + } + } + + #region PUTTY KEY HANDLING + // decode rsa private key with encryption password + private static string DecodePrivateKey(string encryptedPrivateKey, string password) + { + TextReader textReader = new StringReader(encryptedPrivateKey); + PemReader pemReader = new PemReader(textReader, new PasswordFinder(password)); + + AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject(); + + TextWriter textWriter = new StringWriter(); + var pemWriter = new PemWriter(textWriter); + pemWriter.WriteObject(keyPair.Private); + pemWriter.Writer.Flush(); + + return ""+textWriter.ToString(); + } + private class PasswordFinder : IPasswordFinder + { + private string password; + + public PasswordFinder(string password) + { + this.password = password; + } + + + public char[] GetPassword() + { + return password.ToCharArray(); + } } + // read private key pem string to rsacryptoserviceprovider + public static RSACryptoServiceProvider ImportPrivateKey(string pem) + { + PemReader pr = new PemReader(new StringReader(pem)); + AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject(); + RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private); + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); + rsa.ImportParameters(rsaParams); + return rsa; + } + #endregion + + + #region TOKEN private static string GetToken() { // if there is no token, fetch a fresh one @@ -233,11 +323,11 @@ namespace ExternalConnectors.DSS return tokenResult; } } - + #endregion // input must be the secret id to fetch - public static void FetchSecretFromServer(string input, out string username, out string password, out string domain) + public static void FetchSecretFromServer(string input, out string username, out string password, out string domain, out string privatekey) { // get secret id int secretID = Int32.Parse(input); @@ -246,7 +336,7 @@ namespace ExternalConnectors.DSS SSConnectionData.Init(); // get the secret - FetchSecret(secretID, out username, out password, out domain); + FetchSecret(secretID, out username, out password, out domain, out privatekey); } } } diff --git a/ExternalConnectors/DSS/SecretServerRestClient.cs b/ExternalConnectors/DSS/SecretServerRestClient.cs index 614088df..20da6367 100644 --- a/ExternalConnectors/DSS/SecretServerRestClient.cs +++ b/ExternalConnectors/DSS/SecretServerRestClient.cs @@ -72886,6 +72886,9 @@ namespace SecretServerRestClient.DSS } public bool ReadResponseAsString { get; set; } + // RR 2022-09-97 + public bool ReadResponseNoJSONConvert { get; set; } + // RR END protected virtual async System.Threading.Tasks.Task<ObjectResponseResult<T>> ReadObjectResponseAsync<T>(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.Threading.CancellationToken cancellationToken) { @@ -72894,6 +72897,14 @@ namespace SecretServerRestClient.DSS return new ObjectResponseResult<T>(default(T), string.Empty); } + // RR 2022-09-97 + if (ReadResponseNoJSONConvert) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return new ObjectResponseResult<T>((T)(object)responseText, responseText); // not sure if this is best practice, but it works. + } + // RR END + if (ReadResponseAsString) { var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); diff --git a/ExternalConnectors/ExternalConnectors.csproj b/ExternalConnectors/ExternalConnectors.csproj index 4e341403..35422251 100644 --- a/ExternalConnectors/ExternalConnectors.csproj +++ b/ExternalConnectors/ExternalConnectors.csproj @@ -14,6 +14,7 @@ <PackageReference Include="AWSSDK.Core" Version="3.7.12.15" /> <PackageReference Include="AWSSDK.EC2" Version="3.7.79.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> + <PackageReference Include="Portable.BouncyCastle" Version="1.9.0" /> </ItemGroup> <ItemGroup> diff --git a/ExternalConnectors/PuttyKeyFileGenerator.cs b/ExternalConnectors/PuttyKeyFileGenerator.cs new file mode 100644 index 00000000..6c62bca7 --- /dev/null +++ b/ExternalConnectors/PuttyKeyFileGenerator.cs @@ -0,0 +1,109 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace ExternalConnectors; + +public class PuttyKeyFileGenerator +{ + private const int prefixSize = 4; + private const int paddedPrefixSize = prefixSize + 1; + private const int lineLength = 64; + private const string keyType = "ssh-rsa"; + private const string encryptionType = "none"; + + // source from + // https://gist.github.com/canton7/5670788?permalink_comment_id=3240331 + // https://gist.github.com/bosima/ee6630d30b533c7d7b2743a849e9b9d0 + + public static string ToPuttyPrivateKey(RSACryptoServiceProvider cryptoServiceProvider, string Comment = "imported-openssh-key") + { + var publicParameters = cryptoServiceProvider.ExportParameters(false); + byte[] publicBuffer = new byte[3 + keyType.Length + GetPrefixSize(publicParameters.Exponent) + publicParameters.Exponent.Length + + GetPrefixSize(publicParameters.Modulus) + publicParameters.Modulus.Length + 1]; + + using (var bw = new BinaryWriter(new MemoryStream(publicBuffer))) + { + bw.Write(new byte[] { 0x00, 0x00, 0x00 }); + bw.Write(keyType); + PutPrefixed(bw, publicParameters.Exponent, CheckIsNeddPadding(publicParameters.Exponent)); + PutPrefixed(bw, publicParameters.Modulus, CheckIsNeddPadding(publicParameters.Modulus)); + } + var publicBlob = System.Convert.ToBase64String(publicBuffer); + + var privateParameters = cryptoServiceProvider.ExportParameters(true); + byte[] privateBuffer = new byte[paddedPrefixSize + privateParameters.D.Length + paddedPrefixSize + privateParameters.P.Length + paddedPrefixSize + privateParameters.Q.Length + paddedPrefixSize + privateParameters.InverseQ.Length]; + + using (var bw = new BinaryWriter(new MemoryStream(privateBuffer))) + { + PutPrefixed(bw, privateParameters.D, true); + PutPrefixed(bw, privateParameters.P, true); + PutPrefixed(bw, privateParameters.Q, true); + PutPrefixed(bw, privateParameters.InverseQ, true); + } + var privateBlob = System.Convert.ToBase64String(privateBuffer); + + HMACSHA1 hmacsha1 = new HMACSHA1(new SHA1CryptoServiceProvider().ComputeHash(Encoding.ASCII.GetBytes("putty-private-key-file-mac-key"))); + //byte[] bytesToHash = new byte[4 + 7 + 4 + 4 + 4 + Comment.Length + 4 + publicBuffer.Length + 4 + privateBuffer.Length]; + byte[] bytesToHash = new byte[prefixSize + keyType.Length + prefixSize + encryptionType.Length + prefixSize + Comment.Length + + prefixSize + publicBuffer.Length + prefixSize + privateBuffer.Length]; + + using (var bw = new BinaryWriter(new MemoryStream(bytesToHash))) + { + PutPrefixed(bw, Encoding.ASCII.GetBytes("ssh-rsa")); + PutPrefixed(bw, Encoding.ASCII.GetBytes("none")); + PutPrefixed(bw, Encoding.ASCII.GetBytes(Comment)); + PutPrefixed(bw, publicBuffer); + PutPrefixed(bw, privateBuffer); + } + + var hash = string.Join("", hmacsha1.ComputeHash(bytesToHash).Select(x => string.Format("{0:x2}", x))); + + var sb = new StringBuilder(); + sb.AppendLine("PuTTY-User-Key-File-2: " + keyType); + sb.AppendLine("Encryption: " + encryptionType); + sb.AppendLine("Comment: " + Comment); + + var publicLines = SpliceText(publicBlob, lineLength); + sb.AppendLine("Public-Lines: " + publicLines.Length); + foreach (var line in publicLines) + { + sb.AppendLine(line); + } + + var privateLines = SpliceText(privateBlob, lineLength); + sb.AppendLine("Private-Lines: " + privateLines.Length); + foreach (var line in privateLines) + { + sb.AppendLine(line); + } + + sb.AppendLine("Private-MAC: " + hash); + + return sb.ToString(); + } + private static void PutPrefixed(BinaryWriter bw, byte[] bytes, bool addLeadingNull = false) + { + bw.Write(BitConverter.GetBytes(bytes.Length + (addLeadingNull ? 1 : 0)).Reverse().ToArray()); + if (addLeadingNull) + bw.Write(new byte[] { 0x00 }); + bw.Write(bytes); + } + + private static string[] SpliceText(string text, int lineLength) + { + return Regex.Matches(text, ".{1," + lineLength + "}").Cast<Match>().Select(m => m.Value).ToArray(); + } + private static int GetPrefixSize(byte[] bytes) + { + return CheckIsNeddPadding(bytes) ? paddedPrefixSize : prefixSize; + } + private static bool CheckIsNeddPadding(byte[] bytes) + { + // 128 == 10000000 + // This means that the number of bits can be divided by 8. + // According to the algorithm in putty, you need to add a padding. + return bytes[0] >= 128; + } + +}
\ No newline at end of file diff --git a/mRemoteNG/Connection/Protocol/PuttyBase.cs b/mRemoteNG/Connection/Protocol/PuttyBase.cs index 5dc16473..5b956d56 100644 --- a/mRemoteNG/Connection/Protocol/PuttyBase.cs +++ b/mRemoteNG/Connection/Protocol/PuttyBase.cs @@ -12,6 +12,7 @@ using System.Windows.Forms; using mRemoteNG.Properties; using mRemoteNG.Resources.Language; using Connection; +using System.IO; // ReSharper disable ArrangeAccessorOwnerBody @@ -57,6 +58,8 @@ namespace mRemoteNG.Connection.Protocol public override bool Connect() { + string optionalTemporaryPrivateKeyPath = ""; // path to ppk file instead of password. only temporary (extracted from credential vault). + try { _isPuttyNg = PuttyTypeDetector.GetPuttyType() == PuttyTypeDetector.PuttyType.PuttyNg; @@ -85,14 +88,22 @@ namespace mRemoteNG.Connection.Protocol var password = InterfaceControl.Info?.Password ?? ""; var domain = InterfaceControl.Info?.Domain ?? ""; var UserViaAPI = InterfaceControl.Info?.UserViaAPI ?? ""; - + string privatekey = ""; // access secret server api if necessary if (InterfaceControl.Info.ExternalCredentialProvider == ExternalCredentialProvider.DelineaSecretServer) { try { - ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer($"{UserViaAPI}", out username, out password, out domain); + ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer($"{UserViaAPI}", out username, out password, out domain, out privatekey); + + if (!string.IsNullOrEmpty(privatekey)) + { + optionalTemporaryPrivateKeyPath = Path.GetTempFileName(); + File.WriteAllText(optionalTemporaryPrivateKeyPath, privatekey); + FileInfo fileInfo = new FileInfo(optionalTemporaryPrivateKeyPath); + fileInfo.Attributes = FileAttributes.Temporary; + } } catch (Exception ex) { @@ -111,22 +122,26 @@ namespace mRemoteNG.Connection.Protocol username = Properties.OptionsCredentialsPage.Default.DefaultUsername; break; case "custom": - try - { - ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer( - "SSAPI:" + Properties.OptionsCredentialsPage.Default.UserViaAPDefault, out username, out password, - out domain); - } - catch (Exception ex) + + if (Properties.OptionsCredentialsPage.Default.ExternalCredentialProviderDefault == ExternalCredentialProvider.DelineaSecretServer) { - Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0); + try + { + ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer( + $"{Properties.OptionsCredentialsPage.Default.UserViaAPIDefault}", out username, out password, out domain, out privatekey); + } + catch (Exception ex) + { + Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0); + } } + break; } } - if (string.IsNullOrEmpty(password)) + if (string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(optionalTemporaryPrivateKeyPath)) { if (Properties.OptionsCredentialsPage.Default.EmptyCredentials == "custom") { @@ -149,6 +164,13 @@ namespace mRemoteNG.Connection.Protocol arguments.Add("-pw", password); } } + + // use private key if specified + if (!string.IsNullOrEmpty(optionalTemporaryPrivateKeyPath)) + { + arguments.Add("-i", optionalTemporaryPrivateKeyPath); + } + } arguments.Add("-P", InterfaceControl.Info.Port.ToString()); @@ -219,6 +241,15 @@ namespace mRemoteNG.Connection.Protocol Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.ConnectionFailed + Environment.NewLine + ex.Message); return false; } + finally + { + // make sure to remove the private key file + if (!string.IsNullOrEmpty(optionalTemporaryPrivateKeyPath)) + { + System.Threading.Thread.Sleep(500); + System.IO.File.Delete(optionalTemporaryPrivateKeyPath); + } + } } public override void Focus() diff --git a/mRemoteNG/Connection/Protocol/RDP/RdpProtocol6.cs b/mRemoteNG/Connection/Protocol/RDP/RdpProtocol6.cs index fc326113..6f5c89d5 100644 --- a/mRemoteNG/Connection/Protocol/RDP/RdpProtocol6.cs +++ b/mRemoteNG/Connection/Protocol/RDP/RdpProtocol6.cs @@ -409,14 +409,15 @@ namespace mRemoteNG.Connection.Protocol.RDP string gwu = connectionInfo.RDGatewayUsername;
string gwp = connectionInfo.RDGatewayPassword;
string gwd = connectionInfo.RDGatewayDomain;
+ string pkey = "";
// access secret server api if necessary
if (InterfaceControl.Info.RDGatewayExternalCredentialProvider == ExternalCredentialProvider.DelineaSecretServer)
{
try
{
- string idviaapi = InterfaceControl.Info.RDGatewayUserViaAPI;
- ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer($"{idviaapi}", out gwu, out gwp, out gwd);
+ string RDGUserViaAPI = InterfaceControl.Info.RDGatewayUserViaAPI;
+ ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer($"{RDGUserViaAPI}", out gwu, out gwp, out gwd, out pkey);
}
catch (Exception ex)
{
@@ -494,13 +495,14 @@ namespace mRemoteNG.Connection.Protocol.RDP var password = connectionInfo?.Password ?? "";
var domain = connectionInfo?.Domain ?? "";
var UserViaAPI = connectionInfo?.UserViaAPI ?? "";
+ string pkey = "";
// access secret server api if necessary
if (InterfaceControl.Info.ExternalCredentialProvider == ExternalCredentialProvider.DelineaSecretServer)
{
try
{
- ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer($"{UserViaAPI}", out userName, out password, out domain);
+ ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer($"{UserViaAPI}", out userName, out password, out domain, out pkey);
}
catch (Exception ex)
{
@@ -522,7 +524,7 @@ namespace mRemoteNG.Connection.Protocol.RDP case "custom":
try
{
- ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer("SSAPI:" + Properties.OptionsCredentialsPage.Default.UserViaAPDefault, out userName, out password, out domain);
+ ExternalConnectors.DSS.SecretServerInterface.FetchSecretFromServer(Properties.OptionsCredentialsPage.Default.UserViaAPIDefault, out userName, out password, out domain, out pkey);
_rdpClient.UserName = userName;
}
catch (Exception ex)
diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx index 31fb974d..511f1633 100644 --- a/mRemoteNG/Language/Language.resx +++ b/mRemoteNG/Language/Language.resx @@ -2200,10 +2200,10 @@ Nightly Channel includes Alphas, Betas & Release Candidates.</value> <comment>https://docs.microsoft.com/en-us/windows/win32/termserv/imstscsecuredsettings-workdir</comment> </data> <data name="OpeningCommand" xml:space="preserve"> - <value>TODO</value> + <value>OpeningCommand TODO</value> </data> <data name="PropertyDescriptionOpeningCommand" xml:space="preserve"> - <value>TODO</value> + <value>Description of OpeningCommand TODO</value> </data> <data name="RedirectDrives" xml:space="preserve"> <value>Disk Drives</value> diff --git a/mRemoteNG/Properties/OptionsCredentialsPage.Designer.cs b/mRemoteNG/Properties/OptionsCredentialsPage.Designer.cs index 5d3736f4..8e7f71c0 100644 --- a/mRemoteNG/Properties/OptionsCredentialsPage.Designer.cs +++ b/mRemoteNG/Properties/OptionsCredentialsPage.Designer.cs @@ -8,6 +8,8 @@ // </auto-generated> //------------------------------------------------------------------------------ +using Connection; + namespace mRemoteNG.Properties { @@ -58,16 +60,31 @@ namespace mRemoteNG.Properties { this["DefaultDomain"] = value; } } - + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public ExternalCredentialProvider ExternalCredentialProviderDefault + { + get + { + return ((ExternalCredentialProvider)(this["ExternalCredentialProviderDefault"])); + } + set + { + this["ExternalCredentialProviderDefault"] = value; + } + } + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] - public string UserViaAPDefault { + public string UserViaAPIDefault { get { - return ((string)(this["UserViaAPDefault"])); + return ((string)(this["UserViaAPIDefault"])); } set { - this["UserViaAPDefault"] = value; + this["UserViaAPIDefault"] = value; } } diff --git a/mRemoteNG/UI/Forms/OptionsPages/CredentialsPage.cs b/mRemoteNG/UI/Forms/OptionsPages/CredentialsPage.cs index 2d5fcf30..13b45faf 100644 --- a/mRemoteNG/UI/Forms/OptionsPages/CredentialsPage.cs +++ b/mRemoteNG/UI/Forms/OptionsPages/CredentialsPage.cs @@ -54,7 +54,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages txtCredentialsPassword.Text = cryptographyProvider.Decrypt(Properties.OptionsCredentialsPage.Default.DefaultPassword, Runtime.EncryptionKey); txtCredentialsDomain.Text = Properties.OptionsCredentialsPage.Default.DefaultDomain; - txtCredentialsUserViaAPI.Text = Properties.OptionsCredentialsPage.Default.UserViaAPDefault; + txtCredentialsUserViaAPI.Text = Properties.OptionsCredentialsPage.Default.UserViaAPIDefault; } public override void SaveSettings() @@ -77,7 +77,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages Properties.OptionsCredentialsPage.Default.DefaultPassword = cryptographyProvider.Encrypt(txtCredentialsPassword.Text, Runtime.EncryptionKey); Properties.OptionsCredentialsPage.Default.DefaultDomain = txtCredentialsDomain.Text; - Properties.OptionsCredentialsPage.Default.UserViaAPDefault = txtCredentialsUserViaAPI.Text; + Properties.OptionsCredentialsPage.Default.UserViaAPIDefault = txtCredentialsUserViaAPI.Text; } private void radCredentialsCustom_CheckedChanged(object sender, EventArgs e) |