summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md27
-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.cs18
-rw-r--r--TwoStepsAuthenticator/TimeAuthenticator.cs2
8 files changed, 82 insertions, 33 deletions
diff --git a/README.md b/README.md
index d53e4c2..ece3f16 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,4 +41,25 @@ var authenticator = new TwoStepsAuthenticator.TimeAuthenticator();
bool isok = authenticator.CheckCode(secret, code);
</code></pre>
+<<<<<<< HEAD
+### 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>
+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>
+=======
Every time-based code should only be used once. A built-in mechanism ensures that. If you want to control this check by your self you can pass in an instance of IUsedCodesManager.
+>>>>>>> 57f740845da867fc270d9b185508bc5761aa7d45
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 bc2f364..99d1957 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);
@@ -54,18 +59,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 7b39650..d72474c 100644
--- a/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
+++ b/TwoStepsAuthenticator.UnitTests/UsedCodesManagerTests.cs
@@ -20,18 +20,4 @@ namespace TwoStepsAuthenticator.UnitTests {
}
}
-
- internal class MockUsedCodesManager : IUsedCodesManager {
- public long? LastChallenge { get; private set; }
- public string LastCode { get; private set; }
-
- public void AddCode(long challenge, string code) {
- this.LastChallenge = challenge;
- this.LastCode = code;
- }
-
- public bool IsCodeUsed(long 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 ae67ee5..56f1319 100644
--- a/TwoStepsAuthenticator/CounterAuthenticator.cs
+++ b/TwoStepsAuthenticator/CounterAuthenticator.cs
@@ -4,12 +4,14 @@ 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;
public CounterAuthenticator(int windowSize = 10) {
@@ -26,7 +28,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);
}
@@ -37,7 +40,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);
@@ -51,11 +55,13 @@ 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)) {
codeMatch = true;
diff --git a/TwoStepsAuthenticator/TimeAuthenticator.cs b/TwoStepsAuthenticator/TimeAuthenticator.cs
index 572d736..8c42e41 100644
--- a/TwoStepsAuthenticator/TimeAuthenticator.cs
+++ b/TwoStepsAuthenticator/TimeAuthenticator.cs
@@ -16,7 +16,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) ? DefaultUsedCodeManager.Value : usedCodeManager;
this.IntervalSeconds = intervalSeconds;