diff --git a/ExternalConnectors/CPS/CPS.ico b/ExternalConnectors/CPS/CPS.ico
new file mode 100644
index 00000000..5624c387
Binary files /dev/null and b/ExternalConnectors/CPS/CPS.ico differ
diff --git a/ExternalConnectors/CPS/CPSConnectionForm.Designer.cs b/ExternalConnectors/CPS/CPSConnectionForm.Designer.cs
new file mode 100644
index 00000000..aeb7432e
--- /dev/null
+++ b/ExternalConnectors/CPS/CPSConnectionForm.Designer.cs
@@ -0,0 +1,241 @@
+namespace ExternalConnectors.CPS
+{
+ partial class CPSConnectionForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CPSConnectionForm));
+ tbServerURL = new TextBox();
+ label3 = new Label();
+ tbAPIKey = new TextBox();
+ btnOK = new Button();
+ btnCancel = new Button();
+ tableLayoutPanel1 = new TableLayoutPanel();
+ label1 = new Label();
+ label6 = new Label();
+ tbOTP = new TextBox();
+ cbUseSSO = new CheckBox();
+ tableLayoutPanel2 = new TableLayoutPanel();
+ label4 = new Label();
+ tableLayoutPanel1.SuspendLayout();
+ tableLayoutPanel2.SuspendLayout();
+ SuspendLayout();
+ //
+ // tbServerURL
+ //
+ tbServerURL.Dock = DockStyle.Fill;
+ tbServerURL.Location = new Point(298, 5);
+ tbServerURL.Margin = new Padding(5);
+ tbServerURL.Name = "tbServerURL";
+ tbServerURL.Size = new Size(611, 27);
+ tbServerURL.TabIndex = 0;
+ //
+ // label3
+ //
+ label3.AutoSize = true;
+ label3.Dock = DockStyle.Fill;
+ label3.Location = new Point(5, 84);
+ label3.Margin = new Padding(5, 0, 5, 0);
+ label3.Name = "label3";
+ label3.Size = new Size(283, 42);
+ label3.TabIndex = 5;
+ label3.Text = "API Key";
+ label3.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // tbAPIKey
+ //
+ tbAPIKey.Dock = DockStyle.Fill;
+ tbAPIKey.Location = new Point(298, 89);
+ tbAPIKey.Margin = new Padding(5);
+ tbAPIKey.Name = "tbAPIKey";
+ tbAPIKey.Size = new Size(611, 27);
+ tbAPIKey.TabIndex = 4;
+ tbAPIKey.UseSystemPasswordChar = true;
+ //
+ // btnOK
+ //
+ btnOK.Anchor = AnchorStyles.Right;
+ btnOK.DialogResult = DialogResult.OK;
+ btnOK.Location = new Point(337, 16);
+ btnOK.Margin = new Padding(5);
+ btnOK.Name = "btnOK";
+ btnOK.Size = new Size(101, 35);
+ btnOK.TabIndex = 6;
+ btnOK.Text = "OK";
+ btnOK.UseVisualStyleBackColor = true;
+ //
+ // btnCancel
+ //
+ btnCancel.Anchor = AnchorStyles.Left;
+ btnCancel.DialogResult = DialogResult.Cancel;
+ btnCancel.Location = new Point(474, 16);
+ btnCancel.Margin = new Padding(5);
+ btnCancel.Name = "btnCancel";
+ btnCancel.Size = new Size(101, 35);
+ btnCancel.TabIndex = 11;
+ btnCancel.Text = "Cancel";
+ btnCancel.UseVisualStyleBackColor = true;
+ //
+ // tableLayoutPanel1
+ //
+ tableLayoutPanel1.ColumnCount = 2;
+ tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 32.06997F));
+ tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 67.93003F));
+ tableLayoutPanel1.Controls.Add(label1, 0, 0);
+ tableLayoutPanel1.Controls.Add(label3, 0, 2);
+ tableLayoutPanel1.Controls.Add(tbServerURL, 1, 0);
+ tableLayoutPanel1.Controls.Add(tbAPIKey, 1, 2);
+ tableLayoutPanel1.Controls.Add(label6, 0, 3);
+ tableLayoutPanel1.Controls.Add(tbOTP, 1, 3);
+ tableLayoutPanel1.Controls.Add(cbUseSSO, 0, 1);
+ tableLayoutPanel1.Dock = DockStyle.Top;
+ tableLayoutPanel1.Location = new Point(0, 0);
+ tableLayoutPanel1.Margin = new Padding(5);
+ tableLayoutPanel1.Name = "tableLayoutPanel1";
+ tableLayoutPanel1.RowCount = 5;
+ tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
+ tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
+ tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
+ tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
+ tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
+ tableLayoutPanel1.Size = new Size(914, 212);
+ tableLayoutPanel1.TabIndex = 12;
+ //
+ // label1
+ //
+ label1.AutoSize = true;
+ label1.Dock = DockStyle.Fill;
+ label1.Location = new Point(5, 0);
+ label1.Margin = new Padding(5, 0, 5, 0);
+ label1.Name = "label1";
+ label1.Size = new Size(283, 42);
+ label1.TabIndex = 2;
+ label1.Text = "Passwordstate URL";
+ label1.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // label6
+ //
+ label6.AutoSize = true;
+ label6.Dock = DockStyle.Fill;
+ label6.Location = new Point(3, 126);
+ label6.Name = "label6";
+ label6.Size = new Size(287, 42);
+ label6.TabIndex = 15;
+ label6.Text = "2FA OTP (Optional)";
+ //
+ // tbOTP
+ //
+ tbOTP.Dock = DockStyle.Fill;
+ tbOTP.Location = new Point(298, 131);
+ tbOTP.Margin = new Padding(5);
+ tbOTP.Name = "tbOTP";
+ tbOTP.Size = new Size(611, 27);
+ tbOTP.TabIndex = 5;
+ //
+ // cbUseSSO
+ //
+ cbUseSSO.Anchor = AnchorStyles.Left;
+ cbUseSSO.AutoSize = true;
+ cbUseSSO.Location = new Point(5, 53);
+ cbUseSSO.Margin = new Padding(5, 5, 5, 0);
+ cbUseSSO.Name = "cbUseSSO";
+ cbUseSSO.Size = new Size(157, 24);
+ cbUseSSO.TabIndex = 14;
+ cbUseSSO.Text = "Use SSO / WinAuth";
+ cbUseSSO.UseVisualStyleBackColor = true;
+ cbUseSSO.CheckedChanged += cbUseSSO_CheckedChanged;
+ //
+ // tableLayoutPanel2
+ //
+ tableLayoutPanel2.ColumnCount = 5;
+ tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
+ tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
+ tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 26F));
+ tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
+ tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 107F));
+ tableLayoutPanel2.Controls.Add(btnOK, 1, 0);
+ tableLayoutPanel2.Controls.Add(btnCancel, 3, 0);
+ tableLayoutPanel2.Dock = DockStyle.Bottom;
+ tableLayoutPanel2.Location = new Point(0, 300);
+ tableLayoutPanel2.Margin = new Padding(5);
+ tableLayoutPanel2.Name = "tableLayoutPanel2";
+ tableLayoutPanel2.RowCount = 1;
+ tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
+ tableLayoutPanel2.Size = new Size(914, 67);
+ tableLayoutPanel2.TabIndex = 13;
+ //
+ // label4
+ //
+ label4.AutoSize = true;
+ label4.Dock = DockStyle.Fill;
+ label4.Location = new Point(0, 212);
+ label4.Margin = new Padding(5, 0, 5, 0);
+ label4.Name = "label4";
+ label4.Size = new Size(345, 20);
+ label4.TabIndex = 14;
+ label4.Text = "URL is the base URL, like https://pass.domain.local/";
+ label4.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // CPSConnectionForm
+ //
+ AcceptButton = btnOK;
+ AutoScaleDimensions = new SizeF(8F, 20F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(914, 367);
+ Controls.Add(label4);
+ Controls.Add(tableLayoutPanel2);
+ Controls.Add(tableLayoutPanel1);
+ Icon = (Icon)resources.GetObject("$this.Icon");
+ Margin = new Padding(5);
+ Name = "CPSConnectionForm";
+ Text = "Passwordstate API Login Data";
+ Activated += CPSConnectionForm_Activated;
+ tableLayoutPanel1.ResumeLayout(false);
+ tableLayoutPanel1.PerformLayout();
+ tableLayoutPanel2.ResumeLayout(false);
+ ResumeLayout(false);
+ PerformLayout();
+ }
+
+ #endregion
+ private System.Windows.Forms.Label label3;
+
+ public System.Windows.Forms.TextBox tbServerURL;
+ //public System.Windows.Forms.TextBox tbUsername;
+ public System.Windows.Forms.TextBox tbAPIKey;
+ private System.Windows.Forms.Button btnOK;
+ private System.Windows.Forms.Button btnCancel;
+ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
+ public System.Windows.Forms.CheckBox cbUseSSO;
+ private System.Windows.Forms.Label label4;
+ private Label label6;
+ public TextBox tbOTP;
+ }
+}
\ No newline at end of file
diff --git a/ExternalConnectors/CPS/CPSConnectionForm.cs b/ExternalConnectors/CPS/CPSConnectionForm.cs
new file mode 100644
index 00000000..8e17e836
--- /dev/null
+++ b/ExternalConnectors/CPS/CPSConnectionForm.cs
@@ -0,0 +1,41 @@
+namespace ExternalConnectors.CPS
+{
+ public partial class CPSConnectionForm : Form
+ {
+ public CPSConnectionForm()
+ {
+ InitializeComponent();
+ }
+
+ private void CPSConnectionForm_Activated(object sender, EventArgs e)
+ {
+ SetVisibility();
+ if (cbUseSSO.Checked)
+ btnOK.Focus();
+ else
+ {
+ if (tbAPIKey.Text.Length == 0)
+ tbAPIKey.Focus();
+ else
+ tbOTP.Focus();
+ }
+
+ tbAPIKey.Focus();
+ if (!string.IsNullOrEmpty(tbAPIKey.Text) || cbUseSSO.Checked == true)
+ tbOTP.Focus();
+
+
+ }
+
+ private void cbUseSSO_CheckedChanged(object sender, EventArgs e)
+ {
+ SetVisibility();
+ }
+ private void SetVisibility()
+ {
+ bool ch = cbUseSSO.Checked;
+ tbAPIKey.Enabled = !ch;
+ //tbUsername.Enabled = !ch;
+ }
+ }
+}
diff --git a/ExternalConnectors/CPS/CPSConnectionForm.resx b/ExternalConnectors/CPS/CPSConnectionForm.resx
new file mode 100644
index 00000000..261283b3
--- /dev/null
+++ b/ExternalConnectors/CPS/CPSConnectionForm.resx
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+ AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAA
+ AAAtLDAA+PDaAP///wD79ugA/PrzAPf39wBGRUkA7NacAM2WAAA3z6kA+Pz/AIKBgwD+//4A4sNtAHXe
+ xAD8+vIAjuTOANOjHgDV6/4AJZf3APn5+QDw37IAIB8jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAgICAgICCQ4CAgIWAgIUAgICAgICAgkJAgICFhYWAAIIDwICAgICCQwCAhYWFgYCCAgIBwIC
+ AgkJAgsWFhYWBQICCAgIAwIJCQIWFhYWAgICAgINCAgCAgICFhYCAgICAgICAgIRAgICAgICAgICAgIC
+ AgICEwICAgICEhMTExMCAgITExMCAgICAgITExMTAhMTExMCAgICAgICAgICAhMTEwICAgIICAIJCQIC
+ AgIKAgICAgIICAQCAgkJAgICAgICAgICCAgCAgICCQkCAgICAgICFQgBAgICAgwJAgICAgICAggIAgIC
+ AgICEAkCAgICAgIIAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+
+
+
\ No newline at end of file
diff --git a/ExternalConnectors/CPS/PasswordstateInterface.cs b/ExternalConnectors/CPS/PasswordstateInterface.cs
new file mode 100644
index 00000000..5208a2e3
--- /dev/null
+++ b/ExternalConnectors/CPS/PasswordstateInterface.cs
@@ -0,0 +1,301 @@
+using Microsoft.Win32;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.OpenSsl;
+using Org.BouncyCastle.Security;
+using System.Security.Cryptography;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace ExternalConnectors.CPS;
+
+public class PasswordstateInterface
+{
+ private static class CPSConnectionData
+ {
+ public static string ssUsername = "";
+ public static string ssPassword = "";
+ public static string ssUrl = "";
+ public static string ssOTP = "";
+ public static DateTime ssOTPTimeStampExpiration;
+
+ public static bool ssSSO = false;
+ public static bool initdone = false;
+
+ //token
+ //public static string ssTokenBearer = "";
+ //public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
+ //public static string ssTokenRefresh = "";
+
+ public static void Init()
+ {
+ // 2024-05-04 passwordstate currently does not support auth tokens, so we need to re-enter otp codes frequently
+ if (!string.IsNullOrEmpty(ssOTP) && DateTime.Now > ssOTPTimeStampExpiration)
+ {
+ ssOTP = "";
+ initdone = false;
+ }
+
+ if (initdone == true)
+ return;
+
+ RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteCPSInterface");
+ try
+ {
+ // display gui and ask for data
+ CPSConnectionForm f = new CPSConnectionForm();
+ //string? un = key.GetValue("Username") as string;
+ //f.tbUsername.Text = un ?? "";
+ f.tbAPIKey.Text = CPSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
+
+ string? url = key.GetValue("URL") as string;
+ if (url == null || !url.Contains("://"))
+ url = "https://cred.domain.local/SecretServer";
+ f.tbServerURL.Text = url;
+
+ var b = key.GetValue("SSO");
+ if (b == null || (string)b != "True")
+ ssSSO = false;
+ else
+ ssSSO = true;
+ f.cbUseSSO.Checked = ssSSO;
+
+ // show dialog
+ while (true)
+ {
+ _ = f.ShowDialog();
+
+ if (f.DialogResult != DialogResult.OK)
+ return;
+
+ // store values to memory
+ //ssUsername = f.tbUsername.Text;
+ ssPassword = f.tbAPIKey.Text;
+ ssUrl = f.tbServerURL.Text;
+ ssSSO = f.cbUseSSO.Checked;
+ ssOTP = f.tbOTP.Text;
+ ssOTPTimeStampExpiration = DateTime.Now.AddSeconds(30);
+ // check connection first
+ try
+ {
+ if (TestCredentials() == true)
+ {
+ initdone = true;
+ break;
+ }
+ }
+ catch (Exception)
+ {
+ MessageBox.Show("Test Credentials failed - please check your credentials");
+ }
+ }
+
+
+ // write values to registry
+ //key.SetValue("Username", ssUsername);
+ key.SetValue("URL", ssUrl);
+ key.SetValue("SSO", ssSSO);
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ finally
+ {
+ key.Close();
+ }
+ }
+ }
+
+ private static bool TestCredentials()
+ {
+ return ConnectionTest();
+ }
+ private static bool ConnectionTest()
+ {
+ if (CPSConnectionData.ssSSO)
+ {
+ string url = $"{CPSConnectionData.ssUrl}/winapi/passwordlists/";
+
+ using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
+ client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
+
+ var json = client.GetStringAsync(url).Result;
+ JsonNode? data = JsonSerializer.Deserialize(json);
+ if (data == null)
+ return false;
+ return true;
+ }
+ else
+ {
+ string url = $"{CPSConnectionData.ssUrl}/api/passwordlists/";
+ using HttpClient client = new HttpClient();
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
+ client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
+ client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
+
+ var json = client.GetStringAsync(url).Result;
+ JsonNode? data = JsonSerializer.Deserialize(json);
+ if (data == null)
+ return false;
+ return true;
+ }
+ }
+
+ private static JsonNode? FetchDataWinAuth(int secretID)
+ {
+ string url = $"{CPSConnectionData.ssUrl}/winapi/passwords/{secretID}";
+
+ using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
+ client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
+
+ var json = client.GetStringAsync(url).Result;
+ JsonNode? data = JsonSerializer.Deserialize(json);
+ if (data == null)
+ return null;
+ JsonNode? element = data[0];
+ return element;
+ }
+ private static JsonNode? FetchDataAPIKeyAuth(int secretID)
+ {
+ string url = $"{CPSConnectionData.ssUrl}/api/passwords/{secretID}";
+
+ using HttpClient client = new HttpClient();
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
+ client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
+ client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
+
+ var json = client.GetStringAsync(url).Result;
+ JsonNode? data = JsonSerializer.Deserialize(json);
+ if (data == null)
+ return null;
+ JsonNode? element = data[0];
+ return element;
+ }
+
+ private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
+ {
+ // clear return variables
+ secretDomain = "";
+ secretUsername = "";
+ secretPassword = "";
+ privatekey = "";
+ string privatekeypassphrase = "";
+ JsonNode? element = null;
+
+ if (CPSConnectionData.ssSSO)
+ element = FetchDataWinAuth(secretID);
+ else
+ element = FetchDataAPIKeyAuth(secretID);
+
+ if (element == null)
+ return;
+
+ var dom = element["Domain"];
+ if (dom != null) secretDomain = dom.ToString();
+
+ var user = element["UserName"];
+ if (user != null) secretUsername = user.ToString();
+
+ var pw = element["Password"];
+ if (pw != null) secretPassword = pw.ToString();
+
+ var privkey = element["GenericField1"];
+ if (privkey != null) privatekey = privkey.ToString();
+
+ var phrase = element["GenericField3"];
+ if (phrase != null) privatekeypassphrase = phrase.ToString();
+
+ // 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
+
+
+ // input: must be the secret id to fetch
+ public static void FetchSecretFromServer(string secretID, out string username, out string password, out string domain, out string privatekey)
+ {
+ // get secret id
+ int sid = Int32.Parse(secretID);
+
+ // init connection credentials, display popup if necessary
+ CPSConnectionData.Init();
+
+ // get the secret
+ FetchSecret(sid, out username, out password, out domain, out privatekey);
+ }
+}
diff --git a/ExternalConnectors/DSS/SecretServerInterface.cs b/ExternalConnectors/DSS/SecretServerInterface.cs
index 4d9f0493..b0ae930d 100644
--- a/ExternalConnectors/DSS/SecretServerInterface.cs
+++ b/ExternalConnectors/DSS/SecretServerInterface.cs
@@ -7,28 +7,28 @@ using SecretServerAuthentication.DSS;
using SecretServerRestClient.DSS;
using System.Security.Cryptography;
-namespace ExternalConnectors.DSS
+namespace ExternalConnectors.DSS;
+
+public class SecretServerInterface
{
- public class SecretServerInterface
+ private static class SSConnectionData
{
- private static class SSConnectionData
+ public static string ssUsername = "";
+ public static string ssPassword = "";
+ public static string ssUrl = "";
+ public static string ssOTP = "";
+ public static bool ssSSO = false;
+ public static bool initdone = false;
+
+ //token
+ public static string ssTokenBearer = "";
+ public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
+ public static string ssTokenRefresh = "";
+
+ public static void Init()
{
- public static string ssUsername = "";
- public static string ssPassword = "";
- public static string ssUrl = "";
- public static string ssOTP = "";
- public static bool ssSSO = false;
- public static bool initdone = false;
-
- //token
- public static string ssTokenBearer = "";
- public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
- public static string ssTokenRefresh = "";
-
- public static void Init()
- {
- if (initdone == true)
- return;
+ if (initdone == true)
+ return;
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteSSInterface");
try
@@ -39,174 +39,174 @@ namespace ExternalConnectors.DSS
f.tbUsername.Text = un ?? "";
f.tbPassword.Text = SSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
- string? url = key.GetValue("URL") as string;
- if (url == null || !url.Contains("://"))
- url = "https://cred.domain.local/SecretServer";
- f.tbSSURL.Text = url;
+ string? url = key.GetValue("URL") as string;
+ if (url == null || !url.Contains("://"))
+ url = "https://cred.domain.local/SecretServer";
+ f.tbSSURL.Text = url;
- var b = key.GetValue("SSO");
- if (b == null || (string)b != "True")
- ssSSO = false;
- else
- ssSSO = true;
- f.cbUseSSO.Checked = ssSSO;
-
- // show dialog
- while (true)
+ var b = key.GetValue("SSO");
+ if (b == null || (string)b != "True")
+ ssSSO = false;
+ else
+ ssSSO = true;
+ f.cbUseSSO.Checked = ssSSO;
+
+ // show dialog
+ while (true)
+ {
+ _ = f.ShowDialog();
+
+ if (f.DialogResult != DialogResult.OK)
+ return;
+
+ // store values to memory
+ ssUsername = f.tbUsername.Text;
+ ssPassword = f.tbPassword.Text;
+ ssUrl = f.tbSSURL.Text;
+ ssSSO = f.cbUseSSO.Checked;
+ ssOTP = f.tbOTP.Text;
+ // check connection first
+ try
{
- _ = f.ShowDialog();
-
- if (f.DialogResult != DialogResult.OK)
- return;
-
- // store values to memory
- ssUsername = f.tbUsername.Text;
- ssPassword = f.tbPassword.Text;
- ssUrl = f.tbSSURL.Text;
- ssSSO = f.cbUseSSO.Checked;
- ssOTP = f.tbOTP.Text;
- // check connection first
- try
+ if (TestCredentials() == true)
{
- if (TestCredentials() == true)
- {
- initdone = true;
- break;
- }
- }
- catch (Exception)
- {
- MessageBox.Show("Test Credentials failed - please check your credentials");
+ initdone = true;
+ break;
}
}
-
-
- // write values to registry
- key.SetValue("Username", ssUsername);
- key.SetValue("URL", ssUrl);
- key.SetValue("SSO", ssSSO);
- }
- catch (Exception)
- {
- throw;
- }
- finally
- {
- key.Close();
+ catch (Exception)
+ {
+ MessageBox.Show("Test Credentials failed - please check your credentials");
+ }
}
+
+ // write values to registry
+ key.SetValue("Username", ssUsername);
+ key.SetValue("URL", ssUrl);
+ key.SetValue("SSO", ssSSO);
}
- }
-
- private static bool TestCredentials()
- {
- if (SSConnectionData.ssSSO)
+ catch (Exception)
+ {
+ throw;
+ }
+ finally
+ {
+ key.Close();
+ }
+
+ }
+ }
+
+ private static bool TestCredentials()
+ {
+ if (SSConnectionData.ssSSO)
+ {
+ // checking creds doesn't really make sense here, as we can't modify them anyway if something is wrong
+ return true;
+ }
+ else
+ {
+
+ if (!String.IsNullOrEmpty(GetToken()))
{
- // checking creds doesn't really make sense here, as we can't modify them anyway if something is wrong
return true;
}
else
{
-
- if (!String.IsNullOrEmpty(GetToken()))
- {
- return true;
- }
- else
- {
- return false;
- }
+ return false;
}
}
+ }
- private static SecretsServiceClient ConstructSecretsServiceClient()
+ private static SecretsServiceClient ConstructSecretsServiceClient()
+ {
+ string baseURL = SSConnectionData.ssUrl;
+ if (SSConnectionData.ssSSO)
{
- string baseURL = SSConnectionData.ssUrl;
- 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 };
+ var httpClient = new HttpClient(handler);
{
- // REQUIRES IIS CONFIG! https://docs.thycotic.com/ss/11.0.0/api-scripting/webservice-iwa-powershell
- var handler = new HttpClientHandler() { UseDefaultCredentials = true };
- var httpClient = new HttpClient(handler);
- {
- // Call REST API:
- return new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient);
- }
+ // Call REST API:
+ return new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient);
}
- else
- {
- var httpClient = new HttpClient();
- {
-
- var token = GetToken();
- // Set credentials (token):
- httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
-
- // Call REST API:
- return new SecretsServiceClient($"{baseURL}/api", httpClient);
- }
- }
-
}
- private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
+ else
{
- 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)
+ var httpClient = new HttpClient();
{
- if (item.FieldName.ToLower().Equals("domain"))
- secretDomain = item.ItemValue;
- else if (item.FieldName.ToLower().Equals("username"))
- 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)
- {
+ var token = GetToken();
+ // Set credentials (token):
+ httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
- }
- }
-
- // 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)
- {
-
- }
+ // Call REST API:
+ 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)
+ {
+ if (item.FieldName.ToLower().Equals("domain"))
+ secretDomain = item.ItemValue;
+ else if (item.FieldName.ToLower().Equals("username"))
+ 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)
@@ -214,31 +214,31 @@ namespace ExternalConnectors.DSS
TextReader textReader = new StringReader(encryptedPrivateKey);
PemReader pemReader = new(textReader, new PasswordFinder(password));
- AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
+ AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
- TextWriter textWriter = new StringWriter();
- var pemWriter = new PemWriter(textWriter);
- pemWriter.WriteObject(keyPair.Private);
- pemWriter.Writer.Flush();
+ TextWriter textWriter = new StringWriter();
+ var pemWriter = new PemWriter(textWriter);
+ pemWriter.WriteObject(keyPair.Private);
+ pemWriter.Writer.Flush();
- return ""+textWriter.ToString();
- }
- private class PasswordFinder : IPasswordFinder
+ return ""+textWriter.ToString();
+ }
+ private class PasswordFinder : IPasswordFinder
+ {
+ private string password;
+
+ public PasswordFinder(string password)
{
- private string password;
-
- public PasswordFinder(string password)
- {
- this.password = password;
- }
-
-
- public char[] GetPassword()
- {
- return password.ToCharArray();
- }
+ this.password = password;
}
+
+ public char[] GetPassword()
+ {
+ return password.ToCharArray();
+ }
+ }
+
// read private key pem string to rsacryptoserviceprovider
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
{
@@ -252,91 +252,90 @@ namespace ExternalConnectors.DSS
#endregion
- #region TOKEN
- private static string GetToken()
+ #region TOKEN
+ private static string GetToken()
+ {
+ // if there is no token, fetch a fresh one
+ if (String.IsNullOrEmpty(SSConnectionData.ssTokenBearer))
{
- // if there is no token, fetch a fresh one
- if (String.IsNullOrEmpty(SSConnectionData.ssTokenBearer))
+ return GetTokenFresh();
+ }
+ // if there is a token, check if it is valid
+ if (SSConnectionData.ssTokenExpiresOn >= DateTime.UtcNow)
+ {
+ return SSConnectionData.ssTokenBearer;
+ }
+ else
+ {
+ // try using refresh token
+ using (var httpClient = new HttpClient())
{
- return GetTokenFresh();
- }
- // if there is a token, check if it is valid
- if (SSConnectionData.ssTokenExpiresOn >= DateTime.UtcNow)
- {
- return SSConnectionData.ssTokenBearer;
- }
- else
- {
- // try using refresh token
- using (var httpClient = new HttpClient())
+ var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
+ TokenResponse token = new();
+ try
{
- var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
- TokenResponse token = new();
- try
- {
- token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, SSConnectionData.ssTokenRefresh, null).Result;
- var tokenResult = token.Access_token;
+ token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, SSConnectionData.ssTokenRefresh, null).Result;
+ var tokenResult = token.Access_token;
- SSConnectionData.ssTokenBearer = tokenResult;
- SSConnectionData.ssTokenRefresh = token.Refresh_token;
- SSConnectionData.ssTokenExpiresOn = token.Expires_on;
- return tokenResult;
- }
- catch (Exception)
+ SSConnectionData.ssTokenBearer = tokenResult;
+ SSConnectionData.ssTokenRefresh = token.Refresh_token;
+ SSConnectionData.ssTokenExpiresOn = token.Expires_on;
+ return tokenResult;
+ }
+ catch (Exception)
+ {
+ // refresh token failed. clean memory and start fresh
+ SSConnectionData.ssTokenBearer = "";
+ SSConnectionData.ssTokenRefresh = "";
+ SSConnectionData.ssTokenExpiresOn = DateTime.Now;
+ // if OTP is required we need to ask user for a new OTP
+ if (!String.IsNullOrEmpty(SSConnectionData.ssOTP))
{
- // refresh token failed. clean memory and start fresh
- SSConnectionData.ssTokenBearer = "";
- SSConnectionData.ssTokenRefresh = "";
- SSConnectionData.ssTokenExpiresOn = DateTime.Now;
- // if OTP is required we need to ask user for a new OTP
- if (!String.IsNullOrEmpty(SSConnectionData.ssOTP))
- {
- SSConnectionData.initdone = false;
- // the call below executes a connection test, which fetches a valid token
- SSConnectionData.Init();
- // we now have a fresh token in memory. return it to caller
- return SSConnectionData.ssTokenBearer;
- }
- else
- {
- // no user interaction required. get a fresh token and return it to caller
- return GetTokenFresh();
- }
+ SSConnectionData.initdone = false;
+ // the call below executes a connection test, which fetches a valid token
+ SSConnectionData.Init();
+ // we now have a fresh token in memory. return it to caller
+ return SSConnectionData.ssTokenBearer;
+ }
+ else
+ {
+ // no user interaction required. get a fresh token and return it to caller
+ return GetTokenFresh();
}
}
}
}
- static string GetTokenFresh()
+ }
+ static string GetTokenFresh()
+ {
+ using (var httpClient = new HttpClient())
{
- using (var httpClient = new HttpClient())
- {
- // Authenticate:
- var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
- // call below will throw an exception if the creds are invalid
- var token = tokenClient.AuthorizeAsync(Grant_type.Password, SSConnectionData.ssUsername, SSConnectionData.ssPassword, null, SSConnectionData.ssOTP).Result;
- // here we can be sure the creds are ok - return success state
- var tokenResult = token.Access_token;
+ // Authenticate:
+ var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
+ // call below will throw an exception if the creds are invalid
+ var token = tokenClient.AuthorizeAsync(Grant_type.Password, SSConnectionData.ssUsername, SSConnectionData.ssPassword, null, SSConnectionData.ssOTP).Result;
+ // here we can be sure the creds are ok - return success state
+ var tokenResult = token.Access_token;
- SSConnectionData.ssTokenBearer = tokenResult;
- SSConnectionData.ssTokenRefresh = token.Refresh_token;
- SSConnectionData.ssTokenExpiresOn = token.Expires_on;
- 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, out string privatekey)
- {
- // get secret id
- int secretID = Int32.Parse(input);
-
- // init connection credentials, display popup if necessary
- SSConnectionData.Init();
-
- // get the secret
- FetchSecret(secretID, out username, out password, out domain, out privatekey);
+ SSConnectionData.ssTokenBearer = tokenResult;
+ SSConnectionData.ssTokenRefresh = token.Refresh_token;
+ SSConnectionData.ssTokenExpiresOn = token.Expires_on;
+ 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, out string privatekey)
+ {
+ // get secret id
+ int secretID = Int32.Parse(input);
+
+ // init connection credentials, display popup if necessary
+ SSConnectionData.Init();
+
+ // get the secret
+ FetchSecret(secretID, out username, out password, out domain, out privatekey);
+ }
}
diff --git a/ExternalConnectors/ExternalConnectors.csproj b/ExternalConnectors/ExternalConnectors.csproj
index 8d806aee..d921a579 100644
--- a/ExternalConnectors/ExternalConnectors.csproj
+++ b/ExternalConnectors/ExternalConnectors.csproj
@@ -15,8 +15,8 @@
-
-
+
+
@@ -25,6 +25,9 @@
Form
+
+ Form
+
Form
diff --git a/mRemoteNG/Connection/ExternalCredentialProviderSelector.cs b/mRemoteNG/Connection/ExternalCredentialProviderSelector.cs
index 6eb2504a..29acec54 100644
--- a/mRemoteNG/Connection/ExternalCredentialProviderSelector.cs
+++ b/mRemoteNG/Connection/ExternalCredentialProviderSelector.cs
@@ -9,6 +9,10 @@ namespace mRemoteNG.Connection
None = 0,
[LocalizedAttributes.LocalizedDescription(nameof(Language.ECPDelineaSecretServer))]
- DelineaSecretServer = 1
+ DelineaSecretServer = 1,
+
+ [LocalizedAttributes.LocalizedDescription(nameof(Language.ECPClickstudiosPasswordstate))]
+ ClickstudiosPasswordState = 2
+
}
}
diff --git a/mRemoteNG/Connection/Protocol/PuttyBase.cs b/mRemoteNG/Connection/Protocol/PuttyBase.cs
index 2de96413..2cd21c75 100644
--- a/mRemoteNG/Connection/Protocol/PuttyBase.cs
+++ b/mRemoteNG/Connection/Protocol/PuttyBase.cs
@@ -14,6 +14,7 @@ using System.Linq;
using System.Runtime.Versioning;
using System.Threading;
using System.Windows.Forms;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
// ReSharper disable ArrangeAccessorOwnerBody
@@ -127,6 +128,28 @@ namespace mRemoteNG.Connection.Protocol
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
}
}
+ else if (InterfaceControl.Info.ExternalCredentialProvider == ExternalCredentialProvider.ClickstudiosPasswordState)
+ {
+ try
+ {
+ ExternalConnectors.CPS.PasswordstateInterface.FetchSecretFromServer($"{UserViaAPI}", out username, out password, out domain, out privatekey);
+
+ if (!string.IsNullOrEmpty(privatekey))
+ {
+ optionalTemporaryPrivateKeyPath = Path.GetTempFileName();
+ File.WriteAllText(optionalTemporaryPrivateKeyPath, privatekey);
+ FileInfo fileInfo = new(optionalTemporaryPrivateKeyPath)
+ {
+ Attributes = FileAttributes.Temporary
+ };
+ }
+ }
+ catch (Exception ex)
+ {
+ Event_ErrorOccured(this, "Passwordstate Interface Error: " + ex.Message, 0);
+ }
+ }
+
if (string.IsNullOrEmpty(username))
{
diff --git a/mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs b/mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs
index ffcea237..d83c2141 100644
--- a/mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs
+++ b/mRemoteNG/Connection/Protocol/RDP/RdpProtocol.cs
@@ -18,6 +18,8 @@ using MSTSCLib;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
using FileDialog = Microsoft.Win32.FileDialog;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
+using System.DirectoryServices.ActiveDirectory;
namespace mRemoteNG.Connection.Protocol.RDP
{
@@ -450,8 +452,20 @@ namespace mRemoteNG.Connection.Protocol.RDP
{
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
}
-
}
+ else if (InterfaceControl.Info.ExternalCredentialProvider == ExternalCredentialProvider.ClickstudiosPasswordState)
+ {
+ try
+ {
+ string RDGUserViaAPI = InterfaceControl.Info.RDGatewayUserViaAPI;
+ ExternalConnectors.CPS.PasswordstateInterface.FetchSecretFromServer($"{RDGUserViaAPI}", out gwu, out gwp, out gwd, out pkey);
+ }
+ catch (Exception ex)
+ {
+ Event_ErrorOccured(this, "Passwordstate Interface Error: " + ex.Message, 0);
+ }
+ }
+
if (connectionInfo.RDGatewayUseConnectionCredentials != RDGatewayUseConnectionCredentials.AccessToken)
{
@@ -538,7 +552,17 @@ namespace mRemoteNG.Connection.Protocol.RDP
{
Event_ErrorOccured(this, "Secret Server Interface Error: " + ex.Message, 0);
}
-
+ }
+ else if (InterfaceControl.Info.ExternalCredentialProvider == ExternalCredentialProvider.ClickstudiosPasswordState)
+ {
+ try
+ {
+ ExternalConnectors.CPS.PasswordstateInterface.FetchSecretFromServer($"{userViaApi}", out userName, out password, out domain, out pkey);
+ }
+ catch (Exception ex)
+ {
+ Event_ErrorOccured(this, "Passwordstate Interface Error: " + ex.Message, 0);
+ }
}
if (string.IsNullOrEmpty(userName))
diff --git a/mRemoteNG/Language/Language.Designer.cs b/mRemoteNG/Language/Language.Designer.cs
index cedf95c7..79947615 100644
--- a/mRemoteNG/Language/Language.Designer.cs
+++ b/mRemoteNG/Language/Language.Designer.cs
@@ -1807,6 +1807,15 @@ namespace mRemoteNG.Resources.Language {
}
}
+ ///
+ /// Looks up a localized string similar to Clickstudios Passwordstate.
+ ///
+ internal static string ECPClickstudiosPasswordstate {
+ get {
+ return ResourceManager.GetString("ECPClickstudiosPasswordstate", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Delinea Secret Server.
///
diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx
index eba84d39..5420b864 100644
--- a/mRemoteNG/Language/Language.resx
+++ b/mRemoteNG/Language/Language.resx
@@ -2289,7 +2289,10 @@ Nightly Channel includes Alphas, Betas & Release Candidates.
Delinea Secret Server
-
+
+ Clickstudios Passwordstate
+
+
None
diff --git a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs
index 7a354509..fb53dd2c 100644
--- a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs
+++ b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs
@@ -238,7 +238,8 @@ namespace mRemoteNG.UI.Controls.ConnectionInfoPropertyGrid
{
strHide.Add(nameof(AbstractConnectionRecord.UserViaAPI));
}
- else if (SelectedConnectionInfo.ExternalCredentialProvider == ExternalCredentialProvider.DelineaSecretServer)
+ else if (SelectedConnectionInfo.ExternalCredentialProvider == ExternalCredentialProvider.DelineaSecretServer
+ || SelectedConnectionInfo.ExternalCredentialProvider == ExternalCredentialProvider.ClickstudiosPasswordState)
{
strHide.Add(nameof(AbstractConnectionRecord.Username));
strHide.Add(nameof(AbstractConnectionRecord.Password));