Intial commit

master
Nathan Adams 2013-01-24 22:15:56 -06:00
commit 33a1762896
10 changed files with 689 additions and 0 deletions

37
otpnet/LICENSE.txt 100644
View File

@ -0,0 +1,37 @@
OTPNet is licensed under the terms of Apache 2.0
Copyright [2012] [Nathan Adams]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This project was based off of OTPHP (https://github.com/lelag/otphp) by Le Lag
Copyright (c) 2011 Le Lag
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OTPNet
{
// http://stackoverflow.com/a/7135008/195722
public class Base32Encoding
{
public static byte[] ToBytes(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException("input");
}
input = input.TrimEnd('='); //remove padding characters
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
byte[] returnArray = new byte[byteCount];
byte curByte = 0, bitsRemaining = 8;
int mask = 0, arrayIndex = 0;
foreach (char c in input)
{
int cValue = CharToValue(c);
if (bitsRemaining > 5)
{
mask = cValue << (bitsRemaining - 5);
curByte = (byte)(curByte | mask);
bitsRemaining -= 5;
}
else
{
mask = cValue >> (5 - bitsRemaining);
curByte = (byte)(curByte | mask);
returnArray[arrayIndex++] = curByte;
curByte = (byte)(cValue << (3 + bitsRemaining));
bitsRemaining += 3;
}
}
//if we didn't end with a full byte
if (arrayIndex != byteCount)
{
returnArray[arrayIndex] = curByte;
}
return returnArray;
}
public static string ToString(byte[] input)
{
if (input == null || input.Length == 0)
{
throw new ArgumentNullException("input");
}
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
char[] returnArray = new char[charCount];
byte nextChar = 0, bitsRemaining = 5;
int arrayIndex = 0;
foreach (byte b in input)
{
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
returnArray[arrayIndex++] = ValueToChar(nextChar);
if (bitsRemaining < 4)
{
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
returnArray[arrayIndex++] = ValueToChar(nextChar);
bitsRemaining += 5;
}
bitsRemaining -= 3;
nextChar = (byte)((b << bitsRemaining) & 31);
}
//if we didn't end with a full char
if (arrayIndex != charCount)
{
returnArray[arrayIndex++] = ValueToChar(nextChar);
while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
}
return new string(returnArray);
}
private static int CharToValue(char c)
{
int value = (int)c;
//65-90 == uppercase letters
if (value < 91 && value > 64)
{
return value - 65;
}
//50-55 == numbers 2-7
if (value < 56 && value > 49)
{
return value - 24;
}
//97-122 == lowercase letters
if (value < 123 && value > 96)
{
return value - 97;
}
throw new ArgumentException("Character is not a Base32 character.", "c");
}
private static char ValueToChar(byte b)
{
if (b < 26)
{
return (char)(b + 65);
}
if (b < 32)
{
return (char)(b + 24);
}
throw new ArgumentException("Byte is not a value Base32 value.", "b");
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OTPNet
{
public static class StringExtensions
{
/// <summary>Returns a string array that contains the substrings in this string that are seperated a given fixed length.</summary>
/// <param name="s">This string object.</param>
/// <param name="length">Size of each substring.
/// <para>CASE: length &gt; 0 , RESULT: String is split from left to right.</para>
/// <para>CASE: length == 0 , RESULT: String is returned as the only entry in the array.</para>
/// <para>CASE: length &lt; 0 , RESULT: String is split from right to left.</para>
/// </param>
/// <returns>String array that has been split into substrings of equal length.</returns>
/// <example>
/// <code>
/// string s = "1234567890";
/// string[] a = s.Split(4); // a == { "1234", "5678", "90" }
/// </code>
/// </example>
public static string[] Split(this string s, int length)
{
System.Globalization.StringInfo str = new System.Globalization.StringInfo(s);
int lengthAbs = Math.Abs(length);
if (str == null || str.LengthInTextElements == 0 || lengthAbs == 0 || str.LengthInTextElements <= lengthAbs)
return new string[] { str.ToString() };
string[] array = new string[(str.LengthInTextElements % lengthAbs == 0 ? str.LengthInTextElements / lengthAbs : (str.LengthInTextElements / lengthAbs) + 1)];
if (length > 0)
for (int iStr = 0, iArray = 0; iStr < str.LengthInTextElements && iArray < array.Length; iStr += lengthAbs, iArray++)
array[iArray] = str.SubstringByTextElements(iStr, (str.LengthInTextElements - iStr < lengthAbs ? str.LengthInTextElements - iStr : lengthAbs));
else // if (length < 0)
for (int iStr = str.LengthInTextElements - 1, iArray = array.Length - 1; iStr >= 0 && iArray >= 0; iStr -= lengthAbs, iArray--)
array[iArray] = str.SubstringByTextElements((iStr - lengthAbs < 0 ? 0 : iStr - lengthAbs + 1), (iStr - lengthAbs < 0 ? iStr + 1 : lengthAbs));
return array;
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OTPNet
{
class Unixtime
{
private DateTime date;
public Unixtime()
{
this.date = DateTime.Now;
}
public Unixtime(int timestamp)
{
DateTime converted = new DateTime(1970, 1, 1, 0, 0, 0, 0);
DateTime newDateTime = converted.AddSeconds(timestamp);
this.date = newDateTime.ToLocalTime();
}
public DateTime toDateTime()
{
return this.date;
}
public long toTimeStamp()
{
TimeSpan span = (this.date - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).ToLocalTime());
return Convert.ToInt64(span.TotalSeconds);
}
}
}

58
otpnet/OTP/HOTP.cs 100644
View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OTPNet
{
/**
* HOTP - One time password generator
*
* The HOTP class allow for the generation
* and verification of one-time password using
* the HOTP specified algorithm.
*
* This class is meant to be compatible with
* Google Authenticator
*
* This class was originally ported from the rotp
* ruby library available at https://github.com/mdp/rotp
*/
public class HOTP : OTP
{
public HOTP(string secret)
: base(secret, 6, HashAlgorithm.SHA1)
{
}
/**
* Get the password for a specific counter value
* @param integer $count the counter which is used to
* seed the hmac hash function.
* @return integer the One Time Password
*/
public int at(int count)
{
return this.generateOTP(count);
}
/**
* Verify if a password is valid for a specific counter value
*
* @param integer $otp the one-time password
* @param integer $counter the counter value
* @return bool true if the counter is valid, false otherwise
*/
public bool verify(int otp, int counter)
{
return (otp == this.at(counter));
}
public string provisioning_uri(string name, int initial_count)
{
return "otpauth://hotp/" + name + "?secret=" + this.secret + " &counter=" + initial_count.ToString();
}
}
}

147
otpnet/OTP/OTP.cs 100644
View File

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace OTPNet
{
// Please see LICENSE.txt for information on what license this is released under
public enum HashAlgorithm
{
SHA1
}
// This class was ported from this PHP library https://github.com/lelag/otphp which was ported from a Ruby library
/**
* One Time Password Generator
*
* The OTP class allow the generation of one-time
* password that is described in rfc 4xxx.
*
* This is class is meant to be compatible with
* Google Authenticator.
*
* This class was originally ported from the rotp
* ruby library available at https://github.com/mdp/rotp
*/
public class OTP
{
/**
* The base32 encoded secret key
* @var string
*/
public string secret;
/**
* The algorithm used for the hmac hash function
* @var string
*/
public HashAlgorithm digest;
/**
* The number of digits in the one-time password
* @var integer
*/
public int digits;
/**
* Constructor for the OTP class
* @param string $secret the secret key
* @param array $opt options array can contain the
* following keys :
* @param integer digits : the number of digits in the one time password
* Currently Google Authenticator only support 6. Defaults to 6.
* @param string digest : the algorithm used for the hmac hash function
* Google Authenticator only support sha1. Defaults to sha1
*
* @return new OTP class.
*/
public OTP(string secret)
{
this.secret = secret;
this.digits = 6;
this.digest = HashAlgorithm.SHA1;
}
public OTP(string secret, int digits)
{
this.digits = digits;
this.digest = HashAlgorithm.SHA1;
}
public OTP(string secret, int digits, HashAlgorithm digest)
{
this.secret = secret;
this.digits = digits;
this.digest = digest;
}
/**
* Generate a one-time password
*
* @param integer $input : number used to seed the hmac hash function.
* This number is usually a counter (HOTP) or calculated based on the current
* timestamp (see TOTP class).
* @return integer the one-time password
*/
public int generateOTP(Int64 input)
{
HMAC hashgenerator = null;
switch (digest)
{
case HashAlgorithm.SHA1:
hashgenerator = new HMACSHA1(Base32Encoding.ToBytes(secret));
break;
}
hashgenerator.ComputeHash(intToByteString(input));
string hash = "";
foreach(byte b in hashgenerator.Hash)
{
hash += b.ToString("x2");
}
List<int> hmac = new List<int>();
foreach (string s in hash.Split(2))
{
hmac.Add(Int32.Parse(s, System.Globalization.NumberStyles.HexNumber));
}
int offset = hmac[19] & 0xf;
int code = (hmac[offset + 0] & 0x7F) << 24 |
(hmac[offset + 1] & 0xFF) << 16 |
(hmac[offset + 2] & 0xFF) << 8 |
(hmac[offset + 3] & 0xFF);
return code % (int)Math.Pow((double)10, (double)this.digits);
}
public byte[] intToByteString(Int64 i)
{
List<byte> res = new List<byte>();
while (i != 0)
{
res.Add((byte)(i & 0xFF));
i >>= 8;
}
int rcount = res.Count;
for (int z = 0; z < 8 - rcount; z++)
res.Add((byte)0);
res.Reverse();
return res.ToArray();
}
}
}

120
otpnet/OTP/TOTP.cs 100644
View File

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OTPNet
{
/**
* TOTP - One time password generator
*
* The TOTP class allow for the generation
* and verification of one-time password using
* the TOTP specified algorithm.
*
* This class is meant to be compatible with
* Google Authenticator
*
* This class was originally ported from the rotp
* ruby library available at https://github.com/mdp/rotp
*/
public class TOTP : OTP
{
/**
* The interval in seconds for a one-time password timeframe
* Defaults to 30
* @var integer
*/
public double interval;
public TOTP(string secret)
: base(secret, 6, HashAlgorithm.SHA1)
{
this.interval = 30;
}
public TOTP(string secret, double interval)
: base(secret, 6, HashAlgorithm.SHA1)
{
this.interval = interval;
}
public TOTP(string secret, double interval, int digits)
: base(secret, digits, HashAlgorithm.SHA1)
{
this.interval = interval;
}
public TOTP(string secret, double interval, int digits, HashAlgorithm algo)
: base(secret, digits, algo)
{
this.interval = interval;
}
/**
* Get the password for a specific timestamp value
*
* @param integer $timestamp the timestamp which is timecoded and
* used to seed the hmac hash function.
* @return integer the One Time Password
*/
public int at(double timestamp)
{
return this.generateOTP(this.timecode(timestamp));
}
/**
* Get the password for the current timestamp value
*
* @return integer the current One Time Password
*/
public int now()
{
return this.at(new Unixtime().toTimeStamp());
}
/**
* Verify if a password is valid for a specific counter value
*
* @param integer $otp the one-time password
* @param integer $timestamp the timestamp for the a given time, defaults to current time.
* @return bool true if the counter is valid, false otherwise
*/
public bool verify(int otp, double timestamp)
{
return (otp == this.at(timestamp));
}
public bool verify(int otp)
{
//calls verify(int, int)
return this.verify(otp, new Unixtime().toTimeStamp());
}
/**
* Returns the uri for a specific secret for totp method.
* Can be encoded as a image for simple configuration in
* Google Authenticator.
*
* @param string $name the name of the account / profile
* @return string the uri for the hmac secret
*/
public string provisitioning_uri(string name)
{
return "otpauth://totp/" + name + "?secret=" + this.secret;
}
/**
* Transform a timestamp in a counter based on specified internal
*
* @param integer $timestamp
* @return integer the timecode
*/
public Int64 timecode(double timestamp)
{
return (Int64)(((((timestamp * 1000)) / (this.interval * 1000))));
}
}
}

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{F1FD321D-6F2A-4431-8307-6BF959419DBE}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OTPNet</RootNamespace>
<AssemblyName>OTPNet</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Libraries\Base32Encoding.cs" />
<Compile Include="OTP\HOTP.cs" />
<Compile Include="OTP\OTP.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Libraries\StringExtensions.cs" />
<Compile Include="OTP\TOTP.cs" />
<Compile Include="Libraries\Unixtime.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

20
otpnet/OTPNet.sln 100644
View File

@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OTPNet", "OTPNet.csproj", "{F1FD321D-6F2A-4431-8307-6BF959419DBE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F1FD321D-6F2A-4431-8307-6BF959419DBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1FD321D-6F2A-4431-8307-6BF959419DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1FD321D-6F2A-4431-8307-6BF959419DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1FD321D-6F2A-4431-8307-6BF959419DBE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OTPNet")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OTPNet")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ff624b9a-844a-4ede-9cdd-87025ddd4201")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]