diff --git a/mRemoteNGTests/Security/AesCryptographyProviderTests.cs b/mRemoteNGTests/Security/AesCryptographyProviderTests.cs new file mode 100644 index 00000000..e44a111e --- /dev/null +++ b/mRemoteNGTests/Security/AesCryptographyProviderTests.cs @@ -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)); + } + } +} \ No newline at end of file diff --git a/mRemoteNGTests/mRemoteNGTests.csproj b/mRemoteNGTests/mRemoteNGTests.csproj index 4e7d7295..3a9fa4df 100644 --- a/mRemoteNGTests/mRemoteNGTests.csproj +++ b/mRemoteNGTests/mRemoteNGTests.csproj @@ -54,6 +54,10 @@ MinimumRecommendedRules.ruleset + + ..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll + True + False ..\mRemoteV1\References\log4net.dll @@ -100,6 +104,7 @@ + Form diff --git a/mRemoteNGTests/packages.config b/mRemoteNGTests/packages.config index 7692ea5d..1cd6ffc6 100644 --- a/mRemoteNGTests/packages.config +++ b/mRemoteNGTests/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/mRemoteV1/Security/AesCryptographyProvider.cs b/mRemoteV1/Security/AesCryptographyProvider.cs new file mode 100644 index 00000000..75c25ee6 --- /dev/null +++ b/mRemoteV1/Security/AesCryptographyProvider.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/mRemoteV1/Security/BouncyCastleCryptographyEngine.cs b/mRemoteV1/Security/BouncyCastleCryptographyEngine.cs new file mode 100644 index 00000000..f4b6cef0 --- /dev/null +++ b/mRemoteV1/Security/BouncyCastleCryptographyEngine.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/mRemoteV1/Security/ICryptographyProvider.cs b/mRemoteV1/Security/ICryptographyProvider.cs new file mode 100644 index 00000000..062cbdaf --- /dev/null +++ b/mRemoteV1/Security/ICryptographyProvider.cs @@ -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); + } +} \ No newline at end of file diff --git a/mRemoteV1/Security/SecureStringExtensions.cs b/mRemoteV1/Security/SecureStringExtensions.cs new file mode 100644 index 00000000..05188af6 --- /dev/null +++ b/mRemoteV1/Security/SecureStringExtensions.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Text.RegularExpressions; + +namespace mRemoteNG.Security +{ + public static class SecureStringExtensions + { + /// + /// 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/ + /// + /// + /// + 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); + } + } +} \ No newline at end of file diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index e15acffc..b029aa8c 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -46,6 +46,10 @@ + + ..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll + True + ..\packages\Geckofx45.45.0.19.0\lib\net40\Geckofx-Core.dll True @@ -163,6 +167,10 @@ + + + + diff --git a/mRemoteV1/packages.config b/mRemoteV1/packages.config index cfe52582..739bcde6 100644 --- a/mRemoteV1/packages.config +++ b/mRemoteV1/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file