Skip to content

Commit eb78321

Browse files
author
Kyle Spearrin
committed
initial port of otpsharp
1 parent 354040e commit eb78321

19 files changed

+1089
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,6 @@ paket-files/
250250
# JetBrains Rider
251251
.idea/
252252
*.sln.iml
253+
254+
.nuget/
255+
node_modules/

LICENSE LICENSE.txt

File renamed without changes.

Otp.NET.nuspec

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="utf-8"?><package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
2+
<metadata>
3+
<id>Otp.NET</id>
4+
<version>1.0.0</version>
5+
<authors>Kyle Spearrin</authors>
6+
<projectUrl>https://github.com/kspearrin/Otp.NET</projectUrl>
7+
<licenseUrl>https://raw.githubusercontent.com/kspearrin/Otp.NET/master/LICENSE.txt</licenseUrl>
8+
<title>Otp.NET</title>
9+
<description>An implementation of TOTP which is commonly used for multi factor authentication by using a shared key between the client and the server to generate and verify one time use codes. For documentation, visit https://github.com/kspearrin/BerTlv.NET</description>
10+
<tags>otp totp 2fa</tags>
11+
</metadata>
12+
<files>
13+
<file src="src\Otp.NET\bin\Release\net45\Otp.NET.dll" target="lib\net45"/>
14+
<file src="src\Otp.NET\bin\Release\netstandard1.3\Otp.NET.dll" target="lib\netstandard1.3"/>
15+
</files>
16+
</package>

Otp.NET.sln

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 14
4+
VisualStudioVersion = 14.0.25420.1
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{57F82DBE-510A-4E78-ADCD-7A18DB80AA87}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E5579496-CD66-4961-8DD1-A53BA74229E3}"
9+
ProjectSection(SolutionItems) = preProject
10+
global.json = global.json
11+
EndProjectSection
12+
EndProject
13+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Otp.NET", "src\Otp.NET\Otp.NET.xproj", "{E630B67F-150A-4978-A2DD-51B8D8E783EF}"
14+
EndProject
15+
Global
16+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17+
Debug|Any CPU = Debug|Any CPU
18+
Release|Any CPU = Release|Any CPU
19+
EndGlobalSection
20+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
21+
{E630B67F-150A-4978-A2DD-51B8D8E783EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22+
{E630B67F-150A-4978-A2DD-51B8D8E783EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
23+
{E630B67F-150A-4978-A2DD-51B8D8E783EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
24+
{E630B67F-150A-4978-A2DD-51B8D8E783EF}.Release|Any CPU.Build.0 = Release|Any CPU
25+
EndGlobalSection
26+
GlobalSection(SolutionProperties) = preSolution
27+
HideSolutionNode = FALSE
28+
EndGlobalSection
29+
GlobalSection(NestedProjects) = preSolution
30+
{E630B67F-150A-4978-A2DD-51B8D8E783EF} = {57F82DBE-510A-4E78-ADCD-7A18DB80AA87}
31+
EndGlobalSection
32+
EndGlobal

build.cmd

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@echo off
2+
cd %~dp0
3+
4+
SETLOCAL
5+
SET CACHED_NUGET=%LocalAppData%\NuGet\NuGet.exe
6+
7+
IF EXIST %CACHED_NUGET% goto copynuget
8+
IF NOT EXIST %LocalAppData%\NuGet md %LocalAppData%\NuGet
9+
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://www.nuget.org/nuget.exe' -OutFile '%CACHED_NUGET%'"
10+
11+
:copynuget
12+
IF EXIST .nuget\nuget.exe goto build
13+
md .nuget
14+
copy %CACHED_NUGET% .nuget\nuget.exe > nul
15+
16+
:build
17+
call npm install -g gulp
18+
call npm install
19+
call gulp

global.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"projects": [ "src", "test" ],
3+
"sdk": {
4+
"version": "1.0.0-preview2-003131"
5+
}
6+
}

gulpfile.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
var p = require('./package.json'),
2+
gulp = require('gulp'),
3+
assemblyInfo = require('gulp-dotnet-assembly-info'),
4+
xmlpoke = require('gulp-xmlpoke'),
5+
msbuild = require('gulp-msbuild'),
6+
nuget = require('nuget-runner')({
7+
apiKey: process.env.NUGET_API_KEY,
8+
nugetPath: '.nuget/nuget.exe'
9+
});
10+
11+
gulp.task('default', ['nuget']);
12+
13+
gulp.task('restore', [], function () {
14+
return nuget
15+
.restore({
16+
packages: 'Otp.NET.sln',
17+
verbosity: 'normal'
18+
});
19+
});
20+
21+
gulp.task('build', ['restore'], function () {
22+
return gulp
23+
.src('Otp.NET.sln')
24+
.pipe(msbuild({
25+
toolsVersion: 14.0,
26+
targets: ['Clean', 'Build'],
27+
errorOnFail: true,
28+
configuration: 'Release'
29+
}));
30+
});
31+
32+
gulp.task('nuspec', ['build'], function () {
33+
return gulp
34+
.src('Otp.NET.nuspec')
35+
.pipe(xmlpoke({
36+
replacements: [{
37+
xpath: "//package:version",
38+
namespaces: { "package": "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd" },
39+
value: p.version
40+
}]
41+
}))
42+
.pipe(gulp.dest('.'));
43+
});
44+
45+
gulp.task('nuget', ['nuspec'], function () {
46+
return nuget
47+
.pack({
48+
spec: 'Otp.NET.nuspec',
49+
outputDirectory: 'src/Otp.NET/bin/Release',
50+
version: p.version
51+
});
52+
});

package.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "otpnet",
3+
"version": "1.0.0",
4+
"description": "An implementation of TOTP which is commonly used for multi factor authentication by using a shared key between the client and the server to generate and verify one time use codes.",
5+
"homepage": "https://github.com/kspearrin/Otp.NET",
6+
"author": "Kyle Spearrin",
7+
"license": "MIT",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/kspearrin/Otp.NET"
11+
},
12+
"dependencies": {
13+
"gulp": "^3.9.0",
14+
"gulp-dotnet-assembly-info": "^0.1.10",
15+
"gulp-msbuild": "^0.2.11",
16+
"gulp-xmlpoke": "^0.2.0",
17+
"nuget-runner": "^0.1.5"
18+
}
19+
}

src/Otp.NET/Base32Encoding.cs

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
Credits to "Shane" from SO answer here:
3+
http://stackoverflow.com/a/7135008/1090359
4+
*/
5+
6+
using System;
7+
8+
namespace OtpNet
9+
{
10+
public class Base32Encoding
11+
{
12+
public static byte[] ToBytes(string input)
13+
{
14+
if(string.IsNullOrEmpty(input))
15+
{
16+
throw new ArgumentNullException("input");
17+
}
18+
19+
input = input.TrimEnd('='); //remove padding characters
20+
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
21+
byte[] returnArray = new byte[byteCount];
22+
23+
byte curByte = 0, bitsRemaining = 8;
24+
int mask = 0, arrayIndex = 0;
25+
26+
foreach(char c in input)
27+
{
28+
int cValue = CharToValue(c);
29+
30+
if(bitsRemaining > 5)
31+
{
32+
mask = cValue << (bitsRemaining - 5);
33+
curByte = (byte)(curByte | mask);
34+
bitsRemaining -= 5;
35+
}
36+
else
37+
{
38+
mask = cValue >> (5 - bitsRemaining);
39+
curByte = (byte)(curByte | mask);
40+
returnArray[arrayIndex++] = curByte;
41+
curByte = (byte)(cValue << (3 + bitsRemaining));
42+
bitsRemaining += 3;
43+
}
44+
}
45+
46+
//if we didn't end with a full byte
47+
if(arrayIndex != byteCount)
48+
{
49+
returnArray[arrayIndex] = curByte;
50+
}
51+
52+
return returnArray;
53+
}
54+
55+
public static string ToString(byte[] input)
56+
{
57+
if(input == null || input.Length == 0)
58+
{
59+
throw new ArgumentNullException("input");
60+
}
61+
62+
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
63+
char[] returnArray = new char[charCount];
64+
65+
byte nextChar = 0, bitsRemaining = 5;
66+
int arrayIndex = 0;
67+
68+
foreach(byte b in input)
69+
{
70+
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
71+
returnArray[arrayIndex++] = ValueToChar(nextChar);
72+
73+
if(bitsRemaining < 4)
74+
{
75+
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
76+
returnArray[arrayIndex++] = ValueToChar(nextChar);
77+
bitsRemaining += 5;
78+
}
79+
80+
bitsRemaining -= 3;
81+
nextChar = (byte)((b << bitsRemaining) & 31);
82+
}
83+
84+
//if we didn't end with a full char
85+
if(arrayIndex != charCount)
86+
{
87+
returnArray[arrayIndex++] = ValueToChar(nextChar);
88+
while(arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
89+
}
90+
91+
return new string(returnArray);
92+
}
93+
94+
private static int CharToValue(char c)
95+
{
96+
int value = (int)c;
97+
98+
//65-90 == uppercase letters
99+
if(value < 91 && value > 64)
100+
{
101+
return value - 65;
102+
}
103+
//50-55 == numbers 2-7
104+
if(value < 56 && value > 49)
105+
{
106+
return value - 24;
107+
}
108+
//97-122 == lowercase letters
109+
if(value < 123 && value > 96)
110+
{
111+
return value - 97;
112+
}
113+
114+
throw new ArgumentException("Character is not a Base32 character.", "c");
115+
}
116+
117+
private static char ValueToChar(byte b)
118+
{
119+
if(b < 26)
120+
{
121+
return (char)(b + 65);
122+
}
123+
124+
if(b < 32)
125+
{
126+
return (char)(b + 24);
127+
}
128+
129+
throw new ArgumentException("Byte is not a value Base32 value.", "b");
130+
}
131+
132+
}
133+
}

src/Otp.NET/KeyGeneration.cs

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Credits to Devin Martin and the original OtpSharp library:
3+
https://bitbucket.org/devinmartin/otp-sharp/overview
4+
5+
Copyright (C) 2012 Devin Martin
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a
8+
copy of this software and associated documentation files (the "Software"),
9+
to deal in the Software without restriction, including without limitation
10+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
11+
and/or sell copies of the Software, and to permit persons to whom the
12+
Software is furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included
15+
in all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23+
DEALINGS IN THE SOFTWARE.
24+
*/
25+
26+
namespace OtpNet
27+
{
28+
/// <summary>
29+
/// Helpers to work with keys
30+
/// </summary>
31+
public static class KeyGeneration
32+
{
33+
/// <summary>
34+
/// Generates a random key in accordance with the RFC recommened length for each algorithm
35+
/// </summary>
36+
/// <param name="length">Key length</param>
37+
/// <returns>The generated key</returns>
38+
public static byte[] GenerateRandomKey(int length)
39+
{
40+
byte[] key = new byte[length];
41+
using(var rnd = System.Security.Cryptography.RandomNumberGenerator.Create())
42+
{
43+
rnd.GetBytes(key);
44+
return key;
45+
}
46+
}
47+
48+
/// <summary>
49+
/// Generates a random key in accordance with the RFC recommened length for each algorithm
50+
/// </summary>
51+
/// <param name="mode">HashMode</param>
52+
/// <returns>Key</returns>
53+
public static byte[] GenerateRandomKey(OtpHashMode mode = OtpHashMode.Sha1)
54+
{
55+
return GenerateRandomKey(LengthForMode(mode));
56+
}
57+
58+
private static int LengthForMode(OtpHashMode mode)
59+
{
60+
switch(mode)
61+
{
62+
case OtpHashMode.Sha256:
63+
return 32;
64+
case OtpHashMode.Sha512:
65+
return 64;
66+
default: //case OtpHashMode.Sha1:
67+
return 20;
68+
}
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)