* Removed obsoletes from IConfigManipulator. * Removed obsolete models builder extensions. * Removed the obsolete ContentDashboardSettings. * Removed the obsolete InstallMissingDatabase setting on GlobalSettings. * Removed obsolete NuCache settings. * Removed obsolete RuntimeMinificationSettings. * Removed obsolete health check constant. * Removed obsolete icon constant. * Removed obsolete telemetry constant. * Removed obsolete property and constructor on UmbracoBuilder. * Removed obsolete constructor on AuditNotificationsHandler. * Removed obsolete constructor on HTTP header health checks. * Removed obsolete constructor on MediaFileManager. * Removed obsolete GetDefaultFileContent on ViewHelper. * Remove obsoleted methods on embed providers. * Fix tests. * Removed obsolete constructors on BlockEditorDataConverter. * Removed obsolete SeedCacheDuration property on CacheSettings. * Removed obsolete PublishCulture on ContentRepositoryExtensions. * Removed obsolete MonitorLock. * Removed obsolete synchronous HasSavedValues from IDataTypeUsageService and IDataTypeUsageRepository. * Removed obsolete HasSavedPropertyValues from IPropertyTypeUsageService and IPropertyTypeUsageRepository. * Removed obsolete methods in ITrackedReferencesService and ITrackedReferencesRepository. * Removed obsolete DateValueEditor constructors. * Removed obsolete GetAutomaticRelationTypesAliases. * Removed obsolete constructor on TextOnlyValueEditor. * Removed obsolete constructors on RegexValidator and RequiredValidator. * Removed obsolete constructs on SliderValueConverter and TagsValueConverter. * Removed obsolete GetContentType methods from IPublishedCache. * Removed ContentFinderByIdPath. * Removed obsolete constructor on DefaultMediaUrlProvider. * Removed obsolete constructor on Domain. * Removed obsolete constructor on PublishedRequest. * Removed obsolete methods on CheckPermissions. * Removed obsolete GetUserId from IBackOfficeSecurity. * Removed obsolete methods on LegacyPasswordSecurity. * Removed obsolete constructors on AuditService. * Removed obsolete methods on IContentEditingService. * Remove obsolete constructors and methods on ContentService/IContentService. * Removed obsolete constructor in ContentTypeEditingService. * Removed obsolete constructor in MediaTypeEditingService. * Removed obsolete constructor in MemberTypeEditingService. * Removed obsolete constructor in ContentTypeService. * Removed obsolete constructors in ContentTypeServiceBase. * Removed obsolete constructors and methods in ContentVersionService. * Removed obsolete constructor in DataTypeUsageService. * Removed obsolete constructor in DomainService. * Removed obsolete constructor in FileService. * Removes obsolete AttemptMove from IContentService. * Removes obsolete SetPreventCleanup from IContentVersionService. * Removes obsolete GetReferences from IDataTypeService. * Removed obsolete SetConsentLevel from IMetricsConsentService. * Removed obsolete methods from IPackageDataInstallation. * Removed obsolete methods from IPackagingService. * Removed obsolete methods on ITwoFactorLoginService. Removed obsolete ITemporaryMediaService. * Removed obsolete constructor from MediaService, MemberTypeService and MediaTypeService. * More obsolete constructors. * Removed obsoleted overloads on IPropertyValidationService. * Fixed build for tests. * Removed obsolete constructor for PublicAccessService, UserService and RelationService. * Removed GetDefaultMemberType. * Removed obsolete user group functionality from IUserService. * Removed obsolete extension methods on IUserService. * Removed obsolete method from ITelemetryService. * Removed obsolete UdiParserServiceConnectors. * Removed obsolete method on ICookieManager. * Removed obsolete DynamicContext. * Removed obsolete XmlHelper. * Fixed failing integration tests. * Removed obsoletes in Umbraco.Cms.Api.Common * Removed obsoletes in Umbraco.Cms.Api.Delivery * Removed obsoletes in Umbraco.Cms.Api.Management * Removed obsoletes in Umbraco.Examine.Lucene * Removed obsoletes in Umbraco.Infrastructure * Fix failing delivery API contract integration test. * Made integration tests internal. * Removed obsoletes from web projects. * Fix build. * Removed Twitter OEmbed provider * Removed obsolete constructor on PublishedDataType. * Removed obsolete constructors on PublishedCacheBase. * Removed the obsolete PropertyEditorTagsExtensions. * Removed obsoletion properties on configuration response models (#18697) * Removed obsolete methods from server-side models. * Update client-side types and sdk. * Update client-side files. * Removed obsoletion of Utf8ToAsciiConverter.ToAsciiString overload. (#18694) * Removed obsolete method in UserService. (#18710) * Removed obsoleted group alias keys from being publicly available. (#18682) * Removed unneceessary ApiVersion attribute. * Clean-up obsoletions on MemberService (#18703) * Removed obsoleted method on MemberService, added future obsoletion to interface and updated all callers. * Removed obsoletion on member service method that's not obsolete on the interface.
218 lines
7.6 KiB
C#
218 lines
7.6 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Umbraco.Extensions;
|
|
|
|
namespace Umbraco.Cms.Core.Security;
|
|
|
|
/// <summary>
|
|
/// Handles password hashing and formatting for legacy hashing algorithms.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Should probably be internal.
|
|
/// </remarks>
|
|
public class LegacyPasswordSecurity
|
|
{
|
|
public static string GenerateSalt()
|
|
{
|
|
var numArray = new byte[16];
|
|
using (var rng = new RNGCryptoServiceProvider())
|
|
{
|
|
rng.GetBytes(numArray);
|
|
return Convert.ToBase64String(numArray);
|
|
}
|
|
}
|
|
|
|
// Used for tests
|
|
internal string FormatPasswordForStorage(string algorithmType, string hashedPassword, string salt)
|
|
{
|
|
if (!SupportHashAlgorithm(algorithmType))
|
|
{
|
|
throw new InvalidOperationException($"{algorithmType} is not supported");
|
|
}
|
|
|
|
return salt + hashedPassword;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies if the password matches the expected hash+salt of the stored password string
|
|
/// </summary>
|
|
/// <param name="algorithm">The hashing algorithm for the stored password.</param>
|
|
/// <param name="password">The password.</param>
|
|
/// <param name="dbPassword">The value of the password stored in a data store.</param>
|
|
/// <returns></returns>
|
|
public bool VerifyPassword(string algorithm, string password, string dbPassword)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(dbPassword))
|
|
{
|
|
throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword));
|
|
}
|
|
|
|
if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var storedHashedPass = ParseStoredHashPassword(algorithm, dbPassword, out var salt);
|
|
var hashed = HashPassword(algorithm, password, salt);
|
|
return storedHashedPass == hashed;
|
|
}
|
|
catch (ArgumentOutOfRangeException)
|
|
{
|
|
// This can happen if the length of the password is wrong and a salt cannot be extracted.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify a legacy hashed password (HMACSHA1)
|
|
/// </summary>
|
|
public bool VerifyLegacyHashedPassword(string password, string dbPassword)
|
|
{
|
|
using var hashAlgorithm = new HMACSHA1
|
|
{
|
|
// the legacy salt was actually the password :(
|
|
Key = Encoding.Unicode.GetBytes(password),
|
|
};
|
|
|
|
var hashed = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password)));
|
|
|
|
return dbPassword == hashed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new password hash and a new salt
|
|
/// </summary>
|
|
internal string HashNewPassword(string algorithm, string newPassword, out string salt)
|
|
{
|
|
salt = GenerateSalt();
|
|
return HashPassword(algorithm, newPassword, salt);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses out the hashed password and the salt from the stored password string value
|
|
/// </summary>
|
|
/// <param name="algorithm">The hashing algorithm for the stored password.</param>
|
|
/// <param name="storedString"></param>
|
|
/// <param name="salt">returns the salt</param>
|
|
/// <returns></returns>
|
|
public string ParseStoredHashPassword(string algorithm, string storedString, out string salt)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(storedString))
|
|
{
|
|
throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString));
|
|
}
|
|
|
|
if (!SupportHashAlgorithm(algorithm))
|
|
{
|
|
throw new InvalidOperationException($"{algorithm} is not supported");
|
|
}
|
|
|
|
var saltLen = GenerateSalt();
|
|
salt = storedString.Substring(0, saltLen.Length);
|
|
return storedString.Substring(saltLen.Length);
|
|
}
|
|
|
|
public bool SupportHashAlgorithm(string algorithm)
|
|
{
|
|
// This is for the v6-v8 hashing algorithm
|
|
if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Default validation value for old machine keys (switched to HMACSHA256 aspnet 4 https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/breaking-changes)
|
|
if (algorithm.InvariantEquals("SHA1"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hashes a password with a given salt
|
|
/// </summary>
|
|
/// <param name="algorithmType">The hashing algorithm for the password.</param>
|
|
/// <param name="pass"></param>
|
|
/// <param name="salt"></param>
|
|
/// <returns></returns>
|
|
private string HashPassword(string algorithmType, string pass, string salt)
|
|
{
|
|
if (!SupportHashAlgorithm(algorithmType))
|
|
{
|
|
throw new InvalidOperationException($"{algorithmType} is not supported");
|
|
}
|
|
|
|
// This is the correct way to implement this (as per the sql membership provider)
|
|
var bytes = Encoding.Unicode.GetBytes(pass);
|
|
var saltBytes = Convert.FromBase64String(salt);
|
|
byte[] inArray;
|
|
|
|
using HashAlgorithm hashAlgorithm = GetHashAlgorithm(algorithmType);
|
|
if (hashAlgorithm is KeyedHashAlgorithm algorithm)
|
|
{
|
|
KeyedHashAlgorithm keyedHashAlgorithm = algorithm;
|
|
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(saltBytes, 0, numArray2, 0, numArray2.Length);
|
|
keyedHashAlgorithm.Key = numArray2;
|
|
}
|
|
else
|
|
{
|
|
// if the salt bytes is too short 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(saltBytes.Length, numArray2.Length - dstOffset);
|
|
Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count);
|
|
dstOffset += count;
|
|
}
|
|
|
|
keyedHashAlgorithm.Key = numArray2;
|
|
}
|
|
|
|
inArray = keyedHashAlgorithm.ComputeHash(bytes);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
}
|
|
|
|
return Convert.ToBase64String(inArray);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the hash algorithm to use based on the provided <paramref name="algorithm"/>.
|
|
/// </summary>
|
|
/// <param name="algorithm">The hashing algorithm name.</param>
|
|
/// <returns></returns>
|
|
private HashAlgorithm GetHashAlgorithm(string algorithm)
|
|
{
|
|
if (algorithm.IsNullOrWhiteSpace())
|
|
{
|
|
throw new InvalidOperationException("No hash algorithm type specified");
|
|
}
|
|
|
|
var alg = HashAlgorithm.Create(algorithm);
|
|
if (alg == null)
|
|
{
|
|
throw new InvalidOperationException($"The hash algorithm specified {algorithm} cannot be resolved");
|
|
}
|
|
|
|
return alg;
|
|
}
|
|
}
|