diff --git a/src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs b/src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs index 0d984475d0bd..249cc9cfa4e2 100644 --- a/src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs +++ b/src/Identity/Extensions.Core/src/AuthenticatorTokenProvider.cs @@ -54,13 +54,24 @@ public virtual async Task ValidateAsync(string purpose, string token, User return false; } - using var hash = new HMACSHA1(Base32.FromBase32(key)); + var keyBytes = Base32.FromBase32(key); + +#if NET6_0_OR_GREATER + var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); +#else + using var hash = new HMACSHA1(keyBytes); var unixTimestamp = Convert.ToInt64(Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds)); +#endif + var timestep = Convert.ToInt64(unixTimestamp / 30); // Allow codes from 90s in each direction (we could make this configurable?) for (int i = -2; i <= 2; i++) { +#if NET6_0_OR_GREATER + var expectedCode = Rfc6238AuthenticationService.ComputeTotp(keyBytes, (ulong)(timestep + i), modifier: null); +#else var expectedCode = Rfc6238AuthenticationService.ComputeTotp(hash, (ulong)(timestep + i), modifier: null); +#endif if (expectedCode == code) { return true; diff --git a/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs b/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs index 9ed628052bf7..31b2100eb69e 100644 --- a/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs +++ b/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs @@ -30,7 +30,14 @@ public static byte[] GenerateRandomKey() return bytes; } - internal static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier) + internal static int ComputeTotp( +#if NET6_0_OR_GREATER + byte[] key, +#else + HashAlgorithm hashAlgorithm, +#endif + ulong timestepNumber, + string modifier) { // # of 0's = length of pin const int Mod = 1000000; @@ -38,7 +45,12 @@ internal static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumbe // See https://tools.ietf.org/html/rfc4226 // We can add an optional modifier var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber)); + +#if NET6_0_OR_GREATER + var hash = HMACSHA1.HashData(key, ApplyModifier(timestepAsBytes, modifier)); +#else var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier)); +#endif // Generate DT string var offset = hash[hash.Length - 1] & 0xf; @@ -53,7 +65,7 @@ internal static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumbe private static byte[] ApplyModifier(byte[] input, string modifier) { - if (String.IsNullOrEmpty(modifier)) + if (string.IsNullOrEmpty(modifier)) { return input; } @@ -85,10 +97,15 @@ public static int GenerateCode(byte[] securityToken, string modifier = null) // Allow a variance of no greater than 9 minutes in either direction var currentTimeStep = GetCurrentTimeStepNumber(); + +#if NET6_0_OR_GREATER + return ComputeTotp(securityToken, currentTimeStep, modifier); +#else using (var hashAlgorithm = new HMACSHA1(securityToken)) { return ComputeTotp(hashAlgorithm, currentTimeStep, modifier); } +#endif } public static bool ValidateCode(byte[] securityToken, int code, string modifier = null) @@ -100,11 +117,18 @@ public static bool ValidateCode(byte[] securityToken, int code, string modifier // Allow a variance of no greater than 9 minutes in either direction var currentTimeStep = GetCurrentTimeStepNumber(); + +#if !NET6_0_OR_GREATER using (var hashAlgorithm = new HMACSHA1(securityToken)) +#endif { for (var i = -2; i <= 2; i++) { +#if NET6_0_OR_GREATER + var computedTotp = ComputeTotp(securityToken, (ulong)((long)currentTimeStep + i), modifier); +#else var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier); +#endif if (computedTotp == code) { return true; diff --git a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs index 8cebb96d612f..c816da75c822 100644 --- a/src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs +++ b/src/Identity/test/Identity.FunctionalTests/Pages/Account/Manage/EnableAuthenticator.cs @@ -1,14 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Globalization; using System.Net.Http; -using System.Security.Cryptography; -using System.Threading.Tasks; using AngleSharp.Dom.Html; -using Xunit; namespace Microsoft.AspNetCore.Identity.FunctionalTests.Account.Manage { @@ -50,10 +45,10 @@ internal async Task SendValidCodeAsync() public static string ComputeCode(string key) { - var hash = new HMACSHA1(Base32.FromBase32(key)); - var unixTimestamp = Convert.ToInt64(Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds)); + var keyBytes = Base32.FromBase32(key); + var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var timestep = Convert.ToInt64(unixTimestamp / 30); - var topt = Rfc6238AuthenticationService.ComputeTotp(hash, (ulong)timestep, modifier: null); + var topt = Rfc6238AuthenticationService.ComputeTotp(keyBytes, (ulong)timestep, modifier: null); return topt.ToString("D6", CultureInfo.InvariantCulture); } }