From d8811d6b46782c70edf655eaef18807377ebaa33 Mon Sep 17 00:00:00 2001 From: Jason Prothero Date: Wed, 10 May 2017 13:35:28 -0700 Subject: [PATCH] Refactored code for FIPS compliance support. Added/converted to using a more generic Hash() method instead of directly using MD5. --- .../Security/BackOfficeUserStore.cs | 2 +- src/Umbraco.Core/StringExtensions.cs | 100 +++++++++--------- src/Umbraco.Web/Editors/GravatarController.cs | 9 ++ .../Models/Mapping/UserModelMapper.cs | 4 +- src/Umbraco.Web/UmbracoHelper.cs | 36 ++++--- .../umbraco.presentation/library.cs | 3 +- .../plugins/tinymce3/GzipCompressor.cs | 12 +-- .../AzureLocalStorageDirectory.cs | 2 +- .../RazorCore/RazorMacroEngine.cs | 6 +- .../webcontrol/plugin/GzipCompressor.cs | 12 +-- 10 files changed, 102 insertions(+), 84 deletions(-) diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 889c7004d7..7d5774c870 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -494,7 +494,7 @@ namespace Umbraco.Core.Security //the stamp cannot be null, so if it is currently null then we'll just return a hash of the password return Task.FromResult(user.SecurityStamp.IsNullOrWhiteSpace() - ? user.PasswordHash.ToMd5() + ? user.PasswordHash.GenerateHash() : user.SecurityStamp); } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 225ac99ff7..f540305325 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -701,67 +701,72 @@ namespace Umbraco.Core return val; } + /// + /// Generates a hash of a string based on the FIPS compliance setting. + /// + /// Referrs to itself + /// The hashed string + public static string GenerateHash(this string str) + { + return CryptoConfig.AllowOnlyFipsAlgorithms + ? str.ToSHA1() + : str.ToMd5(); + } + /// /// Converts the string to MD5 /// - /// referrs to itself - /// the md5 hashed string + /// Referrs to itself + /// The MD5 hashed string public static string ToMd5(this string stringToConvert) { - //create an instance of the MD5CryptoServiceProvider - var md5Provider = new MD5CryptoServiceProvider(); - - //convert our string into byte array - var byteArray = Encoding.UTF8.GetBytes(stringToConvert); - - //get the hashed values created by our MD5CryptoServiceProvider - var hashedByteArray = md5Provider.ComputeHash(byteArray); - - //create a StringBuilder object - var stringBuilder = new StringBuilder(); - - //loop to each each byte - foreach (var b in hashedByteArray) - { - //append it to our StringBuilder - stringBuilder.Append(b.ToString("x2").ToLower()); - } - - //return the hashed value - return stringBuilder.ToString(); + return stringToConvert.GenerateHash("MD5"); } /// /// Converts the string to SHA1 /// /// referrs to itself - /// the md5 hashed string + /// The SHA1 hashed string public static string ToSHA1(this string stringToConvert) { - //create an instance of the SHA1CryptoServiceProvider - var md5Provider = new SHA1CryptoServiceProvider(); - - //convert our string into byte array - var byteArray = Encoding.UTF8.GetBytes(stringToConvert); - - //get the hashed values created by our SHA1CryptoServiceProvider - var hashedByteArray = md5Provider.ComputeHash(byteArray); - - //create a StringBuilder object - var stringBuilder = new StringBuilder(); - - //loop to each each byte - foreach (var b in hashedByteArray) - { - //append it to our StringBuilder - stringBuilder.Append(b.ToString("x2").ToLower()); - } - - //return the hashed value - return stringBuilder.ToString(); + return stringToConvert.GenerateHash("SHA1"); } + /// Generate a hash of a string based on the hashType passed in + /// + /// Referrs to itself + /// String with the hash type. See remarks section of the CryptoConfig Class in MSDN docs for a list of possible values. + /// The hashed string + private static string GenerateHash(this string str, string hashType) + { + //create an instance of the correct hashing provider based on the type passed in + var hasher = HashAlgorithm.Create(hashType); + if (hasher == null) throw new InvalidOperationException("No hashing type found by name " + hashType); + using (hasher) + { + //convert our string into byte array + var byteArray = Encoding.UTF8.GetBytes(str); + + //get the hashed values created by our selected provider + var hashedByteArray = hasher.ComputeHash(byteArray); + + //create a StringBuilder object + var stringBuilder = new StringBuilder(); + + //loop to each each byte + foreach (var b in hashedByteArray) + { + //append it to our StringBuilder + stringBuilder.Append(b.ToString("x2").ToLower()); + } + + //return the hashed value + return stringBuilder.ToString(); + } + } + /// /// Decodes a string that was encoded with UrlTokenEncode /// @@ -1465,10 +1470,7 @@ namespace Umbraco.Core /// internal static Guid ToGuid(this string text) { - var md5 = MD5.Create(); - byte[] myStringBytes = Encoding.ASCII.GetBytes(text); - byte[] hash = md5.ComputeHash(myStringBytes); - return new Guid(hash); + return new Guid(text.GenerateHash()); } } } diff --git a/src/Umbraco.Web/Editors/GravatarController.cs b/src/Umbraco.Web/Editors/GravatarController.cs index f1e184dce7..23c4a731c2 100644 --- a/src/Umbraco.Web/Editors/GravatarController.cs +++ b/src/Umbraco.Web/Editors/GravatarController.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Security.Cryptography; using Umbraco.Core; using Umbraco.Web.Mvc; @@ -13,6 +14,14 @@ namespace Umbraco.Web.Editors { public string GetCurrentUserGravatarUrl() { + // If FIPS is required, never check the Gravatar service as it only supports MD5 hashing. + // Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception + // and the website will not run. + if (CryptoConfig.AllowOnlyFipsAlgorithms) + { + return null; + } + var userService = Services.UserService; var user = userService.GetUserById(UmbracoContext.Security.CurrentUser.Id); var gravatarHash = user.Email.ToMd5(); diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 72093b19a8..eedc2cafca 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) .ForMember( detail => detail.EmailHash, - opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash())) .ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore()); config.CreateMap() @@ -35,7 +35,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(detail => detail.AllowedSections, opt => opt.MapFrom(user => user.AllowedSections)) .ForMember( detail => detail.EmailHash, - opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash())) .ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore()); config.CreateMap() diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 5befbda7cf..b2d6539b67 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1238,20 +1238,30 @@ namespace Umbraco.Web return _stringUtilities.ReplaceLineBreaksForHtml(text); } - /// - /// Returns an MD5 hash of the string specified - /// - /// The text to create a hash from - /// Md5 has of the string - public string CreateMd5Hash(string text) - { - return text.ToMd5(); - } + /// + /// Returns an MD5 hash of the string specified + /// + /// The text to create a hash from + /// Md5 hash of the string + public string CreateMd5Hash(string text) + { + return text.ToMd5(); + } - /// - /// Strips all html tags from a given string, all contents of the tags will remain. - /// - public HtmlString StripHtml(IHtmlString html, params string[] tags) + /// + /// Returns a FIPS compliant hash of the string specified + /// + /// The text to create a hash from + /// hash of the string + public string CreateHash(string text) + { + return text.GenerateHash(); + } + + /// + /// Strips all html tags from a given string, all contents of the tags will remain. + /// + public HtmlString StripHtml(IHtmlString html, params string[] tags) { return StripHtml(html.ToHtmlString(), tags); } diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 8bd94aa5b6..f728cd05ac 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -663,7 +663,8 @@ namespace umbraco /// Md5 has of the string public static string md5(string text) { - return text.ToMd5(); + //return text.ToMd5(); + return text.GenerateHash(); } /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GzipCompressor.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GzipCompressor.cs index d03dca1d6d..a06df51f5e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GzipCompressor.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GzipCompressor.cs @@ -12,6 +12,7 @@ using System.IO; using System.IO.Compression; using System.Security.Cryptography; using System.Text; +using Umbraco.Core; namespace umbraco.presentation.plugins.tinymce3 { @@ -91,7 +92,7 @@ namespace umbraco.presentation.plugins.tinymce3 key += item.Data; } - key = MD5(key); + key = Hash(key); if (this.NoCompression) { this.SendPlainText(key, to_stream); return; @@ -224,12 +225,9 @@ namespace umbraco.presentation.plugins.tinymce3 } } - private string MD5(string str) { - MD5 md5 = new MD5CryptoServiceProvider(); - byte[] result = md5.ComputeHash(Encoding.ASCII.GetBytes(str)); - str = BitConverter.ToString(result); - - return str.Replace("-", ""); + private string Hash(string str) + { + return str.GenerateHash(); } #endregion diff --git a/src/UmbracoExamine/LocalStorage/AzureLocalStorageDirectory.cs b/src/UmbracoExamine/LocalStorage/AzureLocalStorageDirectory.cs index 56d1b414c5..72a894f32f 100644 --- a/src/UmbracoExamine/LocalStorage/AzureLocalStorageDirectory.cs +++ b/src/UmbracoExamine/LocalStorage/AzureLocalStorageDirectory.cs @@ -16,7 +16,7 @@ namespace UmbracoExamine.LocalStorage { public DirectoryInfo GetLocalStorageDirectory(NameValueCollection config, string configuredPath) { - var appDomainHash = HttpRuntime.AppDomainAppId.ToMd5(); + var appDomainHash = HttpRuntime.AppDomainAppId.GenerateHash(); var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "LuceneDir", //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not diff --git a/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs b/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs index e3c7a22bd0..cddf6bc5dd 100644 --- a/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs +++ b/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs @@ -26,8 +26,8 @@ namespace umbraco.MacroEngines return "~/" + physicalPath; } - public string GetMd5(string text) { - return text.ToMd5(); + public string GetHash(string text) { + return text.GenerateHash(); } /// @@ -145,7 +145,7 @@ namespace umbraco.MacroEngines //Get Rid Of Whitespace From Start/End razorSyntax = razorSyntax.Trim(); //Use MD5 as a cache key - var syntaxMd5 = GetMd5(razorSyntax); + var syntaxMd5 = GetHash(razorSyntax); var fileName = "inline-" + syntaxMd5 + "." + scriptLanguage; return CreateTemporaryRazorFile(razorSyntax, fileName, true); } diff --git a/src/umbraco.editorControls/tinyMCE3/webcontrol/plugin/GzipCompressor.cs b/src/umbraco.editorControls/tinyMCE3/webcontrol/plugin/GzipCompressor.cs index cbd2fc24d8..d84453fa03 100644 --- a/src/umbraco.editorControls/tinyMCE3/webcontrol/plugin/GzipCompressor.cs +++ b/src/umbraco.editorControls/tinyMCE3/webcontrol/plugin/GzipCompressor.cs @@ -12,6 +12,7 @@ using System.IO; using System.IO.Compression; using System.Security.Cryptography; using System.Text; +using Umbraco.Core; namespace umbraco.editorControls.tinyMCE3.webcontrol.plugin { @@ -93,7 +94,7 @@ namespace umbraco.editorControls.tinyMCE3.webcontrol.plugin key += item.Data; } - key = MD5(key); + key = Hash(key); if (this.NoCompression) { this.SendPlainText(key, to_stream); return; @@ -226,12 +227,9 @@ namespace umbraco.editorControls.tinyMCE3.webcontrol.plugin } } - private string MD5(string str) { - MD5 md5 = new MD5CryptoServiceProvider(); - byte[] result = md5.ComputeHash(Encoding.ASCII.GetBytes(str)); - str = BitConverter.ToString(result); - - return str.Replace("-", ""); + private string Hash(string str) + { + return str.GenerateHash(); } #endregion