summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md34
-rw-r--r--TwoStepsAuthenticator.UnitTests/MockUsedCodesManager.cs16
-rw-r--r--TwoStepsAuthenticator.UnitTests/TimeAuthenticatorTests.cs31
-rw-r--r--TwoStepsAuthenticator.UnitTests/TwoStepsAuthenticator.UnitTests.csproj1
-rw-r--r--TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs14
-rw-r--r--TwoStepsAuthenticator.sln6
-rw-r--r--TwoStepsAuthenticator/CounterAuthenticator.cs27
-rw-r--r--TwoStepsAuthenticator/TimeAuthenticator.cs2
8 files changed, 83 insertions, 48 deletions
diff --git a/README.md b/README.md
index 45bc86f..806dc60 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,10 @@ Compatible with Microsoft Authenticator for Windows Phone, and Google Authentica
You can use this library as well for a client application (if you want to create your own authenticator) or for a server application (add two-step authentication on your asp.net website)
+# TOTP: Time-Based One-Time Password Algorithm
+
+## Client usage
+
For a client application, you need to save the secret key for your user. <br/>
Then, you only have to call the method GetCode(string) :
@@ -18,6 +22,8 @@ var authenticator = new TwoStepsAuthenticator.TimeAuthenticator();
var code = authenticator.GetCode(secret);
</code></pre>
+## Server usage
+
On a server application, you will have to generate a secret key, and share it with the user, who will have to enter it in his own authenticator app.
<pre><code>
@@ -35,17 +41,21 @@ var authenticator = new TwoStepsAuthenticator.TimeAuthenticator();
bool isok = authenticator.CheckCode(secret, code);
</code></pre>
-Every code should only be used once. To prevent repeated use of a code a UsedCodesManager class is provided.<br>
-It should be used as a singleton instance.
+### Used codes manager
+Every code should only be used once. To prevent repeated use of a code a IUsedCodesManager interface is provided.<br>
+
+A default implementation is provided : used codes are kept in memory for 5 minutes (long enough for codes to become invalid)
+
+You can define how the used codes are stored, for example if you want to handle persistence (database storage), or if you have multiple webservers.<br/>
+You have to implement the 2 methods of the IUsedCodesManager :
<pre><code>
-var usedCodesManager = new UsedCodesManager();
-var secret = user.secretAuthToken;
-var code = Request.Form["code"];
-if (autenticator.CheckCode(secret, code) && usedCodesManager.IsCodeUsed(secret, code)) {
- usedCodesManager.AddCode(secret, code);
- // OK
-} else {
- // Not OK
-}
-</code></pre> \ No newline at end of file
+void AddCode(ulong challenge, string code);
+bool IsCodeUsed(ulong challenge, string code);
+</code></pre>
+
+When you create a new Authenticator, add the instance of your IUsedCodesManager as the first param
+<pre><code>
+var usedCodeManager = new CustomUsedCodeManager();
+var authenticator = new TwoStepsAuthenticator.TimeAuthenticator(usedCodeManager);
+</code></pre>
diff --git a/TwoStepsAuthenticator.UnitTests/MockUsedCodesManager.cs b/TwoStepsAuthenticator.UnitTests/MockUsedCodesManager.cs
new file mode 100644
index 0000000..cb0065f
--- /dev/null
+++ b/TwoStepsAuthenticator.UnitTests/MockUsedCodesManager.cs
@@ -0,0 +1,16 @@
+namespace TwoStepsAuthenticator.UnitTests
+{
+ internal class MockUsedCodesManager : IUsedCodesManager {
+ public ulong? LastChallenge { get; private set; }
+ public string LastCode { get; private set; }
+
+ public void AddCode(ulong challenge, string code) {
+ this.LastChallenge = challenge;
+ this.LastCode = code;
+ }
+
+ public bool IsCodeUsed(ulong challenge, string code) {
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/TwoStepsAuthenticator.UnitTests/TimeAuthenticatorTests.cs b/TwoStepsAuthenticator.UnitTests/TimeAuthenticatorTests.cs
index 1a1ffc6..55be883 100644
--- a/TwoStepsAuthenticator.UnitTests/TimeAuthenticatorTests.cs
+++ b/TwoStepsAuthenticator.UnitTests/TimeAuthenticatorTests.cs
@@ -4,20 +4,24 @@ using System.Linq;
using System.Text;
using NUnit.Framework;
-namespace TwoStepsAuthenticator.UnitTests {
-
+namespace TwoStepsAuthenticator.UnitTests
+{
+
[TestFixture]
- public class TimeAuthenticatorTests {
+ public class TimeAuthenticatorTests
+ {
private MockUsedCodesManager mockUsedCodesManager { get; set; }
[SetUp]
- public void SetUp() {
+ public void SetUp()
+ {
this.mockUsedCodesManager = new MockUsedCodesManager();
}
[Test]
- public void CreateKey() {
- var authenticator = new TimeAuthenticator(usedCodeManager: mockUsedCodesManager);
+ public void CreateKey()
+ {
+ var authenticator = new TimeAuthenticator(mockUsedCodesManager);
var secret = Authenticator.GenerateKey();
var code = authenticator.GetCode(secret);
@@ -25,9 +29,10 @@ namespace TwoStepsAuthenticator.UnitTests {
}
[Test]
- public void Uses_usedCodesManager() {
+ public void Uses_usedCodesManager()
+ {
var date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
- var authenticator = new TimeAuthenticator(() => date, usedCodeManager: mockUsedCodesManager);
+ var authenticator = new TimeAuthenticator(mockUsedCodesManager, () => date);
var secret = Authenticator.GenerateKey();
var code = authenticator.GetCode(secret);
@@ -41,18 +46,20 @@ namespace TwoStepsAuthenticator.UnitTests {
[TestCase("DRMK64PPMMC7TDZF", "2013-12-04 18:33:01 +0100", "661188")]
[TestCase("EQOGSM3XZUH6SE2Y", "2013-12-04 18:34:56 +0100", "256804")]
[TestCase("4VU7EQACVDMFJSBG", "2013-12-04 18:36:16 +0100", "800872")]
- public void VerifyKeys(string secret, string timeString, string code) {
+ public void VerifyKeys(string secret, string timeString, string code)
+ {
var date = DateTime.Parse(timeString);
- var authenticator = new TimeAuthenticator(() => date, usedCodeManager: mockUsedCodesManager);
+ var authenticator = new TimeAuthenticator(mockUsedCodesManager, () => date);
Assert.IsTrue(authenticator.CheckCode(secret, code));
}
[Test]
- public void VerifyUsedTime() {
+ public void VerifyUsedTime()
+ {
var date = DateTime.Parse("2013-12-05 17:23:50 +0100");
- var authenticator = new TimeAuthenticator(() => date, usedCodeManager: mockUsedCodesManager);
+ var authenticator = new TimeAuthenticator(mockUsedCodesManager, () => date);
DateTime usedTime;
diff --git a/TwoStepsAuthenticator.UnitTests/TwoStepsAuthenticator.UnitTests.csproj b/TwoStepsAuthenticator.UnitTests/TwoStepsAuthenticator.UnitTests.csproj
index aa38917..38fce5d 100644
--- a/TwoStepsAuthenticator.UnitTests/TwoStepsAuthenticator.UnitTests.csproj
+++ b/TwoStepsAuthenticator.UnitTests/TwoStepsAuthenticator.UnitTests.csproj
@@ -51,6 +51,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="CounterAuthenticatorTests.cs" />
+ <Compile Include="MockUsedCodesManager.cs" />
<Compile Include="TimeAuthenticatorTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UsedCodesManagerTests.cs" />
diff --git a/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs b/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
index 1138f54..15ae91e 100644
--- a/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
+++ b/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
@@ -20,18 +20,4 @@ namespace TwoStepsAuthenticator.UnitTests {
}
}
-
- internal class MockUsedCodesManager : IUsedCodesManager {
- public ulong? LastChallenge { get; private set; }
- public string LastCode { get; private set; }
-
- public void AddCode(ulong challenge, string code) {
- this.LastChallenge = challenge;
- this.LastCode = code;
- }
-
- public bool IsCodeUsed(ulong challenge, string code) {
- return false;
- }
- }
}
diff --git a/TwoStepsAuthenticator.sln b/TwoStepsAuthenticator.sln
index 6963eba..88c3a3a 100644
--- a/TwoStepsAuthenticator.sln
+++ b/TwoStepsAuthenticator.sln
@@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwoStepsAuthenticator.TestW
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwoStepsAuthenticator.UnitTests", "TwoStepsAuthenticator.UnitTests\TwoStepsAuthenticator.UnitTests.csproj", "{E936FFA0-2E6E-4CA7-9841-FB844A817E0C}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{378161AA-219B-4470-9339-7D5DC803E4E0}"
+ ProjectSection(SolutionItems) = preProject
+ LICENSE = LICENSE
+ README.md = README.md
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/TwoStepsAuthenticator/CounterAuthenticator.cs b/TwoStepsAuthenticator/CounterAuthenticator.cs
index fffae73..7fad752 100644
--- a/TwoStepsAuthenticator/CounterAuthenticator.cs
+++ b/TwoStepsAuthenticator/CounterAuthenticator.cs
@@ -4,17 +4,21 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace TwoStepsAuthenticator {
+namespace TwoStepsAuthenticator
+{
/// <summary>
/// Implementation of RFC 4226 Counter-Based One-Time Password Algorithm
/// </summary>
- public class CounterAuthenticator : Authenticator {
+ public class CounterAuthenticator : Authenticator
+ {
private readonly int WindowSize;
private readonly IUsedCodesManager UsedCodeManager;
- public CounterAuthenticator(int windowSize = 10, IUsedCodesManager usedCodeManager = null) {
- if (windowSize <= 0) {
+ public CounterAuthenticator(IUsedCodesManager usedCodeManager = null, int windowSize = 10)
+ {
+ if (windowSize <= 0)
+ {
throw new ArgumentException("look-ahead window size must be positive");
}
@@ -28,7 +32,8 @@ namespace TwoStepsAuthenticator {
/// <param name="secret">Shared Secret</param>
/// <param name="counter">Current Counter</param>
/// <returns>OTP</returns>
- public string GetCode(string secret, ulong counter) {
+ public string GetCode(string secret, ulong counter)
+ {
return GetCodeInternal(secret, counter);
}
@@ -39,7 +44,8 @@ namespace TwoStepsAuthenticator {
/// <param name="code">OTP</param>
/// <param name="counter">Current Counter Position</param>
/// <returns>true if any code from counter to counter + WindowSize matches</returns>
- public bool CheckCode(string secret, string code, ulong counter) {
+ public bool CheckCode(string secret, string code, ulong counter)
+ {
ulong successfulSequenceNumber = 0uL;
return CheckCode(secret, code, counter, out successfulSequenceNumber);
@@ -53,13 +59,16 @@ namespace TwoStepsAuthenticator {
/// <param name="counter">Current Counter Position</param>
/// <param name="usedCounter">Matching counter value if successful</param>
/// <returns>true if any code from counter to counter + WindowSize matches</returns>
- public bool CheckCode(string secret, string code, ulong counter, out ulong usedCounter) {
+ public bool CheckCode(string secret, string code, ulong counter, out ulong usedCounter)
+ {
var codeMatch = false;
ulong successfulSequenceNumber = 0uL;
- for (uint i = 0; i <= WindowSize; i++) {
+ for (uint i = 0; i <= WindowSize; i++)
+ {
ulong checkCounter = counter + i;
- if (ConstantTimeEquals(GetCode(secret, checkCounter), code) && !UsedCodeManager.IsCodeUsed(checkCounter, code)) {
+ if (ConstantTimeEquals(GetCode(secret, checkCounter), code) && !UsedCodeManager.IsCodeUsed(checkCounter, code))
+ {
codeMatch = true;
successfulSequenceNumber = checkCounter;
diff --git a/TwoStepsAuthenticator/TimeAuthenticator.cs b/TwoStepsAuthenticator/TimeAuthenticator.cs
index f46b6d0..4966926 100644
--- a/TwoStepsAuthenticator/TimeAuthenticator.cs
+++ b/TwoStepsAuthenticator/TimeAuthenticator.cs
@@ -14,7 +14,7 @@ namespace TwoStepsAuthenticator {
private readonly IUsedCodesManager UsedCodeManager;
private readonly int IntervalSeconds;
- public TimeAuthenticator(Func<DateTime> nowFunc = null, IUsedCodesManager usedCodeManager = 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) ? Authenticator.DefaultUsedCodeManager.Value : usedCodeManager;
this.IntervalSeconds = intervalSeconds;