Moved enhancements made in the CredentialManager branch to a dedicated branch

This commit is contained in:
David Sparer
2016-07-11 11:41:42 -06:00
parent 2b7668aa68
commit b3bac32441
9 changed files with 263 additions and 0 deletions

View 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));
}
}
}

View File

@@ -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>

View File

@@ -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>

View 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);
}
}
}

View 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;
}
}
}

View 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);
}
}

View 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);
}
}
}

View File

@@ -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" />

View File

@@ -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>