From 42e9a93780255da9a61bd756236d58498410e57b Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Tue, 15 Mar 2022 11:36:50 -0700 Subject: [PATCH 1/3] Update recovery code generation --- .../Extensions.Core/src/UserManager.cs | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/Identity/Extensions.Core/src/UserManager.cs b/src/Identity/Extensions.Core/src/UserManager.cs index e4e4c39a8d67..e64e6287acf9 100644 --- a/src/Identity/Extensions.Core/src/UserManager.cs +++ b/src/Identity/Extensions.Core/src/UserManager.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Claims; using System.Security.Cryptography; using System.Text; @@ -2276,7 +2277,60 @@ public virtual string GenerateNewAuthenticatorKey() /// /// protected virtual string CreateTwoFactorRecoveryCode() - => Guid.NewGuid().ToString().Substring(0, 8); + { + var recoveryCode = new StringBuilder(); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append('-'); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + recoveryCode.Append(GetRandomRecoveryCodeChar()); + return recoveryCode.ToString(); + } + + private static char GetRandomRecoveryCodeChar() + { + // We don't want to use any confusing characters like 0/O 1/I/L/l + // Taken from windows valid product key source + const string AllowedChars = "23456789BCDFGHJKMNPQRTVWXY"; + + // Based on RandomNumberGenerator implementation of GetInt32 + uint range = (uint)AllowedChars.Length - 1; + + // Create a mask for the bits that we care about for the range. The other bits will be + // masked away. + uint mask = range; + mask |= mask >> 1; + mask |= mask >> 2; + mask |= mask >> 4; + mask |= mask >> 8; + mask |= mask >> 16; + +#if NETCOREAPP + Span resultBuffer = stackalloc uint[1]; +#else + var resultBuffer = new byte[1]; +#endif + uint result; + + do + { +#if NETCOREAPP + RandomNumberGenerator.Fill(MemoryMarshal.AsBytes(resultBuffer)); +#else + _rng.GetBytes(resultBuffer); +#endif + result = mask & resultBuffer[0]; + } + while (result > range); + + return AllowedChars[(int)result]; + } /// /// Returns whether a recovery code is valid for a user. Note: recovery codes are only valid From f93d5fab6161267c4644c24b4ce2846e5ae2d5fb Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Tue, 15 Mar 2022 11:48:09 -0700 Subject: [PATCH 2/3] Specify capacity --- src/Identity/Extensions.Core/src/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Identity/Extensions.Core/src/UserManager.cs b/src/Identity/Extensions.Core/src/UserManager.cs index e64e6287acf9..2bed99573d64 100644 --- a/src/Identity/Extensions.Core/src/UserManager.cs +++ b/src/Identity/Extensions.Core/src/UserManager.cs @@ -2278,7 +2278,7 @@ public virtual string GenerateNewAuthenticatorKey() /// protected virtual string CreateTwoFactorRecoveryCode() { - var recoveryCode = new StringBuilder(); + var recoveryCode = new StringBuilder(11); recoveryCode.Append(GetRandomRecoveryCodeChar()); recoveryCode.Append(GetRandomRecoveryCodeChar()); recoveryCode.Append(GetRandomRecoveryCodeChar()); From 8b32c9725f7eb76687d8cc528b4a365f696d4dfa Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Wed, 16 Mar 2022 09:16:01 -0700 Subject: [PATCH 3/3] Switch to char array --- src/Identity/Extensions.Core/src/UserManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Identity/Extensions.Core/src/UserManager.cs b/src/Identity/Extensions.Core/src/UserManager.cs index 2bed99573d64..4b1708e71e07 100644 --- a/src/Identity/Extensions.Core/src/UserManager.cs +++ b/src/Identity/Extensions.Core/src/UserManager.cs @@ -2293,12 +2293,11 @@ protected virtual string CreateTwoFactorRecoveryCode() return recoveryCode.ToString(); } + // We don't want to use any confusing characters like 0/O 1/I/L/l + // Taken from windows valid product key source + private static readonly char[] AllowedChars = "23456789BCDFGHJKMNPQRTVWXY".ToCharArray(); private static char GetRandomRecoveryCodeChar() { - // We don't want to use any confusing characters like 0/O 1/I/L/l - // Taken from windows valid product key source - const string AllowedChars = "23456789BCDFGHJKMNPQRTVWXY"; - // Based on RandomNumberGenerator implementation of GetInt32 uint range = (uint)AllowedChars.Length - 1;