summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TwoStepsAuthenticator.TestWebsite/Controllers/HomeController.cs5
-rw-r--r--TwoStepsAuthenticator.UnitTests/CounterAuthenticatorTests.cs4
-rw-r--r--TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs6
-rw-r--r--TwoStepsAuthenticator/CounterAuthenticator.cs11
-rw-r--r--TwoStepsAuthenticator/IUsedCodesManager.cs14
-rw-r--r--TwoStepsAuthenticator/TimeAuthenticator.cs24
-rw-r--r--TwoStepsAuthenticator/UsedCodesManager.cs31
7 files changed, 57 insertions, 38 deletions
diff --git a/TwoStepsAuthenticator.TestWebsite/Controllers/HomeController.cs b/TwoStepsAuthenticator.TestWebsite/Controllers/HomeController.cs
index ead9168..ca6666c 100644
--- a/TwoStepsAuthenticator.TestWebsite/Controllers/HomeController.cs
+++ b/TwoStepsAuthenticator.TestWebsite/Controllers/HomeController.cs
@@ -44,10 +44,9 @@ namespace TwoStepsAuthenticator.TestWebsite.Controllers
public ActionResult DoubleAuth(string code)
{
WebsiteUser user = (WebsiteUser)Session["AuthenticatedUser"];
- var auth = new TwoStepsAuthenticator.TimeAuthenticator();
- if (auth.CheckCode(user.DoubleAuthKey, code) && usedCodesManager.IsCodeUsed(user.DoubleAuthKey, code))
+ var auth = new TwoStepsAuthenticator.TimeAuthenticator(usedCodeManager: usedCodesManager);
+ if (auth.CheckCode(user.DoubleAuthKey, code))
{
- usedCodesManager.AddCode(user.DoubleAuthKey, code);
FormsAuthentication.SetAuthCookie(user.Login, true);
return RedirectToAction("Welcome");
}
diff --git a/TwoStepsAuthenticator.UnitTests/CounterAuthenticatorTests.cs b/TwoStepsAuthenticator.UnitTests/CounterAuthenticatorTests.cs
index 7b2944f..f834178 100644
--- a/TwoStepsAuthenticator.UnitTests/CounterAuthenticatorTests.cs
+++ b/TwoStepsAuthenticator.UnitTests/CounterAuthenticatorTests.cs
@@ -14,9 +14,9 @@ namespace TwoStepsAuthenticator.UnitTests {
public void CreateKey() {
var authenticator = new CounterAuthenticator();
var secret = Authenticator.GenerateKey();
- var code = authenticator.GetCode(secret, 0L);
+ var code = authenticator.GetCode(secret, 0uL);
- Assert.IsTrue(authenticator.CheckCode(secret, code, 0L), "Generated Code doesn't verify");
+ Assert.IsTrue(authenticator.CheckCode(secret, code, 0uL), "Generated Code doesn't verify");
}
// Test Values from http://www.ietf.org/rfc/rfc4226.txt - Appendix D
diff --git a/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs b/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
index 7099c89..15ae91e 100644
--- a/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
+++ b/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
@@ -14,9 +14,9 @@ namespace TwoStepsAuthenticator.UnitTests {
public void Can_add_codes() {
var manager = new UsedCodesManager();
- Assert.IsFalse(manager.IsCodeUsed("abc", "def"));
- manager.AddCode("abc", "def");
- Assert.IsTrue(manager.IsCodeUsed("abc", "def"));
+ Assert.IsFalse(manager.IsCodeUsed(42uL, "def"));
+ manager.AddCode(42uL, "def");
+ Assert.IsTrue(manager.IsCodeUsed(42uL, "def"));
}
}
diff --git a/TwoStepsAuthenticator/CounterAuthenticator.cs b/TwoStepsAuthenticator/CounterAuthenticator.cs
index e9162fd..d4eafec 100644
--- a/TwoStepsAuthenticator/CounterAuthenticator.cs
+++ b/TwoStepsAuthenticator/CounterAuthenticator.cs
@@ -11,12 +11,14 @@ namespace TwoStepsAuthenticator {
/// </summary>
public class CounterAuthenticator : Authenticator {
private readonly int WindowSize;
+ private readonly IUsedCodesManager UsedCodeManager;
- public CounterAuthenticator(int windowSize = 10) {
+ public CounterAuthenticator(int windowSize = 10, IUsedCodesManager usedCodeManager = null) {
if (windowSize <= 0) {
throw new ArgumentException("look-ahead window size must be positive");
}
+ this.UsedCodeManager = (usedCodeManager == null) ? new UsedCodesManager() : usedCodeManager;
this.WindowSize = windowSize;
}
@@ -56,9 +58,12 @@ namespace TwoStepsAuthenticator {
ulong successfulSequenceNumber = 0uL;
for (uint i = 0; i <= WindowSize; i++) {
- if (ConstantTimeEquals(GetCode(secret, counter + i), code)) {
+ ulong checkCounter = counter + i;
+ if (ConstantTimeEquals(GetCode(secret, checkCounter), code) && !UsedCodeManager.IsCodeUsed(checkCounter, code)) {
codeMatch = true;
- successfulSequenceNumber = counter + i;
+ successfulSequenceNumber = checkCounter;
+
+ UsedCodeManager.AddCode(successfulSequenceNumber, code);
}
}
diff --git a/TwoStepsAuthenticator/IUsedCodesManager.cs b/TwoStepsAuthenticator/IUsedCodesManager.cs
index 205df9d..461975c 100644
--- a/TwoStepsAuthenticator/IUsedCodesManager.cs
+++ b/TwoStepsAuthenticator/IUsedCodesManager.cs
@@ -5,21 +5,21 @@ namespace TwoStepsAuthenticator {
/// <summary>
/// Manages used code to prevent repeated use of a code.
/// </summary>
- interface IUsedCodesManager {
+ public interface IUsedCodesManager {
/// <summary>
/// Adds secret/code pair.
/// </summary>
- /// <param name="secret"></param>
- /// <param name="code"></param>
- void AddCode(string secret, string code);
+ /// <param name="challenge">Used Challenge</param>
+ /// <param name="code">Used Code</param>
+ void AddCode(ulong challenge, string code);
/// <summary>
/// Checks if code was previously used.
/// </summary>
- /// <param name="secret"></param>
- /// <param name="code"></param>
+ /// <param name="challenge">Used Challenge</param>
+ /// <param name="code">Used Code</param>
/// <returns></returns>
- bool IsCodeUsed(string secret, string code);
+ bool IsCodeUsed(ulong challenge, string code);
}
}
diff --git a/TwoStepsAuthenticator/TimeAuthenticator.cs b/TwoStepsAuthenticator/TimeAuthenticator.cs
index 0cfc525..015847d 100644
--- a/TwoStepsAuthenticator/TimeAuthenticator.cs
+++ b/TwoStepsAuthenticator/TimeAuthenticator.cs
@@ -11,9 +11,13 @@ namespace TwoStepsAuthenticator {
/// </summary>
public class TimeAuthenticator : Authenticator {
private readonly Func<DateTime> NowFunc;
+ private readonly IUsedCodesManager UsedCodeManager;
+ private readonly int IntervalSeconds;
- public TimeAuthenticator(Func<DateTime> nowFunc = null) {
+ public TimeAuthenticator(Func<DateTime> nowFunc = null, IUsedCodesManager usedCodeManager = null, int intervalSeconds = 30) {
this.NowFunc = (nowFunc == null) ? () => DateTime.Now : nowFunc;
+ this.UsedCodeManager = (usedCodeManager == null) ? new UsedCodesManager() : usedCodeManager;
+ this.IntervalSeconds = intervalSeconds;
}
/// <summary>
@@ -32,10 +36,7 @@ namespace TwoStepsAuthenticator {
/// <param name="date">Time to use as challenge</param>
/// <returns>OTP</returns>
public string GetCode(string secret, DateTime date) {
- TimeSpan ts = (date.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc));
- var interval = (long)ts.TotalSeconds / 30;
-
- return GetCodeInternal(secret, (ulong)interval);
+ return GetCodeInternal(secret, (ulong)GetInterval(date));
}
/// <summary>
@@ -64,15 +65,24 @@ namespace TwoStepsAuthenticator {
// We need to do this in constant time
var codeMatch = false;
for (int i = -2; i <= 1; i++) {
- var checkTime = baseTime.AddSeconds(30 * i);
- if (ConstantTimeEquals(GetCode(secret, checkTime), code)) {
+ var checkTime = baseTime.AddSeconds(IntervalSeconds * i);
+ ulong checkInterval = (ulong)GetInterval(checkTime);
+
+ if (ConstantTimeEquals(GetCode(secret, checkTime), code) && !UsedCodeManager.IsCodeUsed(checkInterval, code)) {
codeMatch = true;
successfulTime = checkTime;
+
+ UsedCodeManager.AddCode(checkInterval, code);
}
}
usedDateTime = successfulTime;
return codeMatch;
}
+
+ private long GetInterval(DateTime dateTime) {
+ TimeSpan ts = (dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+ return (long)ts.TotalSeconds / IntervalSeconds;
+ }
}
}
diff --git a/TwoStepsAuthenticator/UsedCodesManager.cs b/TwoStepsAuthenticator/UsedCodesManager.cs
index 57699c5..6ffd6cf 100644
--- a/TwoStepsAuthenticator/UsedCodesManager.cs
+++ b/TwoStepsAuthenticator/UsedCodesManager.cs
@@ -7,18 +7,23 @@ using System.Timers;
namespace TwoStepsAuthenticator
{
- public class UsedCodesManager : TwoStepsAuthenticator.IUsedCodesManager
+ /// <summary>
+ /// Local, thread-save used codes manager implementation
+ /// </summary>
+ public class UsedCodesManager : IUsedCodesManager
{
- internal class UsedCode
+ internal sealed class UsedCode
{
- public UsedCode(String secret, String code)
+ public UsedCode(ulong challenge, String code)
{
this.UseDate = DateTime.Now;
- this.Code = secret + code;
+ this.Code = code;
+ this.ChallengeValue = challenge;
}
- public DateTime UseDate { get; private set; }
- public String Code { get; private set; }
+ internal DateTime UseDate { get; private set; }
+ internal ulong ChallengeValue { get; private set; }
+ internal String Code { get; private set; }
public override bool Equals(object obj)
{
@@ -27,15 +32,15 @@ namespace TwoStepsAuthenticator
}
var other = obj as UsedCode;
- return (other != null) ? this.Code.Equals(other.Code) : false;
+ return (other != null) ? this.Code.Equals(other.Code) && this.ChallengeValue.Equals(other.ChallengeValue) : false;
}
public override string ToString()
{
- return Code;
+ return String.Format("{0}: {1}", ChallengeValue, Code);
}
public override int GetHashCode()
{
- return Code.GetHashCode();
+ return Code.GetHashCode() + ChallengeValue.GetHashCode() * 17;
}
}
@@ -70,12 +75,12 @@ namespace TwoStepsAuthenticator
}
}
- public void AddCode(String secret, String code)
+ public void AddCode(ulong challenge, String code)
{
try {
rwlock.AcquireWriterLock(lockingTimeout);
- codes.Enqueue(new UsedCode(secret, code));
+ codes.Enqueue(new UsedCode(challenge, code));
}
finally
{
@@ -83,13 +88,13 @@ namespace TwoStepsAuthenticator
}
}
- public bool IsCodeUsed(String secret, String code)
+ public bool IsCodeUsed(ulong challenge, String code)
{
try
{
rwlock.AcquireReaderLock(lockingTimeout);
- return codes.Contains(new UsedCode(secret, code));
+ return codes.Contains(new UsedCode(challenge, code));
}
finally
{