forked from BrandonPotter/GoogleAuthenticator
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue BrandonPotter#65 ValidateTwoFactorPin always returns false, if …
…the secretKey parameter is base32 encoded string - fixes for review
- Loading branch information
1 parent
ff44a79
commit 3cdfd22
Showing
3 changed files
with
84 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,8 +16,9 @@ namespace Google.Authenticator | |
/// </summary> | ||
public class TwoFactorAuthenticator | ||
{ | ||
private static readonly DateTime _epoch = | ||
private static readonly DateTime _epoch = | ||
new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | ||
|
||
private TimeSpan DefaultClockDriftTolerance { get; set; } | ||
|
||
public TwoFactorAuthenticator() => DefaultClockDriftTolerance = TimeSpan.FromMinutes(5); | ||
|
@@ -35,16 +36,16 @@ public class TwoFactorAuthenticator | |
/// should be 10 or less)</param> | ||
/// <returns>SetupCode object</returns> | ||
public SetupCode GenerateSetupCode( | ||
string issuer, | ||
string accountTitleNoSpaces, | ||
string issuer, | ||
string accountTitleNoSpaces, | ||
string accountSecretKey, | ||
bool secretIsBase32, | ||
bool secretIsBase32, | ||
int qrPixelsPerModule = 3) | ||
{ | ||
var key = secretIsBase32 | ||
? Base32Encoding.ToBytes(accountSecretKey) | ||
: Encoding.UTF8.GetBytes(accountSecretKey); | ||
|
||
return GenerateSetupCode(issuer, accountTitleNoSpaces, key, qrPixelsPerModule); | ||
} | ||
|
||
|
@@ -67,21 +68,24 @@ public SetupCode GenerateSetupCode( | |
bool generateQrCode = true) | ||
{ | ||
if (string.IsNullOrWhiteSpace(accountTitleNoSpaces)) | ||
{ | ||
throw new NullReferenceException("Account Title is null"); | ||
|
||
} | ||
|
||
accountTitleNoSpaces = RemoveWhitespace(Uri.EscapeUriString(accountTitleNoSpaces)); | ||
var encodedSecretKey = Base32Encoding.ToString(accountSecretKey); | ||
|
||
var provisionUrl = string.IsNullOrWhiteSpace(issuer) | ||
? $"otpauth://totp/{accountTitleNoSpaces}?secret={encodedSecretKey.Trim('=')}" | ||
// https://github.com/google/google-authenticator/wiki/Conflicting-Accounts | ||
// Added additional prefix to account otpauth://totp/Company:[email protected] | ||
// for backwards compatibility | ||
: $"otpauth://totp/{UrlEncode(issuer)}:{accountTitleNoSpaces}?secret={encodedSecretKey.Trim('=')}&issuer={UrlEncode(issuer)}"; | ||
|
||
if (!generateQrCode) | ||
return new SetupCode(accountTitleNoSpaces, encodedSecretKey.Trim('='), ""); | ||
|
||
return new SetupCode( | ||
accountTitleNoSpaces, | ||
encodedSecretKey.Trim('='), | ||
GenerateQrCodeUrl(qrPixelsPerModule, provisionUrl)); | ||
generateQrCode ? GenerateQrCodeUrl(qrPixelsPerModule, provisionUrl) : ""); | ||
} | ||
|
||
private static string GenerateQrCodeUrl(int qrPixelsPerModule, string provisionUrl) | ||
|
@@ -96,7 +100,6 @@ private static string GenerateQrCodeUrl(int qrPixelsPerModule, string provisionU | |
using (var ms = new MemoryStream()) | ||
{ | ||
qrCodeImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png); | ||
|
||
qrCodeUrl = $"data:image/png;base64,{Convert.ToBase64String(ms.ToArray())}"; | ||
} | ||
} | ||
|
@@ -126,7 +129,7 @@ private static string GenerateQrCodeUrl(int qrPixelsPerModule, string provisionU | |
return qrCodeUrl; | ||
} | ||
|
||
private static string RemoveWhitespace(string str) => | ||
private static string RemoveWhitespace(string str) => | ||
new string(str.Where(c => !char.IsWhiteSpace(c)).ToArray()); | ||
|
||
private string UrlEncode(string value) | ||
|
@@ -137,29 +140,37 @@ private string UrlEncode(string value) | |
foreach (var symbol in value) | ||
{ | ||
if (validChars.IndexOf(symbol) == -1) | ||
{ | ||
result.AppendFormat("%{0:X2}", (int) symbol); | ||
} | ||
else | ||
{ | ||
result.Append(symbol); | ||
} | ||
} | ||
|
||
return result.Replace(" ", "%20").ToString(); | ||
} | ||
|
||
public string GeneratePINAtInterval( | ||
string accountSecretKey, | ||
string accountSecretKey, | ||
long counter, | ||
int digits = 6, | ||
int digits = 6, | ||
bool secretIsBase32 = false) | ||
=> GenerateHashedCode(accountSecretKey, counter, secretIsBase32, digits); | ||
{ | ||
return GenerateHashedCode(accountSecretKey, counter, secretIsBase32, digits); | ||
} | ||
|
||
private string GenerateHashedCode(string secret, | ||
long iterationNumber, | ||
bool secretIsBase32, | ||
int digits = 6) | ||
=> GenerateHashedCode( | ||
secretIsBase32 ? Base32Encoding.ToBytes(secret):Encoding.UTF8.GetBytes(secret), | ||
{ | ||
return GenerateHashedCode( | ||
secretIsBase32 ? Base32Encoding.ToBytes(secret) : Encoding.UTF8.GetBytes(secret), | ||
iterationNumber, | ||
digits); | ||
} | ||
|
||
private string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6) | ||
{ | ||
|
@@ -169,9 +180,7 @@ private string GenerateHashedCode(byte[] key, long iterationNumber, int digits = | |
Array.Reverse(counter); | ||
|
||
var hmac = new HMACSHA1(key); | ||
|
||
var hash = hmac.ComputeHash(counter); | ||
|
||
var offset = hash[hash.Length - 1] & 0xf; | ||
|
||
// Convert the 4 bytes into an integer, ignoring the sign. | ||
|
@@ -191,17 +200,23 @@ private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep) => | |
(long) (now - epoch).TotalSeconds / timeStep; | ||
|
||
public bool ValidateTwoFactorPIN( | ||
string accountSecretKey, | ||
string accountSecretKey, | ||
string twoFactorCodeFromClient, | ||
bool secretIsBase32 = false) => | ||
ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance, secretIsBase32); | ||
bool secretIsBase32 = false) | ||
{ | ||
return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance, | ||
secretIsBase32); | ||
} | ||
|
||
public bool ValidateTwoFactorPIN( | ||
string accountSecretKey, | ||
string twoFactorCodeFromClient, | ||
TimeSpan timeTolerance, | ||
bool secretIsBase32 = false) => | ||
GetCurrentPINs(accountSecretKey, timeTolerance, secretIsBase32).Any(c => c == twoFactorCodeFromClient); | ||
bool secretIsBase32 = false) | ||
{ | ||
return GetCurrentPINs(accountSecretKey, timeTolerance, secretIsBase32) | ||
.Any(c => c == twoFactorCodeFromClient); | ||
} | ||
|
||
public string GetCurrentPIN(string accountSecretKey, bool secretIsBase32 = false) => | ||
GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(), secretIsBase32: secretIsBase32); | ||
|