summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Jennings <stephen.g.jennings@gmail.com>2014-03-19 01:47:00 -0700
committerStephen Jennings <stephen.g.jennings@gmail.com>2014-03-19 01:47:00 -0700
commit983eec539bb6af75eb49c807fafbc47241900884 (patch)
tree71908a44a710b72f02e7cc41efdf99be781806c8
parentb69d3b7bff903e7d0647c6557a7d14a020d476f6 (diff)
downloadOATH.Net-983eec539bb6af75eb49c807fafbc47241900884.zip
OATH.Net-983eec539bb6af75eb49c807fafbc47241900884.tar.gz
OATH.Net-983eec539bb6af75eb49c807fafbc47241900884.tar.bz2
Add ValidateOtp method to TimeBasedOtpGenerator
-rw-r--r--OATH.Net.Test/TimeBasedOtpGeneratorTests.cs46
-rw-r--r--OATH.Net/TimeBasedOtpGenerator.cs39
-rw-r--r--README.md29
3 files changed, 107 insertions, 7 deletions
diff --git a/OATH.Net.Test/TimeBasedOtpGeneratorTests.cs b/OATH.Net.Test/TimeBasedOtpGeneratorTests.cs
index ebd2205..a51d91a 100644
--- a/OATH.Net.Test/TimeBasedOtpGeneratorTests.cs
+++ b/OATH.Net.Test/TimeBasedOtpGeneratorTests.cs
@@ -137,6 +137,52 @@ namespace OathNet.Test
this.TestSHA1AndAssert(key, 6, new DateTime(2011, 10, 17, 7, 52, 0, DateTimeKind.Utc), "139594");
}
+ [Test]
+ public void ValidateOtp_test_validates_within_60_second_validity_period()
+ {
+ keyMock.Setup(k => k.Binary).Returns(binaryForSha1ReferenceKey);
+ var generator = new TimeBasedOtpGenerator(key, 8);
+ var currentTime = new DateTime(2009, 2, 13, 23, 31, 30, DateTimeKind.Utc);
+ var sixtySeconds = TimeSpan.FromSeconds(60);
+ Assert.IsFalse(generator.ValidateOtp("89005924", currentTime.AddSeconds(-90), sixtySeconds), "90 seconds prior should be invalid");
+ Assert.IsTrue(generator.ValidateOtp("89005924", currentTime.AddSeconds(-60), sixtySeconds), "60 seconds prior should be valid");
+ Assert.IsTrue(generator.ValidateOtp("89005924", currentTime.AddSeconds(-15), sixtySeconds), "15 seconds prior should be valid");
+ Assert.IsTrue(generator.ValidateOtp("89005924", currentTime.AddSeconds(+00), sixtySeconds), "The exact time should be valid");
+ Assert.IsTrue(generator.ValidateOtp("89005924", currentTime.AddSeconds(+15), sixtySeconds), "15 seconds after should be valid");
+ Assert.IsTrue(generator.ValidateOtp("89005924", currentTime.AddSeconds(+60), sixtySeconds), "60 seconds after should be valid");
+ Assert.IsFalse(generator.ValidateOtp("89005924", currentTime.AddSeconds(+90), sixtySeconds), "90 seconds after should be invalid");
+ }
+
+ [Test]
+ public void ValidateOtp_test_validates_within_50_second_validity_period()
+ {
+ keyMock.Setup(k => k.Binary).Returns(binaryForSha1ReferenceKey);
+ var generator = new TimeBasedOtpGenerator(key, 8);
+ var currentTime = new DateTime(2033, 5, 18, 3, 33, 20, DateTimeKind.Utc);
+ var fiftySeconds = TimeSpan.FromSeconds(50);
+ Assert.IsFalse(generator.ValidateOtp("69279037", currentTime.AddSeconds(-90), fiftySeconds), "90 seconds prior should be invalid");
+ Assert.IsFalse(generator.ValidateOtp("69279037", currentTime.AddSeconds(-60), fiftySeconds), "60 seconds prior should be invalid");
+ Assert.IsTrue(generator.ValidateOtp("69279037", currentTime.AddSeconds(-30), fiftySeconds), "30 seconds prior should be valid");
+ Assert.IsTrue(generator.ValidateOtp("69279037", currentTime.AddSeconds(+00), fiftySeconds), "The exact time should be valid");
+ Assert.IsTrue(generator.ValidateOtp("69279037", currentTime.AddSeconds(+30), fiftySeconds), "30 seconds after should be valid");
+ Assert.IsFalse(generator.ValidateOtp("69279037", currentTime.AddSeconds(+60), fiftySeconds), "60 seconds after should be invalid");
+ Assert.IsFalse(generator.ValidateOtp("69279037", currentTime.AddSeconds(+90), fiftySeconds), "90 seconds after should be invalid");
+ }
+
+ [Test]
+ public void ValidateOtp_test_validates_with_an_empty_validity_period()
+ {
+ keyMock.Setup(k => k.Binary).Returns(binaryForSha1ReferenceKey);
+ var generator = new TimeBasedOtpGenerator(key, 8);
+ var currentTime = new DateTime(2033, 5, 18, 3, 33, 20, DateTimeKind.Utc);
+ var zeroSeconds = TimeSpan.FromSeconds(0);
+ Assert.IsFalse(generator.ValidateOtp("69279037", currentTime.AddSeconds(-30), zeroSeconds), "30 seconds prior should be invalid");
+ Assert.IsTrue(generator.ValidateOtp("69279037", currentTime.AddSeconds(-01), zeroSeconds), "1 second prior should be valid (due to a 30-second precision)");
+ Assert.IsTrue(generator.ValidateOtp("69279037", currentTime.AddSeconds(+00), zeroSeconds), "The exact time should be valid");
+ Assert.IsTrue(generator.ValidateOtp("69279037", currentTime.AddSeconds(+01), zeroSeconds), "1 seconds after should be valid (due to a 30-second precision)");
+ Assert.IsFalse(generator.ValidateOtp("69279037", currentTime.AddSeconds(+30), zeroSeconds), "30 seconds after should be invalid");
+ }
+
private string GetOtpWithImplicitHMAC(Key key, int digits, DateTime time)
{
var otp = new TimeBasedOtpGenerator(key, digits);
diff --git a/OATH.Net/TimeBasedOtpGenerator.cs b/OATH.Net/TimeBasedOtpGenerator.cs
index 5e76b58..d636e58 100644
--- a/OATH.Net/TimeBasedOtpGenerator.cs
+++ b/OATH.Net/TimeBasedOtpGenerator.cs
@@ -28,6 +28,7 @@ namespace OathNet
/// </example>
public class TimeBasedOtpGenerator
{
+ private static readonly TimeSpan DefaultValidityPeriod = TimeSpan.FromSeconds(60);
private CounterBasedOtpGenerator counterOtp;
/// <summary>
@@ -72,5 +73,43 @@ namespace OathNet
return this.counterOtp.GenerateOtp(steps);
}
+
+ /// <summary>
+ /// Validates a given OTP. A default validity period of 60 seconds is used.
+ /// </summary>
+ /// <param name="providedOtp">The OTP provided by the user attempting authentication.</param>
+ /// <param name="currentTime">The time at which the given OTP should be valid.</param>
+ /// <returns>True if the provided OTP is valid, otherwise false.</returns>
+ public bool ValidateOtp(string providedOtp, DateTime currentTime)
+ {
+ return this.ValidateOtp(providedOtp, currentTime, DefaultValidityPeriod);
+ }
+
+ /// <summary>
+ /// Validates a given OTP using the provided validity period.
+ /// </summary>
+ /// <param name="providedOtp">The OTP provided by the user attempting authentication.</param>
+ /// <param name="currentTime">The time at which the given OTP should be valid.</param>
+ /// <param name="validityPeriod">The interval of time in which the provided OTP should be allowed. For example, a validity period of 60 seconds indicates the code generated at 6:41 PM should still be valid at 6:40 PM and 6:42 PM. This helps accomodate for inaccurately-set clocks.</param>
+ /// <returns>True if the provided OTP is valid, otherwise false.</returns>
+ public bool ValidateOtp(string providedOtp, DateTime currentTime, TimeSpan validityPeriod)
+ {
+ var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ var span = currentTime.ToUniversalTime() - unixEpoch;
+ var steps = (int)(span.TotalSeconds / 30);
+ var interval = (int)(Math.Abs(validityPeriod.TotalSeconds) / 30);
+ var minSteps = steps - interval;
+ var maxSteps = steps + interval;
+
+ for (int step = minSteps; step <= maxSteps; step++)
+ {
+ if (this.counterOtp.GenerateOtp(step).Equals(providedOtp, StringComparison.InvariantCultureIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
}
diff --git a/README.md b/README.md
index 22f5213..75ed1f2 100644
--- a/README.md
+++ b/README.md
@@ -11,17 +11,18 @@ OATH.Net is a .NET library to perform OATH authentication.
## Usage
- public bool AuthorizedWithHOTP(string userSuppliedCode, User user)
+Add to your project with "`Install-Package OATH.Net`".
+
+ // Time-based OTP
+
+ public bool CreateTOTPCode(User user)
{
string secretKey = user.SecretKey;
int otpDigits = 8;
- int counterValue = user.NextCounterValue();
Key key = new Key(secretKey);
- CounterBasedOtpGenerator otp = new CounterBasedOtpGenerator(key, otpDigits);
- string validCode = otp.ComputeOtp(counterValue);
-
- return userSuppliedCode == validCode;
+ TimeBasedOtpGenerator otp = new TimeBasedOtpGenerator(key, otpDigits);
+ return otp.GenerateOtp(DateTime.UtcNow);
}
public bool AuthorizedWithTOTP(string userSuppliedCode, User user)
@@ -31,7 +32,21 @@ OATH.Net is a .NET library to perform OATH authentication.
Key key = new Key(secretKey);
TimeBasedOtpGenerator otp = new TimeBasedOtpGenerator(key, otpDigits);
- string validCode = otp.ComputeOtp(DateTime.UtcNow);
+ return otp.ValidateOtp(userSuppliedCode, DateTime.UtcNow);
+ }
+
+
+ // Counter-based OTP
+
+ public bool AuthorizedWithHOTP(string userSuppliedCode, User user)
+ {
+ string secretKey = user.SecretKey;
+ int otpDigits = 8;
+ int counterValue = user.NextCounterValue();
+
+ Key key = new Key(secretKey);
+ CounterBasedOtpGenerator otp = new CounterBasedOtpGenerator(key, otpDigits);
+ string validCode = otp.ComputeOtp(counterValue);
return userSuppliedCode == validCode;
}