mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 14:07:46 +08:00
feat: Add registry settings for Security Page and encryption key generator
- Added new registry settings for managing security-related configurations on the Security Page. - Implemented an encryption key generator to securely generate a password for store in the registry. - Passwords are placed encrypted on the clipboard for 30 seconds. - Update documents and finalize comments
This commit is contained in:
@@ -40,22 +40,22 @@ namespace mRemoteNG.Config.Settings.Registry
|
||||
#region general credential registry settings
|
||||
|
||||
/// <summary>
|
||||
/// Setting that indicates whether exporting passwords is allowed.
|
||||
/// Specifies whether the export of passwords for saved connections is allowed.
|
||||
/// </summary>
|
||||
public static bool AllowExportPasswords { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting that indicates whether exporting usernames is allowed.
|
||||
/// Specifies whether the export of usernames for saved connections is allowed.
|
||||
/// </summary>
|
||||
public static bool AllowExportUsernames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting that indicates whether saving passwords in connections is allowed.
|
||||
/// Specifies whether the saving of usernames for saved connections is allowed.
|
||||
/// </summary>
|
||||
public static bool AllowSavePasswords { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting that indicates whether saving in connections usernames is allowed.
|
||||
/// Specifies whether the saving of passwords for saved connections is allowed.
|
||||
/// </summary>
|
||||
public static bool AllowSaveUsernames { get; }
|
||||
|
||||
|
||||
103
mRemoteNG/Config/Settings/Registry/OptRegistrySecurityPage.cs
Normal file
103
mRemoteNG/Config/Settings/Registry/OptRegistrySecurityPage.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.Runtime.Versioning;
|
||||
using Microsoft.Win32;
|
||||
using mRemoteNG.App.Info;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools.WindowsRegistry;
|
||||
|
||||
namespace mRemoteNG.Config.Settings.Registry
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public sealed partial class OptRegistrySecurityPage
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies whether the complete connections file is encrypted.
|
||||
/// </summary>
|
||||
public WinRegistryEntry<bool> EncryptCompleteConnectionsFile { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the encryption engine used for encryption.
|
||||
/// </summary>
|
||||
public WinRegistryEntry<string> EncryptionEngine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the block cipher mode used for encryption.
|
||||
/// </summary>
|
||||
public WinRegistryEntry<string> EncryptionBlockCipherMode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the number of iterations used in the encryption key derivation process.
|
||||
/// </summary>
|
||||
public WinRegistryEntry<int> EncryptionKeyDerivationIterations { get; private set; }
|
||||
|
||||
public OptRegistrySecurityPage()
|
||||
{
|
||||
|
||||
RegistryHive hive = WindowsRegistryInfo.Hive;
|
||||
string subKey = WindowsRegistryInfo.SecurityOptions;
|
||||
|
||||
EncryptCompleteConnectionsFile = new WinRegistryEntry<bool>(hive, subKey, nameof(EncryptCompleteConnectionsFile)).Read();
|
||||
EncryptionEngine = new WinRegistryEntry<string>(hive, subKey, nameof(EncryptionEngine)).Read();
|
||||
EncryptionBlockCipherMode = new WinRegistryEntry<string>(hive, subKey, nameof(EncryptionBlockCipherMode)).Read();
|
||||
EncryptionKeyDerivationIterations = new WinRegistryEntry<int>(hive, subKey, nameof(EncryptionKeyDerivationIterations)).Read();
|
||||
|
||||
SetupValidation();
|
||||
Apply();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures validation settings for various parameters
|
||||
/// </summary>
|
||||
private void SetupValidation()
|
||||
{
|
||||
var SecurityPage = new UI.Forms.OptionsPages.SecurityPage();
|
||||
|
||||
EncryptionEngine.SetValidation<BlockCipherEngines>();
|
||||
EncryptionBlockCipherMode.SetValidation<BlockCipherModes>();
|
||||
|
||||
int EncryptionKeyDerivIteraMin = (int)SecurityPage.numberBoxKdfIterations.Minimum;
|
||||
int EncryptionKeyDerivIteraMax = (int)SecurityPage.numberBoxKdfIterations.Maximum;
|
||||
EncryptionKeyDerivationIterations.SetValidation(EncryptionKeyDerivIteraMin, EncryptionKeyDerivIteraMax);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies registry settings and overrides various properties.
|
||||
/// </summary>
|
||||
private void Apply()
|
||||
{
|
||||
ApplyEncryptCompleteConnectionsFile();
|
||||
ApplyEncryptionEngine();
|
||||
ApplyEncryptionBlockCipherMode();
|
||||
ApplyEncryptionKeyDerivationIterations();
|
||||
}
|
||||
|
||||
private void ApplyEncryptCompleteConnectionsFile()
|
||||
{
|
||||
if (EncryptCompleteConnectionsFile.IsSet)
|
||||
Properties.OptionsSecurityPage.Default.EncryptCompleteConnectionsFile = EncryptCompleteConnectionsFile.Value;
|
||||
}
|
||||
|
||||
private void ApplyEncryptionEngine()
|
||||
{
|
||||
if (EncryptionEngine.IsValid)
|
||||
{
|
||||
BlockCipherEngines blockCipherEngines = (BlockCipherEngines)System.Enum.Parse(typeof(BlockCipherEngines), EncryptionEngine.Value);
|
||||
Properties.OptionsSecurityPage.Default.EncryptionEngine = blockCipherEngines;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEncryptionBlockCipherMode()
|
||||
{
|
||||
if (EncryptionBlockCipherMode.IsValid)
|
||||
{
|
||||
BlockCipherModes blockCipherModes = (BlockCipherModes)System.Enum.Parse(typeof(BlockCipherModes), EncryptionBlockCipherMode.Value);
|
||||
Properties.OptionsSecurityPage.Default.EncryptionBlockCipherMode = blockCipherModes;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEncryptionKeyDerivationIterations()
|
||||
{
|
||||
if (EncryptionKeyDerivationIterations.IsValid)
|
||||
Properties.OptionsSecurityPage.Default.EncryptionKeyDerivationIterations = EncryptionKeyDerivationIterations.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
179
mRemoteNG/UI/Forms/OptionsPages/SecurityPage.Designer.cs
generated
179
mRemoteNG/UI/Forms/OptionsPages/SecurityPage.Designer.cs
generated
@@ -40,20 +40,32 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
labelKdfIterations = new MrngLabel();
|
||||
numberBoxKdfIterations = new MrngNumericUpDown();
|
||||
btnTestSettings = new MrngButton();
|
||||
lblRegistrySettingsUsedInfo = new System.Windows.Forms.Label();
|
||||
pnlOptions = new System.Windows.Forms.Panel();
|
||||
groupPasswordGenerator = new System.Windows.Forms.GroupBox();
|
||||
pnlPasswordTxtAndBtn = new System.Windows.Forms.TableLayoutPanel();
|
||||
txtPasswdGenerator = new System.Windows.Forms.TextBox();
|
||||
btnPasswdGenerator = new System.Windows.Forms.Button();
|
||||
lblPasswdGenDescription = new System.Windows.Forms.Label();
|
||||
groupAdvancedSecurityOptions.SuspendLayout();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)numberBoxKdfIterations).BeginInit();
|
||||
pnlOptions.SuspendLayout();
|
||||
groupPasswordGenerator.SuspendLayout();
|
||||
pnlPasswordTxtAndBtn.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// chkEncryptCompleteFile
|
||||
//
|
||||
chkEncryptCompleteFile._mice = MrngCheckBox.MouseState.OUT;
|
||||
chkEncryptCompleteFile.AutoSize = true;
|
||||
chkEncryptCompleteFile.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
chkEncryptCompleteFile.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
chkEncryptCompleteFile.Location = new System.Drawing.Point(12, 10);
|
||||
chkEncryptCompleteFile.Location = new System.Drawing.Point(0, 0);
|
||||
chkEncryptCompleteFile.Margin = new System.Windows.Forms.Padding(6);
|
||||
chkEncryptCompleteFile.Name = "chkEncryptCompleteFile";
|
||||
chkEncryptCompleteFile.Size = new System.Drawing.Size(286, 27);
|
||||
chkEncryptCompleteFile.Padding = new System.Windows.Forms.Padding(4);
|
||||
chkEncryptCompleteFile.Size = new System.Drawing.Size(610, 25);
|
||||
chkEncryptCompleteFile.TabIndex = 0;
|
||||
chkEncryptCompleteFile.Text = "Encrypt complete connection file";
|
||||
chkEncryptCompleteFile.UseVisualStyleBackColor = true;
|
||||
@@ -64,10 +76,10 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
comboBoxEncryptionEngine.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
comboBoxEncryptionEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
comboBoxEncryptionEngine.FormattingEnabled = true;
|
||||
comboBoxEncryptionEngine.Location = new System.Drawing.Point(280, 4);
|
||||
comboBoxEncryptionEngine.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
comboBoxEncryptionEngine.Location = new System.Drawing.Point(197, 8);
|
||||
comboBoxEncryptionEngine.Margin = new System.Windows.Forms.Padding(4);
|
||||
comboBoxEncryptionEngine.Name = "comboBoxEncryptionEngine";
|
||||
comboBoxEncryptionEngine.Size = new System.Drawing.Size(196, 31);
|
||||
comboBoxEncryptionEngine.Size = new System.Drawing.Size(196, 21);
|
||||
comboBoxEncryptionEngine.Sorted = true;
|
||||
comboBoxEncryptionEngine.TabIndex = 1;
|
||||
//
|
||||
@@ -75,10 +87,10 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
//
|
||||
labelEncryptionEngine.AutoSize = true;
|
||||
labelEncryptionEngine.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
labelEncryptionEngine.Location = new System.Drawing.Point(4, 0);
|
||||
labelEncryptionEngine.Location = new System.Drawing.Point(8, 4);
|
||||
labelEncryptionEngine.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
labelEncryptionEngine.Name = "labelEncryptionEngine";
|
||||
labelEncryptionEngine.Size = new System.Drawing.Size(268, 39);
|
||||
labelEncryptionEngine.Size = new System.Drawing.Size(181, 29);
|
||||
labelEncryptionEngine.TabIndex = 0;
|
||||
labelEncryptionEngine.Text = "Encryption Engine";
|
||||
labelEncryptionEngine.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
@@ -87,10 +99,10 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
//
|
||||
labelBlockCipher.AutoSize = true;
|
||||
labelBlockCipher.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
labelBlockCipher.Location = new System.Drawing.Point(4, 39);
|
||||
labelBlockCipher.Location = new System.Drawing.Point(8, 33);
|
||||
labelBlockCipher.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
labelBlockCipher.Name = "labelBlockCipher";
|
||||
labelBlockCipher.Size = new System.Drawing.Size(268, 39);
|
||||
labelBlockCipher.Size = new System.Drawing.Size(181, 29);
|
||||
labelBlockCipher.TabIndex = 2;
|
||||
labelBlockCipher.Text = "Block Cipher Mode";
|
||||
labelBlockCipher.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
@@ -101,20 +113,22 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
comboBoxBlockCipher.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
comboBoxBlockCipher.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
comboBoxBlockCipher.FormattingEnabled = true;
|
||||
comboBoxBlockCipher.Location = new System.Drawing.Point(280, 43);
|
||||
comboBoxBlockCipher.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
comboBoxBlockCipher.Location = new System.Drawing.Point(197, 37);
|
||||
comboBoxBlockCipher.Margin = new System.Windows.Forms.Padding(4);
|
||||
comboBoxBlockCipher.Name = "comboBoxBlockCipher";
|
||||
comboBoxBlockCipher.Size = new System.Drawing.Size(196, 31);
|
||||
comboBoxBlockCipher.Size = new System.Drawing.Size(196, 21);
|
||||
comboBoxBlockCipher.TabIndex = 2;
|
||||
//
|
||||
// groupAdvancedSecurityOptions
|
||||
//
|
||||
groupAdvancedSecurityOptions.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
groupAdvancedSecurityOptions.Controls.Add(tableLayoutPanel1);
|
||||
groupAdvancedSecurityOptions.Location = new System.Drawing.Point(12, 51);
|
||||
groupAdvancedSecurityOptions.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
groupAdvancedSecurityOptions.Location = new System.Drawing.Point(0, 25);
|
||||
groupAdvancedSecurityOptions.Margin = new System.Windows.Forms.Padding(6);
|
||||
groupAdvancedSecurityOptions.Name = "groupAdvancedSecurityOptions";
|
||||
groupAdvancedSecurityOptions.Padding = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
groupAdvancedSecurityOptions.Size = new System.Drawing.Size(906, 201);
|
||||
groupAdvancedSecurityOptions.Padding = new System.Windows.Forms.Padding(4);
|
||||
groupAdvancedSecurityOptions.Size = new System.Drawing.Size(610, 159);
|
||||
groupAdvancedSecurityOptions.TabIndex = 1;
|
||||
groupAdvancedSecurityOptions.TabStop = false;
|
||||
groupAdvancedSecurityOptions.Text = "Advanced Security Options";
|
||||
@@ -133,25 +147,26 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
tableLayoutPanel1.Controls.Add(comboBoxBlockCipher, 1, 1);
|
||||
tableLayoutPanel1.Controls.Add(btnTestSettings, 1, 3);
|
||||
tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
tableLayoutPanel1.Location = new System.Drawing.Point(4, 26);
|
||||
tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
tableLayoutPanel1.Location = new System.Drawing.Point(4, 19);
|
||||
tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4);
|
||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(4);
|
||||
tableLayoutPanel1.RowCount = 4;
|
||||
tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
tableLayoutPanel1.Size = new System.Drawing.Size(480, 171);
|
||||
tableLayoutPanel1.Size = new System.Drawing.Size(401, 136);
|
||||
tableLayoutPanel1.TabIndex = 2;
|
||||
//
|
||||
// labelKdfIterations
|
||||
//
|
||||
labelKdfIterations.AutoSize = true;
|
||||
labelKdfIterations.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
labelKdfIterations.Location = new System.Drawing.Point(4, 78);
|
||||
labelKdfIterations.Location = new System.Drawing.Point(8, 62);
|
||||
labelKdfIterations.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
labelKdfIterations.Name = "labelKdfIterations";
|
||||
labelKdfIterations.Size = new System.Drawing.Size(268, 37);
|
||||
labelKdfIterations.Size = new System.Drawing.Size(181, 30);
|
||||
labelKdfIterations.TabIndex = 4;
|
||||
labelKdfIterations.Text = "Key Derivation Function Iterations";
|
||||
labelKdfIterations.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
@@ -160,12 +175,12 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
//
|
||||
numberBoxKdfIterations.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
numberBoxKdfIterations.Increment = new decimal(new int[] { 1000, 0, 0, 0 });
|
||||
numberBoxKdfIterations.Location = new System.Drawing.Point(280, 82);
|
||||
numberBoxKdfIterations.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
numberBoxKdfIterations.Location = new System.Drawing.Point(197, 66);
|
||||
numberBoxKdfIterations.Margin = new System.Windows.Forms.Padding(4);
|
||||
numberBoxKdfIterations.Maximum = new decimal(new int[] { 50000, 0, 0, 0 });
|
||||
numberBoxKdfIterations.Minimum = new decimal(new int[] { 1000, 0, 0, 0 });
|
||||
numberBoxKdfIterations.Name = "numberBoxKdfIterations";
|
||||
numberBoxKdfIterations.Size = new System.Drawing.Size(196, 29);
|
||||
numberBoxKdfIterations.Size = new System.Drawing.Size(196, 22);
|
||||
numberBoxKdfIterations.TabIndex = 3;
|
||||
numberBoxKdfIterations.ThousandsSeparator = true;
|
||||
numberBoxKdfIterations.Value = new decimal(new int[] { 1000, 0, 0, 0 });
|
||||
@@ -174,32 +189,121 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
//
|
||||
btnTestSettings._mice = MrngButton.MouseState.OUT;
|
||||
btnTestSettings.AutoSize = true;
|
||||
btnTestSettings.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
btnTestSettings.Location = new System.Drawing.Point(294, 119);
|
||||
btnTestSettings.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
btnTestSettings.Location = new System.Drawing.Point(197, 96);
|
||||
btnTestSettings.Margin = new System.Windows.Forms.Padding(4);
|
||||
btnTestSettings.Name = "btnTestSettings";
|
||||
btnTestSettings.Size = new System.Drawing.Size(182, 52);
|
||||
btnTestSettings.Padding = new System.Windows.Forms.Padding(4);
|
||||
btnTestSettings.Size = new System.Drawing.Size(196, 33);
|
||||
btnTestSettings.TabIndex = 4;
|
||||
btnTestSettings.Text = "Test Settings";
|
||||
btnTestSettings.UseVisualStyleBackColor = true;
|
||||
btnTestSettings.Click += BtnTestSettings_Click;
|
||||
//
|
||||
// lblRegistrySettingsUsedInfo
|
||||
//
|
||||
lblRegistrySettingsUsedInfo.BackColor = System.Drawing.SystemColors.ControlLight;
|
||||
lblRegistrySettingsUsedInfo.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
lblRegistrySettingsUsedInfo.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
lblRegistrySettingsUsedInfo.Location = new System.Drawing.Point(0, 0);
|
||||
lblRegistrySettingsUsedInfo.Name = "lblRegistrySettingsUsedInfo";
|
||||
lblRegistrySettingsUsedInfo.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0);
|
||||
lblRegistrySettingsUsedInfo.Size = new System.Drawing.Size(610, 30);
|
||||
lblRegistrySettingsUsedInfo.TabIndex = 3;
|
||||
lblRegistrySettingsUsedInfo.Text = "Some settings are configured by your Administrator. Please contact your administrator for more information.";
|
||||
lblRegistrySettingsUsedInfo.Visible = false;
|
||||
//
|
||||
// pnlOptions
|
||||
//
|
||||
pnlOptions.Controls.Add(groupAdvancedSecurityOptions);
|
||||
pnlOptions.Controls.Add(chkEncryptCompleteFile);
|
||||
pnlOptions.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
pnlOptions.Location = new System.Drawing.Point(0, 30);
|
||||
pnlOptions.Name = "pnlOptions";
|
||||
pnlOptions.Size = new System.Drawing.Size(610, 201);
|
||||
pnlOptions.TabIndex = 2;
|
||||
//
|
||||
// groupPasswordGenerator
|
||||
//
|
||||
groupPasswordGenerator.Controls.Add(pnlPasswordTxtAndBtn);
|
||||
groupPasswordGenerator.Controls.Add(lblPasswdGenDescription);
|
||||
groupPasswordGenerator.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
groupPasswordGenerator.Location = new System.Drawing.Point(0, 231);
|
||||
groupPasswordGenerator.Name = "groupPasswordGenerator";
|
||||
groupPasswordGenerator.Size = new System.Drawing.Size(610, 116);
|
||||
groupPasswordGenerator.TabIndex = 4;
|
||||
groupPasswordGenerator.TabStop = false;
|
||||
groupPasswordGenerator.Text = "Secure Key Generator";
|
||||
//
|
||||
// pnlPasswordTxtAndBtn
|
||||
//
|
||||
pnlPasswordTxtAndBtn.ColumnCount = 2;
|
||||
pnlPasswordTxtAndBtn.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 63.91437F));
|
||||
pnlPasswordTxtAndBtn.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 36.08563F));
|
||||
pnlPasswordTxtAndBtn.Controls.Add(txtPasswdGenerator, 0, 0);
|
||||
pnlPasswordTxtAndBtn.Controls.Add(btnPasswdGenerator, 1, 0);
|
||||
pnlPasswordTxtAndBtn.Location = new System.Drawing.Point(4, 43);
|
||||
pnlPasswordTxtAndBtn.Name = "pnlPasswordTxtAndBtn";
|
||||
pnlPasswordTxtAndBtn.RowCount = 1;
|
||||
pnlPasswordTxtAndBtn.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
pnlPasswordTxtAndBtn.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
pnlPasswordTxtAndBtn.Size = new System.Drawing.Size(327, 31);
|
||||
pnlPasswordTxtAndBtn.TabIndex = 1;
|
||||
//
|
||||
// txtPasswdGenerator
|
||||
//
|
||||
txtPasswdGenerator.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
txtPasswdGenerator.Location = new System.Drawing.Point(3, 4);
|
||||
txtPasswdGenerator.Name = "txtPasswdGenerator";
|
||||
txtPasswdGenerator.PasswordChar = '*';
|
||||
txtPasswdGenerator.Size = new System.Drawing.Size(203, 22);
|
||||
txtPasswdGenerator.TabIndex = 1;
|
||||
txtPasswdGenerator.TextChanged += txtPasswdGenerator_TextChanged;
|
||||
//
|
||||
// btnPasswdGenerator
|
||||
//
|
||||
btnPasswdGenerator.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
btnPasswdGenerator.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
btnPasswdGenerator.Enabled = false;
|
||||
btnPasswdGenerator.Location = new System.Drawing.Point(212, 4);
|
||||
btnPasswdGenerator.Name = "btnPasswdGenerator";
|
||||
btnPasswdGenerator.Size = new System.Drawing.Size(112, 23);
|
||||
btnPasswdGenerator.TabIndex = 2;
|
||||
btnPasswdGenerator.Text = "Copy to Clipboard";
|
||||
btnPasswdGenerator.UseVisualStyleBackColor = true;
|
||||
btnPasswdGenerator.Click += btnPasswdGenerator_Click;
|
||||
//
|
||||
// lblPasswdGenDescription
|
||||
//
|
||||
lblPasswdGenDescription.AutoSize = true;
|
||||
lblPasswdGenDescription.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
lblPasswdGenDescription.Location = new System.Drawing.Point(3, 18);
|
||||
lblPasswdGenDescription.Name = "lblPasswdGenDescription";
|
||||
lblPasswdGenDescription.Size = new System.Drawing.Size(327, 13);
|
||||
lblPasswdGenDescription.TabIndex = 0;
|
||||
lblPasswdGenDescription.Text = "Generate an encrypted password suitable for registry settings.";
|
||||
//
|
||||
// SecurityPage
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(144F, 144F);
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
Controls.Add(chkEncryptCompleteFile);
|
||||
Controls.Add(groupAdvancedSecurityOptions);
|
||||
Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
Controls.Add(groupPasswordGenerator);
|
||||
Controls.Add(pnlOptions);
|
||||
Controls.Add(lblRegistrySettingsUsedInfo);
|
||||
Margin = new System.Windows.Forms.Padding(4);
|
||||
Name = "SecurityPage";
|
||||
Size = new System.Drawing.Size(915, 735);
|
||||
Size = new System.Drawing.Size(610, 490);
|
||||
groupAdvancedSecurityOptions.ResumeLayout(false);
|
||||
groupAdvancedSecurityOptions.PerformLayout();
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)numberBoxKdfIterations).EndInit();
|
||||
pnlOptions.ResumeLayout(false);
|
||||
pnlOptions.PerformLayout();
|
||||
groupPasswordGenerator.ResumeLayout(false);
|
||||
groupPasswordGenerator.PerformLayout();
|
||||
pnlPasswordTxtAndBtn.ResumeLayout(false);
|
||||
pnlPasswordTxtAndBtn.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -210,9 +314,16 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
private Controls.MrngLabel labelBlockCipher;
|
||||
private MrngComboBox comboBoxBlockCipher;
|
||||
private MrngGroupBox groupAdvancedSecurityOptions;
|
||||
private Controls.MrngNumericUpDown numberBoxKdfIterations;
|
||||
internal Controls.MrngNumericUpDown numberBoxKdfIterations;
|
||||
private Controls.MrngLabel labelKdfIterations;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private MrngButton btnTestSettings;
|
||||
internal System.Windows.Forms.Label lblRegistrySettingsUsedInfo;
|
||||
private System.Windows.Forms.Panel pnlOptions;
|
||||
private System.Windows.Forms.GroupBox groupPasswordGenerator;
|
||||
private System.Windows.Forms.TableLayoutPanel pnlPasswordTxtAndBtn;
|
||||
private System.Windows.Forms.TextBox txtPasswdGenerator;
|
||||
private System.Windows.Forms.Button btnPasswdGenerator;
|
||||
private System.Windows.Forms.Label lblPasswdGenDescription;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,22 @@ using mRemoteNG.Security;
|
||||
using mRemoteNG.Security.Factories;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using System.Runtime.Versioning;
|
||||
using mRemoteNG.Config.Settings.Registry;
|
||||
using mRemoteNG.Security.SymmetricEncryption;
|
||||
|
||||
namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public sealed partial class SecurityPage : OptionsPage
|
||||
{
|
||||
#region Private Fields
|
||||
private OptRegistrySecurityPage pageRegSettingsInstance;
|
||||
|
||||
private readonly Timer clipboardClearTimer = new() { Interval = 1000 };
|
||||
private const int clipboardClearSeconds = 30;
|
||||
private int countdownSeconds = clipboardClearSeconds;
|
||||
#endregion
|
||||
|
||||
public SecurityPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -41,6 +51,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
labelKdfIterations.Text = Language.EncryptionKeyDerivationIterations;
|
||||
groupAdvancedSecurityOptions.Text = Language.AdvancedSecurityOptions;
|
||||
btnTestSettings.Text = Language.TestSettings;
|
||||
lblRegistrySettingsUsedInfo.Text = Language.OptionsCompanyPolicyMessage;
|
||||
}
|
||||
|
||||
public override void LoadSettings()
|
||||
@@ -60,6 +71,41 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
Properties.OptionsSecurityPage.Default.EncryptionKeyDerivationIterations = (int)numberBoxKdfIterations.Value;
|
||||
}
|
||||
|
||||
public override void LoadRegistrySettings()
|
||||
{
|
||||
Type settingsType = typeof(OptRegistrySecurityPage);
|
||||
RegistryLoader.RegistrySettings.TryGetValue(settingsType, out var settings);
|
||||
pageRegSettingsInstance = settings as OptRegistrySecurityPage;
|
||||
|
||||
RegistryLoader.Cleanup(settingsType);
|
||||
|
||||
// ***
|
||||
// Disable controls based on the registry settings.
|
||||
//
|
||||
if (pageRegSettingsInstance.EncryptCompleteConnectionsFile.IsSet)
|
||||
DisableControl(chkEncryptCompleteFile);
|
||||
|
||||
if (pageRegSettingsInstance.EncryptionEngine.IsSet)
|
||||
DisableControl(comboBoxEncryptionEngine);
|
||||
|
||||
if (pageRegSettingsInstance.EncryptionBlockCipherMode.IsSet)
|
||||
DisableControl(comboBoxBlockCipher);
|
||||
|
||||
if (pageRegSettingsInstance.EncryptionKeyDerivationIterations.IsSet)
|
||||
DisableControl(numberBoxKdfIterations);
|
||||
|
||||
// Updates the visibility of the information label indicating whether registry settings are used.
|
||||
lblRegistrySettingsUsedInfo.Visible = ShowRegistrySettingsUsedInfo();
|
||||
}
|
||||
|
||||
private bool ShowRegistrySettingsUsedInfo()
|
||||
{
|
||||
return pageRegSettingsInstance.EncryptCompleteConnectionsFile.IsSet
|
||||
|| pageRegSettingsInstance.EncryptionEngine.IsSet
|
||||
|| pageRegSettingsInstance.EncryptionBlockCipherMode.IsSet
|
||||
|| pageRegSettingsInstance.EncryptionKeyDerivationIterations.IsSet;
|
||||
}
|
||||
|
||||
public override void RevertSettings()
|
||||
{
|
||||
}
|
||||
@@ -101,5 +147,108 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the entered password, copies it to the clipboard, and starts a 30-second timer to clear the clipboard.
|
||||
/// Also updates a countdown display in the UI.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event source.</param>
|
||||
/// <param name="e">Event data.</param>
|
||||
private void btnPasswdGenerator_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(txtPasswdGenerator.Text)) return;
|
||||
|
||||
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
|
||||
|
||||
try
|
||||
{
|
||||
// Encrypt and set the clipboard content
|
||||
string encryptedText = cryptographyProvider.Encrypt(txtPasswdGenerator.Text, Runtime.EncryptionKey);
|
||||
System.Windows.Clipboard.SetText(encryptedText);
|
||||
}
|
||||
catch (System.Runtime.InteropServices.COMException)
|
||||
{
|
||||
Runtime.MessageCollector.AddMessage(Messages.MessageClass.ErrorMsg, "Failed to set clipboard content. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the countdown and start the timer
|
||||
ResetAndStartTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the countdown timer and starts it.
|
||||
/// </summary>
|
||||
private void ResetAndStartTimer()
|
||||
{
|
||||
// Stop the timer if it's running
|
||||
if (clipboardClearTimer.Enabled)
|
||||
{
|
||||
clipboardClearTimer.Stop();
|
||||
}
|
||||
|
||||
countdownSeconds = clipboardClearSeconds; // Reset countdown
|
||||
clipboardClearTimer.Tick -= ClipboardClearTimer_Tick; // Detach in case it was previously attached
|
||||
clipboardClearTimer.Tick += ClipboardClearTimer_Tick; // Re-attach event handler
|
||||
clipboardClearTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the timer tick event for clearing the clipboard.
|
||||
/// Decrements the countdown, updates the UI label, and clears the clipboard when the countdown reaches zero.
|
||||
/// Stops the timer and handles any errors that occur during clipboard clearing.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event source.</param>
|
||||
/// <param name="e">Event data.</param>
|
||||
private void ClipboardClearTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (countdownSeconds > 0)
|
||||
{
|
||||
countdownSeconds--;
|
||||
}
|
||||
else
|
||||
{
|
||||
StopClipboardClearTimer();
|
||||
ClearClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the clipboard clear timer and detaches the event handler.
|
||||
/// </summary>
|
||||
private void StopClipboardClearTimer()
|
||||
{
|
||||
clipboardClearTimer.Stop();
|
||||
clipboardClearTimer.Tick -= ClipboardClearTimer_Tick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the clipboard and handles any exceptions that occur.
|
||||
/// </summary>
|
||||
private void ClearClipboard()
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Windows.Clipboard.Clear();
|
||||
txtPasswdGenerator.Clear();
|
||||
}
|
||||
catch (System.Runtime.InteropServices.COMException)
|
||||
{
|
||||
Runtime.MessageCollector.AddMessage(Messages.MessageClass.ErrorMsg, "Failed to clear clipboard.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the TextChanged event for the password generator TextBox.
|
||||
/// Enables or disables the button depending on whether the TextBox has content.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event source.</param>
|
||||
/// <param name="e">Event data.</param>
|
||||
private void txtPasswdGenerator_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
// Enable the button if there's text in the TextBox, disable it otherwise.
|
||||
btnPasswdGenerator.Enabled = !string.IsNullOrWhiteSpace(txtPasswdGenerator.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,64 @@
|
||||
<root>
|
||||
<?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">
|
||||
|
||||
@@ -18,7 +18,7 @@ Common
|
||||
|
||||
Allow Export Usernames
|
||||
----------------------
|
||||
Determines whether exporting usernames is allowed.
|
||||
Specifies whether the export of usernames for saved connections is allowed.
|
||||
|
||||
- **Value Name:** ``AllowExportUsernames``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
@@ -30,7 +30,7 @@ Determines whether exporting usernames is allowed.
|
||||
|
||||
Allow Export Passwords
|
||||
----------------------
|
||||
Determines whether exporting passwords is allowed.
|
||||
Specifies whether the export of passwords for saved connections is allowed.
|
||||
|
||||
- **Value Name:** ``AllowExportPasswords``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
@@ -42,7 +42,7 @@ Determines whether exporting passwords is allowed.
|
||||
|
||||
Allow Save Usernames
|
||||
--------------------
|
||||
Determines whether saving usernames is allowed.
|
||||
Specifies whether the saving of usernames for saved connections is allowed.
|
||||
|
||||
- **Value Name:** ``AllowSaveUsernames``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
@@ -61,7 +61,7 @@ Determines whether saving usernames is allowed.
|
||||
|
||||
Allow Save Passwords
|
||||
--------------------
|
||||
Determines whether saving passwords is allowed.
|
||||
Specifies whether the saving of passwords for saved connections is allowed.
|
||||
|
||||
- **Value Name:** ``AllowSavePasswords``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
@@ -154,7 +154,8 @@ Specifies the default domain.
|
||||
|
||||
Default Username Enabled
|
||||
------------------------
|
||||
Specifies that entering the custom default username field is enabled.
|
||||
Controls whether the default username field is enabled or disabled.
|
||||
Locking the field may make more sense than disabling the entire settings option.
|
||||
|
||||
- **Value Name:** ``DefaultUsernameEnabled``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
@@ -166,7 +167,8 @@ Specifies that entering the custom default username field is enabled.
|
||||
|
||||
Default Password Enabled
|
||||
------------------------
|
||||
Specifies that entering the custom default password field is enabled.
|
||||
Controls whether the default password field is enabled or disabled.
|
||||
Locking the field may make more sense than disabling the entire settings option.
|
||||
|
||||
- **Value Name:** ``DefaultPasswordEnabled``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
@@ -178,7 +180,8 @@ Specifies that entering the custom default password field is enabled.
|
||||
|
||||
Default User Via API Enabled
|
||||
----------------------------
|
||||
Specifies that entering the custom default api user field is enabled.
|
||||
Controls whether the default user via API field is enabled or disabled.
|
||||
Locking the field may make more sense than disabling the entire settings option.
|
||||
|
||||
- **Value Name:** ``DefaultUserViaAPIEnabled``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
|
||||
82
mRemoteNGDocumentation/registry/security_settings.rst
Normal file
82
mRemoteNGDocumentation/registry/security_settings.rst
Normal file
@@ -0,0 +1,82 @@
|
||||
*****************
|
||||
Security Settings
|
||||
*****************
|
||||
.. versionadded:: v1.77.3
|
||||
|
||||
.. warning::
|
||||
Before proceeding with any changes to the Windows Registry, it is imperative that you carefully read and comprehend the
|
||||
**Modifying the Registry**, **Restricted Registry Settings** and **Disclaimer**
|
||||
on :doc:`Registry Settings Infromation <registry_settings_information>`.
|
||||
|
||||
|
||||
Options
|
||||
====================
|
||||
Configure the options page to modify functionalities as described.
|
||||
|
||||
- **Registry Hive:** ``HKEY_LOCAL_MACHINE``
|
||||
- **Registry Path:** ``SOFTWARE\mRemoteNG\Security\Options``
|
||||
|
||||
Encrypt Complete Connections File
|
||||
---------------------------------
|
||||
Specifies the encryption engine used for encryption.
|
||||
|
||||
- **Value Name:** ``EncryptCompleteConnectionsFile``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
- **Values:**
|
||||
|
||||
- Enable: ``true``
|
||||
- Disable: ``false``
|
||||
|
||||
|
||||
Encryption Engine
|
||||
-----------------
|
||||
Specifies the encryption engine used for encryption.
|
||||
|
||||
- **Value Name:** ``EncryptionEngine``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
- **Values:**
|
||||
|
||||
- ``AES``
|
||||
- ``Serpent``
|
||||
- ``Twofish``
|
||||
|
||||
|
||||
Encryption Block Cipher Mode
|
||||
----------------------------
|
||||
Specifies the block cipher mode used for encryption.
|
||||
|
||||
- **Value Name:** ``EncryptionBlockCipherMode``
|
||||
- **Value Type:** ``REG_SZ``
|
||||
- **Values:**
|
||||
|
||||
- ``GCM``
|
||||
- ``CCM``
|
||||
- ``EAX``
|
||||
|
||||
|
||||
Encryption Key Derivation Iterations
|
||||
------------------------------------
|
||||
Specifies the number of iterations used in the encryption key derivation process.
|
||||
|
||||
- **Value Name:** ``EncryptionKeyDerivationIterations``
|
||||
- **Value Type:** ``REG_DWORD``
|
||||
- **Values:**
|
||||
|
||||
- Minimum: ``1000``
|
||||
- Maximum: ``50000``
|
||||
|
||||
|
||||
Registry Template
|
||||
=================
|
||||
|
||||
.. code::
|
||||
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\mRemoteNG\Security]
|
||||
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\mRemoteNG\Security\Options]
|
||||
"EncryptionEngine"="AES"
|
||||
"EncryptionBlockCipherMode"="GCM"
|
||||
"EncryptCompleteConnectionsFile"="false"
|
||||
"EncryptionKeyDerivationIterations"=dword:00009c40
|
||||
@@ -30,3 +30,4 @@ Make changes with caution and ensure that you have backups before making any adj
|
||||
registry/credential_settings.rst
|
||||
registry/sqlServer_settings.rst
|
||||
registry/updates_settings.rst
|
||||
registry/security_settings.rst
|
||||
Reference in New Issue
Block a user