summaryrefslogtreecommitdiffstats
path: root/TwoStepsAuthenticator.DotnetCore/TimeAuthenticator.cs
diff options
context:
space:
mode:
Diffstat (limited to 'TwoStepsAuthenticator.DotnetCore/TimeAuthenticator.cs')
-rw-r--r--TwoStepsAuthenticator.DotnetCore/TimeAuthenticator.cs117
1 files changed, 117 insertions, 0 deletions
diff --git a/TwoStepsAuthenticator.DotnetCore/TimeAuthenticator.cs b/TwoStepsAuthenticator.DotnetCore/TimeAuthenticator.cs
new file mode 100644
index 0000000..7272b77
--- /dev/null
+++ b/TwoStepsAuthenticator.DotnetCore/TimeAuthenticator.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TwoStepsAuthenticator
+{
+
+ /// <summary>
+ /// Implementation of rfc6238 Time-Based One-Time Password Algorithm
+ /// </summary>
+ 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)
+ {
+ this.NowFunc = (nowFunc == null) ? () => DateTime.Now : nowFunc;
+ this.UsedCodeManager = (usedCodeManager == null) ? DefaultUsedCodeManager.Value : usedCodeManager;
+ this.IntervalSeconds = intervalSeconds;
+ }
+
+ /// <summary>
+ /// Generates One-Time Password.
+ /// </summary>
+ /// <param name="secret">Shared Secret</param>
+ /// <returns>OTP</returns>
+ public string GetCode(string secret)
+ {
+ return GetCode(secret, NowFunc());
+ }
+
+ /// <summary>
+ /// Generates One-Time Password.
+ /// </summary>
+ /// <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)
+ {
+ return GetCodeInternal(secret, (ulong)GetInterval(date));
+ }
+
+ /// <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>
+ /// <returns>true if code matches</returns>
+ public bool CheckCode(string secret, string code, object user)
+ {
+ DateTime successfulTime = DateTime.MinValue;
+
+ 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, 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++)
+ {
+ var checkTime = baseTime.AddSeconds(IntervalSeconds * i);
+ var checkInterval = GetInterval(checkTime);
+
+ if (ConstantTimeEquals(GetCode(secret, checkTime), code) && (user == null || !UsedCodeManager.IsCodeUsed(checkInterval, code, user)))
+ {
+ codeMatch = true;
+ successfulTime = checkTime;
+
+ UsedCodeManager.AddCode(checkInterval, code, user);
+ }
+ }
+
+ usedDateTime = successfulTime;
+ return codeMatch;
+ }
+
+
+ /// <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>
+ [Obsolete("The CheckCode method should only be used with a user object")]
+ 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;
+ }
+ }
+}