diff --git a/src/Umbraco.Core/Security/MachineKeyGenerator.cs b/src/Umbraco.Core/Security/MachineKeyGenerator.cs
new file mode 100644
index 0000000000..9dd06f44bd
--- /dev/null
+++ b/src/Umbraco.Core/Security/MachineKeyGenerator.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Umbraco.Core.Security
+{
+ ///
+ /// Used to generate a machine key
+ ///
+ internal class MachineKeyGenerator
+ {
+ ///
+ /// Generates the string to be stored in the web.config
+ ///
+ ///
+ ///
+ /// Machine key details are here: https://msdn.microsoft.com/en-us/library/vstudio/w8h3skw9%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396
+ ///
+ public string GenerateConfigurationBlock()
+ {
+ var c = @"";
+
+ var Xxx = 3;
+
+ return string.Format(c, GenerateAESDecryptionKey(), GenerateHMACSHA256ValidationKey());
+ }
+
+ public string GenerateHMACSHA256ValidationKey()
+ {
+ //See: https://msdn.microsoft.com/en-us/library/vstudio/w8h3skw9%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396
+ //See: https://msdn.microsoft.com/en-us/library/ff649308.aspx?f=255&MSPPError=-2147217396
+ /*
+ key value Specifies a manually assigned key.
+ The validationKey value must be manually set to a string of hexadecimal
+ characters to ensure consistent configuration across all servers in a Web farm.
+ The length of the key depends on the hash algorithm that is used:
+
+ AES requires a 256-bit key (64 hexadecimal characters).
+ MD5 requires a 128-bit key (32 hexadecimal characters).
+ SHA1 requires a 160-bit key (40 hexadecimal characters).
+ 3DES requires a 192-bit key (48 hexadecimal characters).
+ HMACSHA256 requires a 256-bit key (64 hexadecimal characters) == DEFAULT
+ HMACSHA384 requires a 384-bit key (96 hexadecimal characters).
+ HMACSHA512 requires a 512-bit key (128 hexadecimal characters).
+ */
+
+ //64 in length = 256 bits
+ return GenerateKey(64);
+ }
+
+ public string GenerateAESDecryptionKey()
+ {
+ //See: //See: https://msdn.microsoft.com/en-us/library/vstudio/w8h3skw9%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396
+ /*
+ key value Specifies a manually assigned key.
+ The decryptionKey value must be manually set to a string of
+ hexadecimal characters to ensure consistent configuration across all servers in a Web farm.
+ The key should be 64 bits (16 hexadecimal characters) long for DES encryption, or 192 bits
+ (48 hexadecimal characters) long for 3DES. For AES, the key can be 128 bits (32 characters),
+ 192 bits (48 characters), or 256 bits (64 characters) long.
+ */
+
+ //64 in length = 256 bits
+ return GenerateKey(64);
+ }
+
+ private string GenerateKey(int len = 64)
+ {
+ var buff = new byte[len / 2];
+ var rng = new RNGCryptoServiceProvider();
+ rng.GetBytes(buff);
+ var sb = new StringBuilder(len);
+
+ for (int i = 0; i < buff.Length; i++)
+ sb.Append(string.Format("{0:X2}", buff[i]));
+
+ return sb.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs
index 982ced30c7..b911245be4 100644
--- a/src/Umbraco.Core/Security/MembershipProviderBase.cs
+++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs
@@ -674,7 +674,7 @@ namespace Umbraco.Core.Security
if (PasswordFormat == MembershipPasswordFormat.Clear)
return pass;
var bytes = Encoding.Unicode.GetBytes(pass);
- var numArray1 = Convert.FromBase64String(salt);
+ var saltBytes = Convert.FromBase64String(salt);
byte[] inArray;
if (PasswordFormat == MembershipPasswordFormat.Hashed)
@@ -684,22 +684,27 @@ namespace Umbraco.Core.Security
if (algorithm != null)
{
var keyedHashAlgorithm = algorithm;
- if (keyedHashAlgorithm.Key.Length == numArray1.Length)
- keyedHashAlgorithm.Key = numArray1;
- else if (keyedHashAlgorithm.Key.Length < numArray1.Length)
- {
+ if (keyedHashAlgorithm.Key.Length == saltBytes.Length)
+ {
+ //if the salt bytes is the required key length for the algorithm, use it as-is
+ keyedHashAlgorithm.Key = saltBytes;
+ }
+ else if (keyedHashAlgorithm.Key.Length < saltBytes.Length)
+ {
+ //if the salt bytes is too long for the required key length for the algorithm, reduce it
var numArray2 = new byte[keyedHashAlgorithm.Key.Length];
- Buffer.BlockCopy(numArray1, 0, numArray2, 0, numArray2.Length);
+ Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length);
keyedHashAlgorithm.Key = numArray2;
}
else
{
+ //if the salt bytes is too long for the required key length for the algorithm, extend it
var numArray2 = new byte[keyedHashAlgorithm.Key.Length];
var dstOffset = 0;
while (dstOffset < numArray2.Length)
{
- var count = Math.Min(numArray1.Length, numArray2.Length - dstOffset);
- Buffer.BlockCopy(numArray1, 0, numArray2, dstOffset, count);
+ var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset);
+ Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count);
dstOffset += count;
}
keyedHashAlgorithm.Key = numArray2;
@@ -708,9 +713,9 @@ namespace Umbraco.Core.Security
}
else
{
- var buffer = new byte[numArray1.Length + bytes.Length];
- Buffer.BlockCopy(numArray1, 0, buffer, 0, numArray1.Length);
- Buffer.BlockCopy(bytes, 0, buffer, numArray1.Length, bytes.Length);
+ var buffer = new byte[saltBytes.Length + bytes.Length];
+ Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length);
+ Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length);
inArray = hashAlgorithm.ComputeHash(buffer);
}
}
@@ -718,9 +723,9 @@ namespace Umbraco.Core.Security
{
//this code is copied from the sql membership provider - pretty sure this could be nicely re-written to completely
// ignore the salt stuff since we are not salting the password when encrypting.
- var password = new byte[numArray1.Length + bytes.Length];
- Buffer.BlockCopy(numArray1, 0, password, 0, numArray1.Length);
- Buffer.BlockCopy(bytes, 0, password, numArray1.Length, bytes.Length);
+ var password = new byte[saltBytes.Length + bytes.Length];
+ Buffer.BlockCopy(saltBytes, 0, password, 0, saltBytes.Length);
+ Buffer.BlockCopy(bytes, 0, password, saltBytes.Length, bytes.Length);
inArray = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40);
}
return Convert.ToBase64String(inArray);
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 596c72b56a..43e94fce8e 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -666,6 +666,7 @@
+
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js
new file mode 100644
index 0000000000..bdcef63d95
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js
@@ -0,0 +1,14 @@
+angular.module("umbraco.install").controller("Umbraco.Installer.MachineKeyController", function ($scope, installerService) {
+
+
+ $scope.continue = function () {
+ installerService.status.current.model = true;
+ installerService.forward();
+ };
+
+ $scope.ignoreKey = function () {
+ installerService.status.current.model = false;
+ installerService.forward();
+ };
+
+});
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.html b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.html
new file mode 100644
index 0000000000..732c7c1d56
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.html
@@ -0,0 +1,23 @@
+
+
Configure an ASP.Net Machine Key
+
+ By default the installer will generate a custom ASP.Net Machine Key for your site and install it into your web.config file.
+ A Machine Key is used for hashing and encryption and it is recommended that you install a custom one into your site.
+ This ensures that your site is fully portable between environments that might have different Machine Key settings and that
+ your site by default will work with load balancing when installed between various server environments.
+
+
+
+
\ No newline at end of file
diff --git a/src/Umbraco.Web/Install/InstallHelper.cs b/src/Umbraco.Web/Install/InstallHelper.cs
index 6c088365a1..a3069342fc 100644
--- a/src/Umbraco.Web/Install/InstallHelper.cs
+++ b/src/Umbraco.Web/Install/InstallHelper.cs
@@ -48,6 +48,7 @@ namespace Umbraco.Web.Install
new MajorVersion7UpgradeReport(_umbContext.Application),
new Version73FileCleanup(_umbContext.HttpContext, _umbContext.Application.ProfilingLogger.Logger),
new DatabaseConfigureStep(_umbContext.Application),
+ new ConfigureMachineKey(_umbContext.Application),
new DatabaseInstallStep(_umbContext.Application),
new DatabaseUpgradeStep(_umbContext.Application),
new StarterKitDownloadStep(_umbContext.Application, _umbContext.Security, _umbContext.HttpContext),
diff --git a/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs b/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs
new file mode 100644
index 0000000000..e4038df4ab
--- /dev/null
+++ b/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Configuration;
+using System.Linq;
+using System.Web.Configuration;
+using System.Xml.Linq;
+using Umbraco.Core;
+using Umbraco.Core.IO;
+using Umbraco.Core.Security;
+using Umbraco.Web.Install.Models;
+
+namespace Umbraco.Web.Install.InstallSteps
+{
+ [InstallSetupStep(InstallationType.NewInstall,
+ "ConfigureMachineKey", "machinekey", 2,
+ "Updating some security settings...",
+ PerformsAppRestart = true)]
+ internal class ConfigureMachineKey : InstallSetupStep
+ {
+ private readonly ApplicationContext _appContext;
+
+ public ConfigureMachineKey(ApplicationContext appContext)
+ {
+ if (appContext == null) throw new ArgumentNullException("appContext");
+ _appContext = appContext;
+ }
+
+ public override string View
+ {
+ get { return HasMachineKey() == false ? base.View : ""; }
+ }
+
+ ///
+ /// Don't display the view or execute if a machine key already exists
+ ///
+ ///
+ private bool HasMachineKey()
+ {
+ var section = (MachineKeySection)WebConfigurationManager.GetSection("system.web/machineKey");
+ return section.ElementInformation.Source != null;
+ }
+
+ ///
+ /// The step execution method
+ ///
+ ///
+ ///
+ public override InstallSetupResult Execute(bool? model)
+ {
+ if (model.HasValue && model.Value == false) return null;
+
+ //install the machine key
+ var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root));
+ var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
+
+ var systemWeb = xml.Root.DescendantsAndSelf("system.web").Single();
+
+ // Update appSetting if it exists, or else create a new appSetting for the given key and value
+ var machineKey = systemWeb.Descendants("machineKey").FirstOrDefault();
+ if (machineKey != null) return null;
+
+ var generator = new MachineKeyGenerator();
+ var generatedSection = generator.GenerateConfigurationBlock();
+ systemWeb.Add(XElement.Parse(generatedSection));
+
+ xml.Save(fileName, SaveOptions.DisableFormatting);
+
+ return null;
+ }
+
+ public override bool RequiresExecution(bool? model)
+ {
+ return HasMachineKey() == false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs
index ce28a26176..9f74596ca2 100644
--- a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs
+++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Web.Install.Models;
@@ -94,8 +95,9 @@ namespace Umbraco.Web.Install.InstallSteps
result.DetermineInstalledVersion();
return false;
}
- catch (Exception)
+ catch (Exception ex)
{
+ _applicationContext.ProfilingLogger.Logger.Error("An error occurred, reconfiguring...", ex);
//something went wrong, could not connect so probably need to reconfigure
return true;
}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 59d066093e..950e494e35 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -359,6 +359,7 @@
+