diff options
author | Stephen Jennings <stephen.g.jennings@gmail.com> | 2014-03-19 01:47:00 -0700 |
---|---|---|
committer | Stephen Jennings <stephen.g.jennings@gmail.com> | 2014-03-19 01:47:00 -0700 |
commit | 983eec539bb6af75eb49c807fafbc47241900884 (patch) | |
tree | 71908a44a710b72f02e7cc41efdf99be781806c8 | |
parent | b69d3b7bff903e7d0647c6557a7d14a020d476f6 (diff) | |
download | OATH.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.cs | 46 | ||||
-rw-r--r-- | OATH.Net/TimeBasedOtpGenerator.cs | 39 | ||||
-rw-r--r-- | README.md | 29 |
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; + } } } @@ -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; } |