Skip to content

Commit

Permalink
The UsedCodeManager needs to have an user object, in order to avoid a…
Browse files Browse the repository at this point in the history
… user to block a temp code for every other users
  • Loading branch information
glacasa committed Mar 17, 2014
1 parent 0d73b10 commit 6f71d43
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public ActionResult DoubleAuth(string code)
{
WebsiteUser user = (WebsiteUser)Session["AuthenticatedUser"];
var auth = new TwoStepsAuthenticator.TimeAuthenticator(usedCodeManager: usedCodesManager);
if (auth.CheckCode(user.DoubleAuthKey, code))
if (auth.CheckCode(user.DoubleAuthKey, code, user))
{
FormsAuthentication.SetAuthCookie(user.Login, true);
return RedirectToAction("Welcome");
Expand Down
6 changes: 3 additions & 3 deletions TwoStepsAuthenticator.UnitTests/MockUsedCodesManager.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
namespace TwoStepsAuthenticator.UnitTests
{
internal class MockUsedCodesManager : IUsedCodesManager {
public ulong? LastChallenge { get; private set; }
public long? LastChallenge { get; private set; }
public string LastCode { get; private set; }

public void AddCode(ulong challenge, string code) {
public void AddCode(long challenge, string code, object user) {
this.LastChallenge = challenge;
this.LastCode = code;
}

public bool IsCodeUsed(ulong challenge, string code) {
public bool IsCodeUsed(long challenge, string code, object user) {
return false;
}
}
Expand Down
4 changes: 2 additions & 2 deletions TwoStepsAuthenticator.UnitTests/TimeAuthenticatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void Uses_usedCodesManager()
public void Prevent_code_reuse() {
var date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var usedCodesManager = new UsedCodesManager();
var authenticator = new TimeAuthenticator(() => date, usedCodeManager: usedCodesManager);
var authenticator = new TimeAuthenticator(usedCodesManager, () => date);
var secret = Authenticator.GenerateKey();
var code = authenticator.GetCode(secret);

Expand Down Expand Up @@ -76,7 +76,7 @@ public void VerifyUsedTime()

DateTime usedTime;

Assert.True(authenticator.CheckCode("H22Q7WAMQYFZOJ2Q", "696227", out usedTime));
Assert.True(authenticator.CheckCode("H22Q7WAMQYFZOJ2Q", "696227", null, out usedTime));

// 17:23:50 - 30s
Assert.AreEqual(usedTime.Hour, 17);
Expand Down
6 changes: 3 additions & 3 deletions TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public class UsedCodesManagerTests {
public void Can_add_codes() {
var manager = new UsedCodesManager();

Assert.IsFalse(manager.IsCodeUsed(42L, "def"));
manager.AddCode(42L, "def");
Assert.IsTrue(manager.IsCodeUsed(42L, "def"));
Assert.IsFalse(manager.IsCodeUsed(42L, "def","u"));
manager.AddCode(42L, "def", "u");
Assert.IsTrue(manager.IsCodeUsed(42L, "def", "u"));
}

}
Expand Down
8 changes: 5 additions & 3 deletions TwoStepsAuthenticator/IUsedCodesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ public interface IUsedCodesManager {
/// </summary>
/// <param name="challenge">Used Challenge</param>
/// <param name="code">Used Code</param>
void AddCode(long timestamp, string code);
/// <param name="user">The user</param>
void AddCode(long timestamp, string code, object user);

/// <summary>
/// Checks if code was previously used.
/// </summary>
/// <param name="challenge">Used Challenge</param>
/// <param name="code">Used Code</param>
/// <returns></returns>
bool IsCodeUsed(long timestamp, string code);
/// <param name="user">The user</param>
/// <returns>True if the user as already used the code</returns>
bool IsCodeUsed(long timestamp, string code, object user);
}
}
52 changes: 39 additions & 13 deletions TwoStepsAuthenticator/TimeAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@
using System.Text;
using System.Threading.Tasks;

namespace TwoStepsAuthenticator {

namespace TwoStepsAuthenticator
{

/// <summary>
/// Implementation of rfc6238 Time-Based One-Time Password Algorithm
/// </summary>
public class TimeAuthenticator : Authenticator {
public class TimeAuthenticator : Authenticator
{
private static readonly Lazy<IUsedCodesManager> DefaultUsedCodeManager = new Lazy<IUsedCodesManager>(() => new UsedCodesManager());

private readonly Func<DateTime> NowFunc;
private readonly IUsedCodesManager UsedCodeManager;
private readonly int IntervalSeconds;

public TimeAuthenticator(IUsedCodesManager usedCodeManager = null, Func<DateTime> nowFunc = null, int intervalSeconds = 30) {
public TimeAuthenticator(IUsedCodesManager usedCodeManager = null, Func<DateTime> nowFunc = null, int intervalSeconds = 30)
{
this.NowFunc = (nowFunc == null) ? () => DateTime.Now : nowFunc;
this.UsedCodeManager = (usedCodeManager == null) ? DefaultUsedCodeManager.Value : usedCodeManager;
this.IntervalSeconds = intervalSeconds;
Expand All @@ -27,7 +30,8 @@ public TimeAuthenticator(IUsedCodesManager usedCodeManager = null, Func<DateTime
/// </summary>
/// <param name="secret">Shared Secret</param>
/// <returns>OTP</returns>
public string GetCode(string secret) {
public string GetCode(string secret)
{
return GetCode(secret, NowFunc());
}

Expand All @@ -37,7 +41,8 @@ public string GetCode(string secret) {
/// <param name="secret">Shared Secret</param>
/// <param name="date">Time to use as challenge</param>
/// <returns>OTP</returns>
public string GetCode(string secret, DateTime date) {
public string GetCode(string secret, DateTime date)
{
return GetCodeInternal(secret, (ulong)GetInterval(date));
}

Expand All @@ -46,43 +51,64 @@ public string GetCode(string secret, DateTime date) {
/// </summary>
/// <param name="secret">Shared Secret</param>
/// <param name="code">OTP</param>
/// <param name="user">The user</param>
/// <returns>true if code matches</returns>
public bool CheckCode(string secret, string code) {
public bool CheckCode(string secret, string code, object user)
{
DateTime successfulTime = DateTime.MinValue;

return CheckCode(secret, code, out successfulTime);
return CheckCode(secret, code, user, out successfulTime);
}

/// <summary>
/// Checks if the passed code is valid.
/// </summary>
/// <param name="secret">Shared Secret</param>
/// <param name="code">OTP</param>
/// <param name="user">The user</param>
/// <param name="usedDateTime">Matching time if successful</param>
/// <returns>true if code matches</returns>
public bool CheckCode(string secret, string code, out DateTime usedDateTime) {
public bool CheckCode(string secret, string code, object user, out DateTime usedDateTime)
{
var baseTime = NowFunc();
DateTime successfulTime = DateTime.MinValue;

// We need to do this in constant time
var codeMatch = false;
for (int i = -2; i <= 1; i++) {
for (int i = -2; i <= 1; i++)
{
var checkTime = baseTime.AddSeconds(IntervalSeconds * i);
var checkInterval = GetInterval(checkTime);

if (ConstantTimeEquals(GetCode(secret, checkTime), code) && !UsedCodeManager.IsCodeUsed(checkInterval, code)) {
if (ConstantTimeEquals(GetCode(secret, checkTime), code) && (user == null || !UsedCodeManager.IsCodeUsed(checkInterval, code, user)))
{
codeMatch = true;
successfulTime = checkTime;

UsedCodeManager.AddCode(checkInterval, code);
UsedCodeManager.AddCode(checkInterval, code, user);
}
}

usedDateTime = successfulTime;
return codeMatch;
}

private long GetInterval(DateTime dateTime) {

/// <summary>
/// Checks if the passed code is valid.
/// </summary>
/// <param name="secret">Shared Secret</param>
/// <param name="code">OTP</param>
/// <returns>true if code matches</returns>
public bool CheckCode(string secret, string code)
{
DateTime successfulTime = DateTime.MinValue;

return CheckCode(secret, code, null, out successfulTime);
}

private long GetInterval(DateTime dateTime)
{
TimeSpan ts = (dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc));
return (long)ts.TotalSeconds / IntervalSeconds;
}
Expand Down
41 changes: 23 additions & 18 deletions TwoStepsAuthenticator/UsedCodesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,36 @@ public class UsedCodesManager : IUsedCodesManager
{
internal sealed class UsedCode
{
public UsedCode(long timestamp, String code)
public UsedCode(long timestamp, String code, object user)
{
this.UseDate = DateTime.Now;
this.Code = code;
this.Timestamp = timestamp;
this.User = user;
}

internal DateTime UseDate { get; private set; }
internal long Timestamp { get; private set; }
internal String Code { get; private set; }
internal object User { get; private set; }

public override bool Equals(object obj)
{
if (Object.ReferenceEquals(this, obj)) {
if (Object.ReferenceEquals(this, obj))
{
return true;
}

var other = obj as UsedCode;
return (other != null) ? this.Code.Equals(other.Code) && this.Timestamp.Equals(other.Timestamp) : false;
return (other != null) && this.Code.Equals(other.Code) && this.Timestamp.Equals(other.Timestamp) && this.User.Equals(other.User);
}
public override string ToString()
{
return String.Format("{0}: {1}", Timestamp, Code);
}
public override int GetHashCode()
{
return Code.GetHashCode() + Timestamp.GetHashCode() * 17;
return Code.GetHashCode() + (Timestamp.GetHashCode() + User.GetHashCode() * 17) * 17;
}
}

Expand All @@ -61,42 +64,44 @@ void cleaner_Elapsed(object sender, ElapsedEventArgs e)
{
var timeToClean = DateTime.Now.AddMinutes(-5);

try
try
{
rwlock.AcquireWriterLock(lockingTimeout);

while (codes.Count > 0 && codes.Peek().UseDate < timeToClean) {
while (codes.Count > 0 && codes.Peek().UseDate < timeToClean)
{
codes.Dequeue();
}
}
finally
}
finally
{
rwlock.ReleaseWriterLock();
}
}

public void AddCode(long timestamp, String code)
public void AddCode(long timestamp, String code, object user)
{
try {
try
{
rwlock.AcquireWriterLock(lockingTimeout);

codes.Enqueue(new UsedCode(timestamp, code));
}
finally
codes.Enqueue(new UsedCode(timestamp, code, user));
}
finally
{
rwlock.ReleaseWriterLock();
}
}

public bool IsCodeUsed(long timestamp, String code)
public bool IsCodeUsed(long timestamp, String code, object user)
{
try
try
{
rwlock.AcquireReaderLock(lockingTimeout);

return codes.Contains(new UsedCode(timestamp, code));
}
finally
return codes.Contains(new UsedCode(timestamp, code, user));
}
finally
{
rwlock.ReleaseReaderLock();
}
Expand Down

0 comments on commit 6f71d43

Please sign in to comment.