mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 22:11:48 +08:00
Moved enhancements made in the CredentialManager branch to a dedicated branch
This commit is contained in:
58
mRemoteNGTests/Security/AesCryptographyProviderTests.cs
Normal file
58
mRemoteNGTests/Security/AesCryptographyProviderTests.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Security;
|
||||
using mRemoteNG.Security;
|
||||
using NUnit.Framework;
|
||||
|
||||
|
||||
namespace mRemoteNGTests.Security
|
||||
{
|
||||
public class AesCryptographyProviderTests
|
||||
{
|
||||
private AesCryptographyProvider _aesCryptographyProvider;
|
||||
private SecureString _encryptionKey;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_aesCryptographyProvider = new AesCryptographyProvider();
|
||||
_encryptionKey = "mypassword111111".ConvertToSecureString();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_aesCryptographyProvider = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetBlockSizeReturnsProperValueForAes()
|
||||
{
|
||||
Assert.That(_aesCryptographyProvider.BlockSize, Is.EqualTo(16));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EncryptionOutputsBase64String()
|
||||
{
|
||||
var plainText = "MySecret!";
|
||||
var cipherText = _aesCryptographyProvider.Encrypt(plainText, _encryptionKey);
|
||||
Assert.That(cipherText.IsBase64String, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecryptedTextIsEqualToOriginalPlainText()
|
||||
{
|
||||
var plainText = "MySecret!";
|
||||
var cipherText = _aesCryptographyProvider.Encrypt(plainText, _encryptionKey);
|
||||
var decryptedCipherText = _aesCryptographyProvider.Decrypt(cipherText, _encryptionKey);
|
||||
Assert.That(decryptedCipherText, Is.EqualTo(plainText));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EncryptingTheSameValueReturnsNewCipherTextEachTime()
|
||||
{
|
||||
var plainText = "MySecret!";
|
||||
var cipherText1 = _aesCryptographyProvider.Encrypt(plainText, _encryptionKey);
|
||||
var cipherText2 = _aesCryptographyProvider.Encrypt(plainText, _encryptionKey);
|
||||
Assert.That(cipherText1, Is.Not.EqualTo(cipherText2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,10 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
|
||||
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\mRemoteV1\References\log4net.dll</HintPath>
|
||||
@@ -100,6 +104,7 @@
|
||||
<Compile Include="Config\Connections\SqlUpdateQueryBuilderTest.cs" />
|
||||
<Compile Include="Config\Connections\SqlUpdateTimerTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Security\AesCryptographyProviderTests.cs" />
|
||||
<Compile Include="UI\Controls\CustomListViewTests.cs" />
|
||||
<Compile Include="UI\Controls\TestForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="BouncyCastle" version="1.8.1" targetFramework="net45" />
|
||||
<package id="NSubstitute" version="1.10.0.0" targetFramework="net45" />
|
||||
<package id="NUnit" version="3.2.0" targetFramework="net45" />
|
||||
</packages>
|
||||
33
mRemoteV1/Security/AesCryptographyProvider.cs
Normal file
33
mRemoteV1/Security/AesCryptographyProvider.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using Org.BouncyCastle.Crypto.Engines;
|
||||
|
||||
|
||||
namespace mRemoteNG.Security
|
||||
{
|
||||
public class AesCryptographyProvider : ICryptographyProvider
|
||||
{
|
||||
private AesEngine _aesEngine;
|
||||
private readonly Encoding _encoding;
|
||||
|
||||
public int BlockSize => _aesEngine.GetBlockSize();
|
||||
|
||||
public AesCryptographyProvider()
|
||||
{
|
||||
_aesEngine = new AesEngine();
|
||||
_encoding = Encoding.UTF8;
|
||||
}
|
||||
|
||||
public string Encrypt(string plainText, SecureString encryptionKey)
|
||||
{
|
||||
var bcEngine = new BouncyCastleCryptographyEngine(_aesEngine, _encoding);
|
||||
return bcEngine.Encrypt(plainText, encryptionKey);
|
||||
}
|
||||
|
||||
public string Decrypt(string cipherText, SecureString decryptionKey)
|
||||
{
|
||||
var bcEngine = new BouncyCastleCryptographyEngine(_aesEngine, _encoding);
|
||||
return bcEngine.Decrypt(cipherText, decryptionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
mRemoteV1/Security/BouncyCastleCryptographyEngine.cs
Normal file
82
mRemoteV1/Security/BouncyCastleCryptographyEngine.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Modes;
|
||||
using Org.BouncyCastle.Crypto.Paddings;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace mRemoteNG.Security
|
||||
{
|
||||
internal class BouncyCastleCryptographyEngine
|
||||
{
|
||||
private readonly Encoding _encoding;
|
||||
private readonly IBlockCipher _blockCipher;
|
||||
private IBlockCipherPadding _padding;
|
||||
private const bool ActionEncrypt = true;
|
||||
private const bool ActionDecrypt = false;
|
||||
|
||||
internal IBlockCipherPadding Padding
|
||||
{
|
||||
get { return _padding; }
|
||||
set {
|
||||
if (value != null)
|
||||
_padding = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal BouncyCastleCryptographyEngine(IBlockCipher cipherEngine, Encoding encoding)
|
||||
{
|
||||
_blockCipher = new CbcBlockCipher(cipherEngine);
|
||||
_encoding = encoding;
|
||||
}
|
||||
|
||||
internal string Encrypt(string plain, SecureString key)
|
||||
{
|
||||
var result = BouncyCastleCrypto(ActionEncrypt, plain, key);
|
||||
return Convert.ToBase64String(result);
|
||||
}
|
||||
|
||||
internal string Decrypt(string cipher, SecureString key)
|
||||
{
|
||||
var result = BouncyCastleCrypto(ActionDecrypt, cipher, key);
|
||||
return _encoding.GetString(result);
|
||||
}
|
||||
|
||||
|
||||
private byte[] BouncyCastleCrypto(bool forEncrypt, string input, SecureString key)
|
||||
{
|
||||
try
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
var iv = BuildInitializationVector(); //for the sake of demo
|
||||
|
||||
//Set up
|
||||
var cipher = new PaddedBufferedBlockCipher(_blockCipher);
|
||||
var keyParam = new KeyParameter(Encoding.Default.GetBytes(key.ConvertToUnsecureString()));
|
||||
var keyParamWithIv = new ParametersWithIV(keyParam, iv, 0, 16);
|
||||
|
||||
// Encrypt/Decrypt
|
||||
cipher.Init(forEncrypt, keyParamWithIv);
|
||||
var outputBytes = new byte[cipher.GetOutputSize(inputBytes.Length)];
|
||||
var length = cipher.ProcessBytes(inputBytes, outputBytes, 0);
|
||||
cipher.DoFinal(outputBytes, length); //Do the final block
|
||||
return outputBytes;
|
||||
}
|
||||
catch (CryptoException ex)
|
||||
{
|
||||
throw new CryptoException("", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] BuildInitializationVector()
|
||||
{
|
||||
var numberOfBytes = _blockCipher.GetBlockSize();
|
||||
var iv = new byte[numberOfBytes];
|
||||
var randomNumberGenerator = new SecureRandom();
|
||||
randomNumberGenerator.NextBytes(iv, 0, numberOfBytes);
|
||||
return iv;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
mRemoteV1/Security/ICryptographyProvider.cs
Normal file
11
mRemoteV1/Security/ICryptographyProvider.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Security;
|
||||
|
||||
namespace mRemoteNG.Security
|
||||
{
|
||||
public interface ICryptographyProvider
|
||||
{
|
||||
string Encrypt(string plainText, SecureString encryptionKey);
|
||||
|
||||
string Decrypt(string cipherText, SecureString decryptionKey);
|
||||
}
|
||||
}
|
||||
64
mRemoteV1/Security/SecureStringExtensions.cs
Normal file
64
mRemoteV1/Security/SecureStringExtensions.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace mRemoteNG.Security
|
||||
{
|
||||
public static class SecureStringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Method to marshall a SecureString out of protected memory into a standard String object that is required by most other functions.
|
||||
/// Code initially taken from Fabio Pintos
|
||||
/// Source: https://blogs.msdn.microsoft.com/fpintos/2009/06/12/how-to-properly-convert-securestring-to-string/
|
||||
/// </summary>
|
||||
/// <param name="securePassword"></param>
|
||||
/// <returns></returns>
|
||||
public static string ConvertToUnsecureString(this SecureString securePassword)
|
||||
{
|
||||
if (securePassword == null)
|
||||
throw new ArgumentNullException(nameof(securePassword));
|
||||
|
||||
var unmanagedString = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
|
||||
return Marshal.PtrToStringUni(unmanagedString);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ConvertToEncryptedString(this SecureString secureString)
|
||||
{
|
||||
return "TEST ENCRYPTION";
|
||||
}
|
||||
|
||||
public static SecureString ConvertFromEncryptedString(this SecureString secureString, string encryptedString)
|
||||
{
|
||||
var unencryptedString = encryptedString;
|
||||
return unencryptedString.ConvertToSecureString();
|
||||
}
|
||||
|
||||
public static SecureString ConvertToSecureString(this string unsecuredPassword)
|
||||
{
|
||||
if (unsecuredPassword == null)
|
||||
throw new ArgumentNullException(nameof(unsecuredPassword));
|
||||
|
||||
var secureString = new SecureString();
|
||||
foreach (var character in unsecuredPassword.ToCharArray())
|
||||
secureString.AppendChar(character);
|
||||
// ReSharper disable once RedundantAssignment
|
||||
unsecuredPassword = null;
|
||||
return secureString;
|
||||
}
|
||||
|
||||
public static bool IsBase64String(this string s)
|
||||
{
|
||||
s = s.Trim();
|
||||
return (s.Length % 4 == 0) && Regex.IsMatch(s, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,10 @@
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
|
||||
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Geckofx-Core, Version=45.0.19.0, Culture=neutral, PublicKeyToken=3209ac31600d1857, processorArchitecture=x86">
|
||||
<HintPath>..\packages\Geckofx45.45.0.19.0\lib\net40\Geckofx-Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@@ -163,6 +167,10 @@
|
||||
<Compile Include="Connection\Protocol\RDP\RDPVersions.cs" />
|
||||
<Compile Include="Connection\Protocol\VNC\VNCEnum.cs" />
|
||||
<Compile Include="Messages\MessageClassEnum.cs" />
|
||||
<Compile Include="Security\AesCryptographyProvider.cs" />
|
||||
<Compile Include="Security\BouncyCastleCryptographyEngine.cs" />
|
||||
<Compile Include="Security\ICryptographyProvider.cs" />
|
||||
<Compile Include="Security\SecureStringExtensions.cs" />
|
||||
<Compile Include="Tools\ArgumentParser.cs" />
|
||||
<Compile Include="Tools\CmdArgumentsInterpreter.cs" />
|
||||
<Compile Include="Tools\ExternalToolsTypeConverter.cs" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="BouncyCastle" version="1.8.1" targetFramework="net40" />
|
||||
<package id="Geckofx45" version="45.0.19.0" targetFramework="net40" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user