diff --git a/XenAdmin/Dialogs/RestoreSession/ChangeMasterPasswordDialog.cs b/XenAdmin/Dialogs/RestoreSession/ChangeMasterPasswordDialog.cs
index fb989e3dd..544c64843 100644
--- a/XenAdmin/Dialogs/RestoreSession/ChangeMasterPasswordDialog.cs
+++ b/XenAdmin/Dialogs/RestoreSession/ChangeMasterPasswordDialog.cs
@@ -31,6 +31,7 @@
using System;
using System.Windows.Forms;
+using XenAdmin.Core;
using XenCenterLib;
@@ -38,12 +39,12 @@ namespace XenAdmin.Dialogs.RestoreSession
{
public partial class ChangeMasterPasswordDialog : XenDialogBase
{
- private readonly byte[] OldProposedPassword;
+ private readonly byte[] _currentPasswordHash;
- public ChangeMasterPasswordDialog(byte[] proposedPassword)
+ public ChangeMasterPasswordDialog(byte[] currentPasswordHash)
{
InitializeComponent();
- OldProposedPassword = proposedPassword;
+ _currentPasswordHash = currentPasswordHash;
currentPasswordError.Visible = false;
newPasswordError.Visible = false;
}
@@ -67,9 +68,10 @@ namespace XenAdmin.Dialogs.RestoreSession
private void okButton_Click(object sender, EventArgs e)
{
- var oldPasswordCorrect = Settings.PassCorrect(currentTextBox.Text, OldProposedPassword);
+ var currentPasswordCorrect = !string.IsNullOrEmpty(currentTextBox.Text) &&
+ Helpers.ArrayElementsEqual(EncryptionUtils.ComputeHash(currentTextBox.Text), _currentPasswordHash);
- if (oldPasswordCorrect && !string.IsNullOrEmpty(masterTextBox.Text) &&
+ if (currentPasswordCorrect && !string.IsNullOrEmpty(masterTextBox.Text) &&
masterTextBox.Text == reEnterMasterTextBox.Text)
{
NewPassword = EncryptionUtils.ComputeHash(masterTextBox.Text);
@@ -77,7 +79,7 @@ namespace XenAdmin.Dialogs.RestoreSession
return;
}
- if (!oldPasswordCorrect)
+ if (!currentPasswordCorrect)
currentPasswordError.ShowError(Messages.PASSWORD_INCORRECT);
else if (masterTextBox.Text != reEnterMasterTextBox.Text)
newPasswordError.ShowError(Messages.PASSWORDS_DONT_MATCH);
diff --git a/XenAdmin/Dialogs/RestoreSession/EnterMasterPasswordDialog.cs b/XenAdmin/Dialogs/RestoreSession/EnterMasterPasswordDialog.cs
index 2fa55b431..38003788d 100644
--- a/XenAdmin/Dialogs/RestoreSession/EnterMasterPasswordDialog.cs
+++ b/XenAdmin/Dialogs/RestoreSession/EnterMasterPasswordDialog.cs
@@ -31,24 +31,27 @@
using System;
using System.Windows.Forms;
+using XenAdmin.Core;
+using XenCenterLib;
namespace XenAdmin.Dialogs.RestoreSession
{
public partial class EnterMasterPasswordDialog : XenDialogBase
{
- private byte[] TemporaryMasterPassword;
+ private readonly byte[] _temporaryMasterPassword;
public EnterMasterPasswordDialog(byte[] temporaryMasterPassword)
{
InitializeComponent();
- TemporaryMasterPassword = temporaryMasterPassword;
+ _temporaryMasterPassword = temporaryMasterPassword;
passwordError.Visible = false;
}
private void okButton_Click(object sender, EventArgs e)
{
- if (Settings.PassCorrect(masterTextBox.Text,TemporaryMasterPassword))
+ if (!string.IsNullOrEmpty(masterTextBox.Text) &&
+ Helpers.ArrayElementsEqual(EncryptionUtils.ComputeHash(masterTextBox.Text), _temporaryMasterPassword))
{
DialogResult = DialogResult.OK;
}
@@ -63,6 +66,7 @@ namespace XenAdmin.Dialogs.RestoreSession
private void masterTextBox_TextChanged(object sender, EventArgs e)
{
passwordError.Visible = false;
+ okButton.Enabled = !string.IsNullOrEmpty(masterTextBox.Text);
}
}
}
\ No newline at end of file
diff --git a/XenAdmin/Dialogs/RestoreSession/LoadSessionDialog.cs b/XenAdmin/Dialogs/RestoreSession/LoadSessionDialog.cs
index 59601bdc7..8fd93b992 100644
--- a/XenAdmin/Dialogs/RestoreSession/LoadSessionDialog.cs
+++ b/XenAdmin/Dialogs/RestoreSession/LoadSessionDialog.cs
@@ -32,15 +32,12 @@
using System;
using System.Drawing;
using System.Windows.Forms;
-using XenCenterLib;
namespace XenAdmin.Dialogs.RestoreSession
{
public partial class LoadSessionDialog : XenDialogBase
{
- private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
-
public LoadSessionDialog(bool isRetry = false)
{
InitializeComponent();
@@ -48,25 +45,8 @@ namespace XenAdmin.Dialogs.RestoreSession
}
internal override string HelpName => "LoadSessionDialog";
- public byte[] PasswordHash
- {
- get
- {
- if (!string.IsNullOrEmpty(passBox.Text))
- {
- try
- {
- return EncryptionUtils.ComputeHash(passBox.Text);
- }
- catch (Exception exp)
- {
- log.Error("Could not hash entered password.", exp);
- }
- }
- return null;
- }
- }
+ public string Password => passBox.Text;
private void passBox_TextChanged(object sender, EventArgs e)
{
diff --git a/XenAdmin/MainWindow.cs b/XenAdmin/MainWindow.cs
index ca4355d47..fed569c4f 100755
--- a/XenAdmin/MainWindow.cs
+++ b/XenAdmin/MainWindow.cs
@@ -2229,20 +2229,7 @@ namespace XenAdmin
Properties.Settings.Default.WindowSize = this.Size;
Properties.Settings.Default.WindowLocation = this.Location;
- try
- {
- Settings.SaveServerList();
- Properties.Settings.Default.Save();
- }
- catch (ConfigurationErrorsException ex)
- {
- using (var dlg = new ErrorDialog(string.Format(Messages.MESSAGEBOX_SAVE_CORRUPTED, Settings.GetUserConfigPath()))
- {WindowTitle = Messages.MESSAGEBOX_SAVE_CORRUPTED_TITLE})
- {
- dlg.ShowDialog(this);
- }
- log.Error("Could not save settings.", ex);
- }
+ Settings.SaveServerList(); //this calls Settings.TrySaveSettings()
base.OnClosing(e);
}
diff --git a/XenAdmin/Program.cs b/XenAdmin/Program.cs
index 2634f9b2b..cd4dddd3b 100644
--- a/XenAdmin/Program.cs
+++ b/XenAdmin/Program.cs
@@ -484,7 +484,7 @@ namespace XenAdmin
try
{
// Lets save the connections first, so we can save their connected state
- Settings.SaveIfRequired();
+ Settings.SaveServerList(); //this calls Settings.TrySaveSettings()
}
catch (Exception)
{
diff --git a/XenAdmin/Settings.cs b/XenAdmin/Settings.cs
index f723e5d8c..25188fb3d 100644
--- a/XenAdmin/Settings.cs
+++ b/XenAdmin/Settings.cs
@@ -98,6 +98,7 @@ namespace XenAdmin
Properties.Settings.Default.SaveSession = false;
return;
}
+
if (!Registry.AllowCredentialSave)
{
Program.SkipSessionSave = true;
@@ -106,81 +107,63 @@ namespace XenAdmin
RestoreSessionWithPassword(null, false);
return;
}
- // Only try if the user has specified he actually wants to save sessions...
- if (Properties.Settings.Default.SaveSession == true || Properties.Settings.Default.RequirePass)
+
+ Program.MasterPassword = null;
+
+ if (Properties.Settings.Default.SaveSession || Properties.Settings.Default.RequirePass)
{
- // Only try if we actually have a saved session list...
- if ((Properties.Settings.Default.ServerList != null && Properties.Settings.Default.ServerList.Length > 0) || (Properties.Settings.Default.ServerAddressList != null && Properties.Settings.Default.ServerAddressList.Length > 0))
+ if (Properties.Settings.Default.ServerList != null && Properties.Settings.Default.ServerList.Length > 0 ||
+ Properties.Settings.Default.ServerAddressList != null && Properties.Settings.Default.ServerAddressList.Length > 0)
{
if (!Properties.Settings.Default.RequirePass)
{
- Program.MasterPassword = null;
Program.SkipSessionSave = true;
RestoreSessionWithPassword(null, true);
return;
}
- byte[] passHash = PromptForMasterPassword(false);
- // passHash will be null if the user cancelled...
- if (passHash != null)
+ // close the splash screen before opening the password dialog (the main window closes the
+ // splash screen after this method is called, however, this cannot happen because the dialog
+ // is launched modally blocking the UI thread and is additionally behind the splash screen)
+ Program.CloseSplash();
+
+ string password = null;
+ do
{
- if (!RestoreSessionWithPassword(passHash, true))
- {
- // User got the password wrong. Repeat until he gets it
- // right or cancels...
- while (passHash != null)
- {
- passHash = PromptForMasterPassword(true);
- if (passHash != null)
- {
- if (RestoreSessionWithPassword(passHash, true))
- break;
- }
- }
- }
- }
- // if the user has cancelled we start a new session
- if (passHash == null)
- {
- // an error state which can only occur on cancelled clicked
- Properties.Settings.Default.SaveSession = false;
- Properties.Settings.Default.RequirePass = true;
- RestoreSessionWithPassword(null, false);
- }
- else
- {
- // otherwise make sure we have the correct settings
- Properties.Settings.Default.SaveSession = true;
- Properties.Settings.Default.RequirePass = true;
- }
+ using (var dialog = new LoadSessionDialog(password != null))
+ password = dialog.ShowDialog(Program.MainWindow) == DialogResult.OK
+ ? dialog.Password
+ : null;
+ } while (password != null && !RestoreSessionWithPassword(password, true));
+
+ Properties.Settings.Default.SaveSession = password != null;
+ Properties.Settings.Default.RequirePass = true;
Program.SkipSessionSave = true;
- Program.MasterPassword = passHash;
+
+ if (password == null)
+ RestoreSessionWithPassword(null, false); //if the user has cancelled start a new session
+ else
+ Program.MasterPassword = EncryptionUtils.ComputeHash(password);
}
else
{
- // this is where the user comes in if it is the first time connecting
Properties.Settings.Default.RequirePass = false;
Properties.Settings.Default.SaveSession = false;
- Program.MasterPassword = null;
}
}
else
{
- Program.MasterPassword = null;
Program.SkipSessionSave = true;
RestoreSessionWithPassword(null, false);
}
}
///
- /// Tries to restore the session list using the given password hash as
- /// the key. Returns true if successful, false otherwise (usu. due to
+ /// Tries to restore the session list using the given password as the key.
+ /// Returns true if successful, false otherwise (usually due to
/// a decryption failure, in turn due to a wrong password).
///
- ///
- ///
- ///
- private static bool RestoreSessionWithPassword(byte[] passHash, bool useOriginalList)
+ private static bool RestoreSessionWithPassword(string password, bool useOriginalList)
{
string[] encServerList;
@@ -220,7 +203,7 @@ namespace XenAdmin
{
foreach (string encEntry in encServerList)
{
- decryptedList[idx] = EncryptionUtils.DecryptString(encEntry, passHash);
+ decryptedList[idx] = EncryptionUtils.DecryptString(encEntry, password);
idx++;
}
}
@@ -337,24 +320,6 @@ namespace XenAdmin
}
}
- ///
- /// Prompts for the password to use to decrypt the server list.
- /// Returns the secure hash of the password entered.
- ///
- ///
- ///
- private static byte[] PromptForMasterPassword(bool isRetry)
- {
- // close the splash screen before opening the password dialog (the dialog comes up behind the splash screen)
- Program.CloseSplash();
-
- using (var dialog = new LoadSessionDialog(isRetry))
- if (dialog.ShowDialog(Program.MainWindow) == DialogResult.OK)
- return dialog.PasswordHash;
-
- return null;
- }
-
///
/// If an exception is thrown while saving the list, this method will notify the user
/// and then call Application.Exit().
@@ -434,7 +399,7 @@ namespace XenAdmin
}
// Save the address, port and friendly name in case the user clicks cancel on the resume password dialog
- string entryAddress = protectCredentials(connection.Hostname, port, connection.FriendlyName);
+ string entryAddress = ProtectCredentials(connection.Hostname, port, connection.FriendlyName);
encServerAddressList.Add(entryAddress);
}
@@ -467,7 +432,7 @@ namespace XenAdmin
}
}
- private static string protectCredentials(String serverName, int port, string friendlyName)
+ private static string ProtectCredentials(String serverName, int port, string friendlyName)
{
string entryStr = string.Join(SEPARATOR.ToString(), new string[] { serverName, port.ToString(), friendlyName });
return EncryptionUtils.Protect(entryStr);
@@ -554,30 +519,9 @@ namespace XenAdmin
public static void SetVNCPassword(string vm_uuid, char[] password)
{
VNCPasswords[vm_uuid] = password == null ? null : new string(password);
- EncryptServerList();
SaveServerList();
}
- ///
- /// Compares the hash of 'p' with the given byte array.
- ///
- ///
- ///
- ///
- internal static bool PassCorrect(string p, byte[] temporaryPassword)
- {
- if (p.Length == 0)
- return false;
-
- return Helpers.ArrayElementsEqual(EncryptionUtils.ComputeHash(p), temporaryPassword);
- }
-
- public static void SaveIfRequired()
- {
- SaveServerList();
- TrySaveSettings();
- }
-
public static void AddCertificate(string hashString, string hostname)
{
Dictionary known_servers = KnownServers;
diff --git a/XenAdmin/WinformsXenAdminConfigProvider.cs b/XenAdmin/WinformsXenAdminConfigProvider.cs
index e105f9b4d..fc42f017b 100644
--- a/XenAdmin/WinformsXenAdminConfigProvider.cs
+++ b/XenAdmin/WinformsXenAdminConfigProvider.cs
@@ -175,7 +175,7 @@ namespace XenAdmin
public void SaveSettingsIfRequired()
{
- Settings.SaveIfRequired();
+ Settings.SaveServerList();
}
private AsyncAction.SudoElevationResult GetElevatedSession(List allowedRoles,
diff --git a/XenAdminTests/UnitTests/EncryptionUtilTests.cs b/XenAdminTests/UnitTests/EncryptionUtilTests.cs
new file mode 100644
index 000000000..580ae16e8
--- /dev/null
+++ b/XenAdminTests/UnitTests/EncryptionUtilTests.cs
@@ -0,0 +1,166 @@
+/* Copyright (c) Citrix Systems, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+using System;
+using System.IO;
+using System.Text;
+using System.Security.Cryptography;
+using NUnit.Framework;
+using XenCenterLib;
+
+
+namespace XenAdminTests.UnitTests
+{
+ [TestFixture, Category(TestCategories.Unit)]
+ internal class EncryptionUtilTests
+ {
+ private string[] _transformedStrings =
+ {
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ "abcçdefgğhıijklmnoöprsştuüvyz",
+ "短𠀁𪛕",
+ "ÀàÂâÆæ,Çç,ÉéÈèÊêËë,ÎîÏï,ÔôŒœ,ÙùÛûÜü,Ÿÿ"
+ };
+
+ private string[] _keys =
+ {
+ "kd385Am£dm*C",
+ "j33LZ$89cngA39%",
+ "Pzm#d&cJ309",
+ "短𠀁𪛕",
+ "çdefgğhıijklmno",
+ "ÀâæÇÉèêëîïÔŒÙÛüŸ"
+ };
+
+ [Test]
+ public void TestProtectionCurrentUser()
+ {
+ foreach (var str in _transformedStrings)
+ {
+ var protectedStr = EncryptionUtils.Protect(str);
+ var unprotectedStr = EncryptionUtils.Unprotect(protectedStr);
+ Assert.AreEqual(str, unprotectedStr,
+ "String corruption after protection, then un-protection (current user)");
+ }
+ }
+
+ [Test]
+ public void TestProtectionLocalMachine()
+ {
+ foreach (var str in _transformedStrings)
+ {
+ var protectedStr = EncryptionUtils.ProtectForLocalMachine(str);
+ var unprotectedStr = EncryptionUtils.UnprotectForLocalMachine(protectedStr);
+ Assert.AreEqual(str, unprotectedStr,
+ "String corruption after protection, then un-protection (local machine)");
+ }
+ }
+
+ [Test]
+ public void TestProtectionCurrentUserLegacy()
+ {
+ foreach (var str in _transformedStrings)
+ {
+ var legacyProtectedStr = LegacyProtection(str, DataProtectionScope.CurrentUser);
+ var unprotectedStr = EncryptionUtils.UnprotectForLocalMachine(legacyProtectedStr);
+ Assert.AreEqual(str, unprotectedStr,
+ "String corruption after legacy protection, then un-protection (current user)");
+ }
+ }
+
+ [Test]
+ public void TestProtectionLocalMachineLegacy()
+ {
+ foreach (var str in _transformedStrings)
+ {
+ var legacyProtectedStr = LegacyProtection(str, DataProtectionScope.LocalMachine);
+ var unprotectedStr = EncryptionUtils.UnprotectForLocalMachine(legacyProtectedStr);
+ Assert.AreEqual(str, unprotectedStr,
+ "String corruption after legacy protection, then un-protection (local machine)");
+ }
+ }
+
+ [Test]
+ public void TestEncryption()
+ {
+ foreach (var str in _transformedStrings)
+ foreach (var key in _keys)
+ {
+ var encryptedStr = EncryptionUtils.EncryptString(str, EncryptionUtils.ComputeHash(key));
+ var decryptedStr = EncryptionUtils.DecryptString(encryptedStr, key);
+ Assert.AreEqual(str, decryptedStr,
+ "String corruption after encryption, then decryption");
+ }
+ }
+
+ [Test]
+ public void TestEncryptionLegacy()
+ {
+ foreach (var str in _transformedStrings)
+ foreach (var key in _keys)
+ {
+ var legacyEncryptedStr = LegacyEncryption(str, EncryptionUtils.ComputeHash(key));
+ var decryptedStr = EncryptionUtils.DecryptString(legacyEncryptedStr, key);
+ Assert.AreEqual(str, decryptedStr,
+ "String corruption after legacy encryption, then decryption");
+ }
+ }
+
+
+ private static string LegacyProtection(string str, DataProtectionScope scope)
+ {
+ byte[] saltBytes = new UnicodeEncoding().GetBytes("XenRocks");
+ byte[] dataBytes = Encoding.Unicode.GetBytes(str);
+ byte[] protectedBytes = ProtectedData.Protect(dataBytes, saltBytes, scope);
+ return $"{Convert.ToBase64String(protectedBytes)},{Convert.ToBase64String(saltBytes)}";
+ }
+
+ private static string LegacyEncryption(string clearString, byte[] keyBytes)
+ {
+ byte[] saltBytes = new UnicodeEncoding().GetBytes("XenRocks");
+
+ using (var alg = new AesManaged())
+ {
+ alg.Key = keyBytes;
+ alg.IV = saltBytes;
+
+ using (var ms = new MemoryStream())
+ using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write))
+ {
+ byte[] clearBytes = Encoding.Unicode.GetBytes(clearString);
+ cs.Write(clearBytes, 0, clearBytes.Length);
+ cs.FlushFinalBlock(); //important for getting the padding right
+ return $"{Convert.ToBase64String(ms.ToArray())},{Convert.ToBase64String(saltBytes)}";
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/XenAdminTests/XenAdminTests.csproj b/XenAdminTests/XenAdminTests.csproj
index 34dde29fd..23f61c8a2 100644
--- a/XenAdminTests/XenAdminTests.csproj
+++ b/XenAdminTests/XenAdminTests.csproj
@@ -55,6 +55,7 @@
3.5
+
@@ -116,6 +117,7 @@
+
diff --git a/XenCenterLib/EncryptionUtils.cs b/XenCenterLib/EncryptionUtils.cs
index dcd5571a7..f4a715149 100644
--- a/XenCenterLib/EncryptionUtils.cs
+++ b/XenCenterLib/EncryptionUtils.cs
@@ -36,87 +36,112 @@ using System.Text;
namespace XenCenterLib
{
- ///
- /// Used to centralise the encryption routines used for master password
- /// and session lists
- ///
- public class EncryptionUtils
+ public static class EncryptionUtils
{
- private static byte[] salt;
+ private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
+
+ private const int SALT_LENGTH = 16;
+
+ public enum HashMethod
+ {
+ Md5,
+ Sha256
+ }
+
+ private static string StringOf(this HashMethod method)
+ {
+ switch (method)
+ {
+ case HashMethod.Md5:
+ return "MD5";
+ case HashMethod.Sha256:
+ return "SHA256";
+ default:
+ throw new ArgumentOutOfRangeException(nameof(method), method, null);
+ }
+ }
///
- /// Returns a secure hash of the given input string (usually the
- /// master password). We currently use SHA-1.
+ /// Returns a secure hash of the given input string.
///
- /// The string to hash
+ /// The string to hash
+ /// The hash algorithm implementation to use.
/// The secure hash
- public static byte[] ComputeHash(String password)
+ public static byte[] ComputeHash(string input, HashMethod method = HashMethod.Sha256)
{
- //SHA1 hasher = SHA1.Create();
- MD5 hasher = MD5.Create();
- UnicodeEncoding ue = new UnicodeEncoding();
- byte[] bytes = ue.GetBytes(password);
-
- byte[] hash = hasher.ComputeHash(bytes);
+ if (input == null)
+ return null;
- return hash;
+ UnicodeEncoding ue = new UnicodeEncoding();
+ byte[] bytes = ue.GetBytes(input);
+
+ using (var hasher = HashAlgorithm.Create(method.StringOf()))
+ return hasher?.ComputeHash(bytes);
}
public static string Protect(string data)
{
- byte[] dataBytes = Encoding.Unicode.GetBytes(data);
- byte[] protectedBytes = ProtectedData.Protect(dataBytes, GetSalt(), DataProtectionScope.CurrentUser);
- return Convert.ToBase64String(protectedBytes);
+ return ProtectForScope(data, DataProtectionScope.CurrentUser);
}
- public static string Unprotect(string protectedstring)
+ public static string Unprotect(string protectedString)
{
- byte[] protectedBytes = Convert.FromBase64String(protectedstring);
- byte[] dataBytes = ProtectedData.Unprotect(protectedBytes, GetSalt(), DataProtectionScope.CurrentUser);
- return Encoding.Unicode.GetString(dataBytes);
+ return UnprotectForScope(protectedString, DataProtectionScope.CurrentUser);
}
public static string ProtectForLocalMachine(string data)
{
- byte[] dataBytes = Encoding.Unicode.GetBytes(data);
- byte[] protectedBytes = ProtectedData.Protect(dataBytes, GetSalt(), DataProtectionScope.LocalMachine);
- return Convert.ToBase64String(protectedBytes);
+ return ProtectForScope(data, DataProtectionScope.LocalMachine);
}
- public static string UnprotectForLocalMachine(string protectedstring)
+ public static string UnprotectForLocalMachine(string protectedString)
{
- byte[] protectedBytes = Convert.FromBase64String(protectedstring);
- byte[] dataBytes = ProtectedData.Unprotect(protectedBytes, GetSalt(), DataProtectionScope.LocalMachine);
+ return UnprotectForScope(protectedString, DataProtectionScope.LocalMachine);
+ }
+
+ private static string ProtectForScope(string data, DataProtectionScope scope)
+ {
+ byte[] saltBytes = GetSalt();
+ byte[] dataBytes = Encoding.Unicode.GetBytes(data);
+ byte[] protectedBytes = ProtectedData.Protect(dataBytes, saltBytes, scope);
+ return $"{Convert.ToBase64String(protectedBytes)},{Convert.ToBase64String(saltBytes)}";
+ }
+
+ private static string UnprotectForScope(string protectedString, DataProtectionScope scope)
+ {
+ var parts = protectedString.Split(new[] {','}, 2);
+ byte[] saltBytes = parts.Length == 2
+ ? Convert.FromBase64String(parts[1])
+ : new UnicodeEncoding().GetBytes("XenRocks"); //backwards compatibility
+ byte[] protectedBytes = Convert.FromBase64String(parts[0]);
+ byte[] dataBytes = ProtectedData.Unprotect(protectedBytes, saltBytes, scope);
return Encoding.Unicode.GetString(dataBytes);
}
///
- /// Encrypt a given string using the given key. The cipherText is Base64
- /// encoded and returned. The algorithjm currently used is "Rijndael"
+ /// Encrypt a given string using the given key.
///
/// The string to encrypt.
/// The key for the encryption algorithm
/// The Base64 encoded cipher text
- public static String EncryptString(String clearString, byte[] keyBytes)
+ public static string EncryptString(string clearString, byte[] keyBytes)
{
- MemoryStream ms = new MemoryStream();
+ byte[] saltBytes = GetSalt();
- //DES alg = DES.Create();
- //RC2 alg = RC2.Create();
- Rijndael alg = Rijndael.Create();
+ using (var alg = new AesManaged())
+ {
+ alg.Key = keyBytes;
+ alg.IV = saltBytes;
- alg.Key = keyBytes;
- alg.IV = GetSalt();
-
- byte[] clearBytes = Encoding.Unicode.GetBytes(clearString);
-
- CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
- cs.Write(clearBytes, 0, clearBytes.Length);
- cs.Close();
-
- byte[] cipherText = ms.ToArray();
-
- return Convert.ToBase64String(cipherText);
+ using (var ms = new MemoryStream())
+ using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write))
+ {
+ byte[] clearBytes = Encoding.Unicode.GetBytes(clearString);
+ cs.Write(clearBytes, 0, clearBytes.Length);
+ cs.FlushFinalBlock(); //important for getting the padding right
+ return $"{Convert.ToBase64String(ms.ToArray())},{Convert.ToBase64String(saltBytes)}";
+ }
+ }
}
///
@@ -124,38 +149,59 @@ namespace XenCenterLib
///
/// The base64 encoded cipher text that was produced
/// by encryptString
- /// The key to use to decrypt
+ /// The key for the decryption algorithm
/// The decrypted text.
- public static String DecryptString(String cipherText64, byte[] keyBytes)
+ public static string DecryptString(string cipherText64, string key)
{
- MemoryStream ms = new MemoryStream();
+ var parts = cipherText64.Split(new[] {','}, 2);
+ byte[] saltBytes = parts.Length == 2
+ ? Convert.FromBase64String(parts[1])
+ : new UnicodeEncoding().GetBytes("XenRocks"); //backwards compatibility
+ byte[] cipherBytes = Convert.FromBase64String(parts[0]);
- byte[] cipherBytes = Convert.FromBase64String(cipherText64);
- Rijndael alg = Rijndael.Create();
- alg.Key = keyBytes;
- alg.IV = GetSalt();
+ try
+ {
+ using (var alg = new AesManaged())
+ {
+ alg.IV = saltBytes;
+ alg.Key = ComputeHash(key);
+ return DecryptString(cipherBytes, alg);
+ }
+ }
+ catch (Exception e)
+ {
+ log.Warn("Failed to decrypt. Trying legacy mode.", e);
- CryptoStream cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);
- cs.Write(cipherBytes, 0, cipherBytes.Length);
- cs.Close();
-
- byte[] plainBytes = ms.ToArray();
-
- return Encoding.Unicode.GetString(plainBytes);
+ using (var alg = Rijndael.Create())
+ {
+ alg.IV = saltBytes;
+ alg.Key = ComputeHash(key, HashMethod.Md5);
+ return DecryptString(cipherBytes, alg); //backwards compatibility
+ }
+ }
}
+ private static string DecryptString(byte[] cipherBytes, SymmetricAlgorithm alg)
+ {
+ using (var ms = new MemoryStream())
+ {
+ using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write))
+ {
+ cs.Write(cipherBytes, 0, cipherBytes.Length);
+ cs.FlushFinalBlock(); //important for getting the padding right
+ return Encoding.Unicode.GetString(ms.ToArray());
+ }
+ }
+ }
private static byte[] GetSalt()
{
- // NOTE: This is what we did in Geneva - may want
- // to do something less lame in future...
- if (salt == null)
+ using (var rngCsProvider = new RNGCryptoServiceProvider())
{
- UnicodeEncoding ue = new UnicodeEncoding();
- salt = ue.GetBytes("XenRocks");
+ var saltBytes = new byte[SALT_LENGTH];
+ rngCsProvider.GetBytes(saltBytes);
+ return saltBytes;
}
-
- return salt;
}
}
}