diff --git a/Socialvoid/Client/HttpClientMessageHandler.cs b/Socialvoid/Client/HttpClientMessageHandler.cs
deleted file mode 100644
index c71bead..0000000
--- a/Socialvoid/Client/HttpClientMessageHandler.cs
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Buffers;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Net;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft;
-using Microsoft.VisualStudio.Threading;
-using Nerdbank.Streams;
-using StreamJsonRpc;
-using StreamJsonRpc.Protocol;
-
-///
-/// A that sends requests and receives responses over HTTP using .
-///
-///
-/// See the spec for JSON-RPC over HTTP here: https://www.jsonrpc.org/historical/json-rpc-over-http.html.
-/// Only the POST method is supported.
-///
-public class HttpClientMessageHandler : IJsonRpcMessageHandler
-{
- #nullable enable
- private static readonly ReadOnlyCollection AllowedContentTypes = new ReadOnlyCollection(new string[]
- {
- "application/json-rpc",
- "application/json",
- "application/jsonrequest",
- });
-
- ///
- /// The Content-Type header to use in requests.
- ///
- private static readonly MediaTypeHeaderValue ContentTypeHeader = new MediaTypeHeaderValue(AllowedContentTypes[0]);
-
- ///
- /// The Accept header to use in requests.
- ///
- private static readonly MediaTypeWithQualityHeaderValue AcceptHeader = new MediaTypeWithQualityHeaderValue(AllowedContentTypes[0]);
-
- private readonly HttpClient httpClient;
- private readonly Uri requestUri;
- private readonly AsyncQueue incomingMessages = new AsyncQueue();
-
- ///
- /// Backing field for the property.
- ///
- private TraceSource traceSource = new TraceSource(nameof(JsonRpc));
-
- ///
- /// Initializes a new instance of the class
- /// with the default .
- ///
- /// The to use for transmitting JSON-RPC requests.
- /// The URI to POST to where the entity will be the JSON-RPC message.
- public HttpClientMessageHandler(HttpClient httpClient, Uri requestUri)
- : this(httpClient, requestUri, new JsonMessageFormatter())
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The to use for transmitting JSON-RPC requests.
- /// The URI to POST to where the entity will be the JSON-RPC message.
- /// The message formatter.
- public HttpClientMessageHandler(HttpClient httpClient, Uri requestUri, IJsonRpcMessageFormatter formatter)
- {
- this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
- this.requestUri = requestUri ?? throw new ArgumentNullException(nameof(requestUri));
- this.Formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
- }
-
- ///
- /// Event IDs raised to our .
- ///
- public enum TraceEvent
- {
- ///
- /// An HTTP response with an error status code was received.
- ///
- HttpErrorStatusCodeReceived,
- }
-
- ///
- /// Gets or sets the used to trace details about the HTTP transport operations.
- ///
- /// The value can never be null.
- /// Thrown by the setter if a null value is provided.
- public TraceSource TraceSource
- {
- get => this.traceSource;
- set
- {
- Requires.NotNull(value, nameof(value));
- this.traceSource = value;
- }
- }
-
- ///
- public bool CanRead => true;
-
- ///
- public bool CanWrite => true;
-
- ///
- public IJsonRpcMessageFormatter Formatter { get; }
-
- ///
- public async ValueTask ReadAsync(CancellationToken cancellationToken)
- {
- var response = await this.incomingMessages.DequeueAsync(cancellationToken).ConfigureAwait(false);
-
- var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
- using (var sequence = new Sequence())
- {
-#if NETCOREAPP2_1
- int bytesRead;
- do
- {
- var memory = sequence.GetMemory(4096);
- bytesRead = await responseStream.ReadAsync(memory, cancellationToken).ConfigureAwait(false);
- sequence.Advance(bytesRead);
- }
- while (bytesRead > 0);
-#else
- var buffer = ArrayPool.Shared.Rent(4096);
- try
- {
- int bytesRead;
- while (true)
- {
- bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
- if (bytesRead == 0)
- {
- break;
- }
-
- var memory = sequence.GetMemory(bytesRead);
- buffer.AsMemory(0, bytesRead).CopyTo(memory);
- sequence.Advance(bytesRead);
- }
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
- }
-#endif
-
- return this.Formatter.Deserialize(sequence);
- }
- }
-
- ///
- public async ValueTask WriteAsync(JsonRpcMessage content, CancellationToken cancellationToken)
- {
- // Cast here because we only support transmitting requests anyway.
- var contentAsRequest = (JsonRpcRequest)content;
-
- // The JSON-RPC over HTTP spec requires that we supply a Content-Length header, so we have to serialize up front
- // in order to measure its length.
- using (var sequence = new Sequence())
- {
- this.Formatter.Serialize(sequence, content);
-
- var requestMessage = new HttpRequestMessage(HttpMethod.Post, this.requestUri);
- requestMessage.Headers.Accept.Add(AcceptHeader);
- requestMessage.Content = new StreamContent(sequence.AsReadOnlySequence.AsStream());
- requestMessage.Content.Headers.ContentType = ContentTypeHeader;
- requestMessage.Content.Headers.ContentLength = sequence.Length;
-
- var response = await this.httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);
- if (response.IsSuccessStatusCode)
- {
- VerifyThrowStatusCode(contentAsRequest.IsResponseExpected ? HttpStatusCode.OK : HttpStatusCode.NoContent, response.StatusCode);
- }
- else
- {
- this.TraceSource.TraceEvent(TraceEventType.Error, (int)TraceEvent.HttpErrorStatusCodeReceived, "Received HTTP {0} {1} response to JSON-RPC request for method \"{2}\".", (int)response.StatusCode, response.StatusCode, contentAsRequest.Method);
- }
-
- // The response is expected to be a success code, or an error code with a content-type that we can deserialize.
- if (response.IsSuccessStatusCode || (response.Content?.Headers.ContentType?.MediaType is string mediaType && AllowedContentTypes.Contains(mediaType)))
- {
- // Some requests don't merit response messages, such as notifications in JSON-RPC.
- // Servers may communicate this with 202 or 204 HTTPS status codes in the response.
- // Others may (poorly?) send a 200 response but with an empty entity.
- if (response.Content?.Headers.ContentLength > 0)
- {
- // Make the response available for receiving.
- this.incomingMessages.Enqueue(response);
- }
- }
- else
- {
- // Throw an exception because of the unexpected failure from the server without a JSON-RPC message attached.
- response.EnsureSuccessStatusCode();
- }
- }
- }
-
- private static void VerifyThrowStatusCode(HttpStatusCode expected, HttpStatusCode actual)
- {
- if (expected != actual)
- {
- throw new BadRpcHeaderException($"Expected \"{(int)expected} {expected}\" response but received \"{(int)actual} {actual}\" instead.");
- }
- }
-}
\ No newline at end of file
diff --git a/Socialvoid/Client/SocialvoidClient.cs b/Socialvoid/Client/SocialvoidClient.cs
index 07a04a8..8adf7b6 100644
--- a/Socialvoid/Client/SocialvoidClient.cs
+++ b/Socialvoid/Client/SocialvoidClient.cs
@@ -1,11 +1,24 @@
-using System;
-using System.Text.Encodings;
-using System.Text;
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+using System;
using System.Net.Http;
-using System.Collections.Generic;
using System.IO;
-using StreamJsonRpc;
-using StreamJsonRpc.Protocol;
using Socialvoid.Security;
using Socialvoid.JObjects;
using Socialvoid.Errors.ServerErrors;
@@ -128,10 +141,6 @@ namespace Socialvoid.Client
//-------------------------------------------------
#region field's Region
///
- /// since: v0.0.0
- ///
- protected JsonMessageFormatter _formatter = new(Encoding.UTF8);
- ///
/// the endpoint url of socialvoid servers.
/// since: v0.0.0
///
@@ -256,7 +265,7 @@ namespace Socialvoid.Client
{PublicHashKey, PublicHash},
{PrivateHashKey, PrivateHash},
{PlatformKey, Platform},
- //{NameKey, ClientName},
+ {NameKey, ClientName},
{VersionKey, Version},
};
@@ -267,8 +276,18 @@ namespace Socialvoid.Client
message.Content = SerializeContent(request);
message.Content.Headers.ContentType = _contentTypeValue;
var jresp = ParseContent(message);
+
+ if (!string.IsNullOrEmpty(jresp.Result.ChallengeSecret))
+ {
+ _should_otp = true;
+ _otp = GetChallengeAnswer(jresp.Result.ChallengeSecret);
+ // set challenege secret to null to avoid sending it again.
+ // this will avoid future conflicts in using old challenge secret.
+ jresp.Result.ChallengeSecret = null;
+ }
+ _session = jresp.Result;
- return null;
+ return _session;
}
///
/// AuthenticateUser method (session.authenticate_user),
@@ -359,6 +378,14 @@ namespace Socialvoid.Client
Console.WriteLine(contentStr);
}
+ ///
+ /// returns a challenge's answer using the session's challenge secret.
+ /// since: v0.0.0
+ ///
+ protected internal virtual string GetChallengeAnswer(string secret)
+ {
+ return null;
+ }
#endregion
//-------------------------------------------------
diff --git a/Socialvoid/Client/SvClient.cs b/Socialvoid/Client/SvClient.cs
index 59a8071..1dbcc6d 100644
--- a/Socialvoid/Client/SvClient.cs
+++ b/Socialvoid/Client/SvClient.cs
@@ -1,4 +1,20 @@
-using System;
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
namespace Socialvoid.Client
{
diff --git a/Socialvoid/Security/Otp/Base32Encoding.cs b/Socialvoid/Security/Otp/Base32Encoding.cs
new file mode 100644
index 0000000..d880eef
--- /dev/null
+++ b/Socialvoid/Security/Otp/Base32Encoding.cs
@@ -0,0 +1,184 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+/*
+ * Credits to "Shane" from SO answer here:
+ * http://stackoverflow.com/a/7135008/1090359
+ */
+
+using System;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Base32 encoding/decoding helper class.
+ /// since: v0.0.0
+ ///
+ public static class Base32Encoding
+ {
+ //-------------------------------------------------
+ #region static Method's Region
+ ///
+ /// Converts a string to a byte array using the specified encoding.
+ /// since: v0.0.0
+ ///
+ ///
+ ///
+ 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;
+ }
+ ///
+ /// Converts an array of byte to a Base32-encoded string.
+ /// since: v0.0.0
+ ///
+ ///
+ ///
+ 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);
+ }
+ ///
+ /// Converts a valid base32 character to it's corresponding value.
+ /// since: v0.0.0
+ ///
+ ///
+ private static int CharToValue(char c)
+ {
+ // 65 - 90 == uppercase letters
+ if(c < 91 && c > 64)
+ {
+ return c - 65;
+ }
+
+ // 50 - 55 == numbers 2-7
+ if(c < 56 && c > 49)
+ {
+ return c - 24;
+ }
+
+ // 97 - 122 == lowercase letters
+ if(c < 123 && c > 96)
+ {
+ return c - 97;
+ }
+
+ // isn't in any of these chars range?
+ throw new ArgumentException(
+ "Character is not a valid Base32 character.",
+ nameof(c));
+ }
+ ///
+ /// Converts a valid base32 byte value to its corresponding char.
+ /// since: v0.0.0
+ ///
+ ///
+ 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 Base32 value", nameof(b));
+ }
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/Hotp.cs b/Socialvoid/Security/Otp/Hotp.cs
new file mode 100644
index 0000000..282b50a
--- /dev/null
+++ b/Socialvoid/Security/Otp/Hotp.cs
@@ -0,0 +1,153 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+using System;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Calculate HMAC-Based One-Time-Passwords (HOTP) from a secret key.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The specifications for the methods of this class can be found in RFC 4226:
+ /// http://tools.ietf.org/html/rfc4226
+ ///
+ public sealed class Hotp : Otp
+ {
+ //-------------------------------------------------
+ #region field's Region
+ ///
+ /// The HOTP size.
+ /// since: v0.0.0
+ ///
+ private readonly int _hotpSize;
+ #endregion
+ //-------------------------------------------------
+ #region Constructor's Region
+ ///
+ /// Creates an HOTP instance.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The secret key to use in HOTP calculations.
+ ///
+ ///
+ /// The hash mode to use.
+ ///
+ /// The number of digits that the returning HOTP should have. The default is 6.
+ public Hotp(byte[] secretKey, OtpHashMode mode = OtpHashMode.Sha1, int hotpSize = 6)
+ : base(secretKey, mode)
+ {
+ if(hotpSize < 6 || hotpSize > 8)
+ {
+ throw new ArgumentOutOfRangeException(nameof(hotpSize),
+ "The hotpSize must be between 6 and 8");
+ }
+
+ _hotpSize = hotpSize;
+ }
+ ///
+ /// Create a HOTP instance.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The key to use in HOTP calculations.
+ ///
+ ///
+ /// The hash mode to use.
+ ///
+ ///
+ /// The number of digits that the returning HOTP should have.
+ /// The default value is 6.
+ ///
+ public Hotp(IKeyProvider key,
+ OtpHashMode mode = OtpHashMode.Sha1,
+ int hotpSize = 6)
+ : base(key, mode)
+ {
+
+ if(hotpSize < 6 || hotpSize > 8)
+ {
+ throw new ArgumentOutOfRangeException(nameof(hotpSize),
+ "The hotpSize must be between 6 and 8");
+ }
+
+ _hotpSize = hotpSize;
+ }
+ #endregion
+ //-------------------------------------------------
+ #region overrided Method's Region
+ ///
+ /// Takes a time step and computes a HOTP code.
+ /// since: v0.0.0
+ ///
+ ///
+ /// the counter. This is the number of time steps that have passed.
+ ///
+ ///
+ /// The hash mode to use.
+ ///
+ ///
+ /// HOTP calculated code.
+ ///
+ protected override string Compute(long counter, OtpHashMode mode)
+ {
+ var data = KeyUtilities.GetBigEndianBytes(counter);
+ var otp = this.CalculateOtp(data, mode);
+ return Digits(otp, _hotpSize);
+ }
+ #endregion
+ //-------------------------------------------------
+ #region Get Method's Region
+ ///
+ /// Takes a counter and then computes a HOTP value.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The timestamp to use for the HOTP calculation.
+ ///
+ /// a HOTP value
+ public string ComputeHOTP(long counter)
+ {
+ return this.Compute(counter, _hashMode);
+ }
+ ///
+ /// Verify a value that has been provided with the calculated value.
+ /// since: v0.0.0
+ ///
+ /// the trial HOTP value.
+ ///
+ ///
+ /// The counter value to verify
+ ///
+ ///
+ /// true if there is a match; otherwise false.
+ ///
+ public bool VerifyHotp(string hotp, long counter) =>
+ hotp == ComputeHOTP(counter);
+
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/IKeyProvider.cs b/Socialvoid/Security/Otp/IKeyProvider.cs
new file mode 100644
index 0000000..89c580c
--- /dev/null
+++ b/Socialvoid/Security/Otp/IKeyProvider.cs
@@ -0,0 +1,57 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Interface used to interact with a key.
+ /// since: v0.0.0
+ ///
+ public interface IKeyProvider
+ {
+ //-------------------------------------------------
+ #region Get Method's Region
+ ///
+ /// Uses the key to get an HMAC using the specified algorithm and data.
+ /// since: v0.0.0
+ ///
+ ///
+ /// This is a much better API than the previous API which would briefly
+ /// expose the key for all derived types.
+ ///
+ /// Now a derived type could be bound to an HSM/smart card/etc if
+ /// required and a lot of the security limitations of in app/memory
+ /// exposure of the key can be eliminated.
+ ///
+ ///
+ /// The HMAC algorithm to use.
+ ///
+ ///
+ /// The data used to compute the HMAC.
+ ///
+ /// HMAC of the key and data
+ byte[] ComputeHmac(OtpHashMode mode, byte[] data);
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/InMemoryKey.cs b/Socialvoid/Security/Otp/InMemoryKey.cs
new file mode 100644
index 0000000..9ed378d
--- /dev/null
+++ b/Socialvoid/Security/Otp/InMemoryKey.cs
@@ -0,0 +1,167 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+using System;
+using System.Security.Cryptography;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Represents a key in memory.
+ /// since: v0.0.0
+ ///
+ ///
+ /// This will attempt to use the Windows data protection api to
+ /// encrypt the key in memory.
+ /// However, this type favors working over memory protection.
+ /// This is an attempt to minimize exposure in memory, nothing more.
+ /// This protection is flawed in many ways and is limited to Windows.
+ ///
+ /// In order to use the key to compute an hmac it must be temporarily
+ /// decrypted, used, then re-encrypted.
+ /// This does expose the key in memory for a time.
+ /// If a memory dump occurs in this time the plaintext key will be part
+ /// of it. Furthermore, there are potentially artifacts from the hmac
+ /// computation, GC compaction, or any number of other leaks even after
+ /// the key is re-encrypted.
+ ///
+ /// This type favors working over memory protection. If the particular
+ /// platform isn't supported then, unless forced by modifying the
+ /// IsPlatformSupported method, it will just store the key in a standard
+ /// byte array.
+ ///
+ public class InMemoryKey : IKeyProvider
+ {
+ //-------------------------------------------------
+ #region field's Region
+ ///
+ /// The key data in memory.
+ /// since: v0.0.0
+ ///
+ internal readonly byte[] _KeyData;
+
+ ///
+ /// The key length representing the length of the .
+ /// since: v0.0.0
+ ///
+ internal readonly int _keyLength;
+ ///
+ /// Used for locking.
+ /// since: v0.0.0
+ ///
+ private readonly object _stateSync = new();
+ #endregion
+ //-------------------------------------------------
+ #region Constructor's Region
+ ///
+ /// Creates an instance of a key.
+ ///
+ /// Plaintext key data
+ public InMemoryKey(byte[] key)
+ {
+ if(!(key != null))
+ throw new ArgumentNullException("key");
+ if(!(key.Length > 0))
+ throw new ArgumentException("The key must not be empty");
+
+ _keyLength = key.Length;
+ int paddedKeyLength = (int)Math.Ceiling((decimal)key.Length / (decimal)16) * 16;
+ _KeyData = new byte[paddedKeyLength];
+ Array.Copy(key, _KeyData, key.Length);
+ }
+ #endregion
+ //-------------------------------------------------
+ #region Get Method's Region
+ ///
+ /// Gets a copy of the plaintext key.
+ /// since: v0.0.0
+ ///
+ ///
+ /// This is internal rather than protected so that the tests can
+ /// use this method.
+ ///
+ ///
+ /// Plaintext Key
+ ///
+ internal byte[] GetCopyOfKey()
+ {
+ var plainKey = new byte[_keyLength];
+ lock(_stateSync)
+ {
+ Array.Copy(_KeyData, plainKey, _keyLength);
+ }
+ return plainKey;
+ }
+ ///
+ /// Uses the key to get an HMAC using the specified algorithm and data.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The HMAC algorithm to use
+ ///
+ ///
+ /// The data used to compute the HMAC
+ ///
+ ///
+ /// HMAC of the key and data
+ ///
+ public byte[] ComputeHmac(OtpHashMode mode, byte[] data)
+ {
+ byte[] hashedValue = null;
+ using(HMAC hmac = CreateHmacHash(mode))
+ {
+ byte[] key = this.GetCopyOfKey();
+ try
+ {
+ hmac.Key = key;
+ hashedValue = hmac.ComputeHash(data);
+ }
+ finally
+ {
+ KeyUtilities.Destroy(key);
+ }
+ }
+
+ return hashedValue;
+ }
+
+ #endregion
+ //-------------------------------------------------
+ #region static Method's Region
+ ///
+ /// Create an HMAC object for the specified algorithm.
+ /// since: v0.0.0
+ ///
+ private static HMAC CreateHmacHash(OtpHashMode otpHashMode)
+ {
+ return otpHashMode switch
+ {
+ OtpHashMode.Sha256 => new HMACSHA256(),
+ OtpHashMode.Sha512 => new HMACSHA512(),
+ _ => new HMACSHA1() //OtpHashMode.Sha1
+ };
+ }
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/KeyGeneration.cs b/Socialvoid/Security/Otp/KeyGeneration.cs
new file mode 100644
index 0000000..3cee5a8
--- /dev/null
+++ b/Socialvoid/Security/Otp/KeyGeneration.cs
@@ -0,0 +1,132 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+
+using System;
+using System.Security.Cryptography;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Helpers to work with key generations.
+ /// since: v0.0.0
+ ///
+ public static class KeyGeneration
+ {
+ //-------------------------------------------------
+ #region static Method's Region
+ ///
+ /// Generates a random key in accordance with the RFC recommened
+ /// length for each algorithm.
+ /// since: v0.0.0
+ ///
+ /// the key length
+ /// The generated key
+ public static byte[] GenerateRandomKey(int length)
+ {
+ byte[] key = new byte[length];
+ using(var rnd = RandomNumberGenerator.Create())
+ {
+ rnd.GetBytes(key);
+ return key;
+ }
+ }
+ ///
+ /// Generates a random key in accordance with the RFC recommened
+ /// length for each algorithm.
+ /// since: v0.0.0
+ ///
+ /// HashMode
+ /// Key
+ public static byte[] GenerateRandomKey(OtpHashMode mode = OtpHashMode.Sha1) =>
+ GenerateRandomKey(LengthForMode(mode));
+ ///
+ /// Uses the procedure defined in RFC 4226 section 7.5 to derive a key
+ /// from the master key.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The master key from which to derive a device specific key.
+ ///
+ ///
+ /// The public identifier that is unique to the authenticating device.
+ ///
+ ///
+ /// The hash mode to use. This will determine the resulting key lenght.
+ /// The default value is sha-1 (as per the RFC) which is 20 bytes
+ ///
+ /// Derived key
+ public static byte[] DeriveKeyFromMaster(IKeyProvider masterKey,
+ byte[] publicIdentifier, OtpHashMode mode = OtpHashMode.Sha1)
+ {
+ if(masterKey == null)
+ {
+ throw new ArgumentNullException(nameof(masterKey),
+ "The master key cannot be null");
+ }
+ return masterKey.ComputeHmac(mode, publicIdentifier);
+ }
+ ///
+ /// Uses the procedure defined in RFC 4226 section 7.5 to derive a key
+ /// from the master key.
+ ///
+ /// The master key from which to derive a device specific key
+ /// A serial number that is unique to the authenticating device
+ /// The hash mode to use. This will determine the resulting key lenght. The default is sha-1 (as per the RFC) which is 20 bytes
+ /// Derived key
+ public static byte[] DeriveKeyFromMaster(IKeyProvider masterKey,
+ int serialNumber,
+ OtpHashMode mode = OtpHashMode.Sha1) =>
+ DeriveKeyFromMaster(masterKey,
+ KeyUtilities.GetBigEndianBytes(serialNumber), mode);
+
+ private static HashAlgorithm GetHashAlgorithmForMode(OtpHashMode mode)
+ {
+ switch(mode)
+ {
+ case OtpHashMode.Sha256:
+ return SHA256.Create();
+ case OtpHashMode.Sha512:
+ return SHA512.Create();
+ default: //case OtpHashMode.Sha1:
+ return SHA1.Create();
+ }
+ }
+
+ private static int LengthForMode(OtpHashMode mode)
+ {
+ switch(mode)
+ {
+ case OtpHashMode.Sha256:
+ return 32;
+ case OtpHashMode.Sha512:
+ return 64;
+ default: //case OtpHashMode.Sha1:
+ return 20;
+ }
+ }
+
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/KeyUtilities.cs b/Socialvoid/Security/Otp/KeyUtilities.cs
new file mode 100644
index 0000000..1c0f2cf
--- /dev/null
+++ b/Socialvoid/Security/Otp/KeyUtilities.cs
@@ -0,0 +1,96 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+
+using System;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Some helper methods to perform common key functions.
+ /// since: v0.0.0
+ ///
+ internal static class KeyUtilities
+ {
+ //-------------------------------------------------
+ #region static Method's Region
+ ///
+ /// Overwrite potentially sensitive data with random junk.
+ /// since: v0.0.0
+ ///
+ ///
+ /// Warning!
+ ///
+ /// This isn't foolproof by any means.
+ /// The garbage collector could have moved the actual location in memory
+ /// to another location during a collection cycle and left the old data
+ /// in place simply marking it as available.
+ /// We can't control this or even detect it.
+ /// This method is simply a good faith effort to limit the exposure of
+ /// sensitive data in memory as much as possible.
+ ///
+ internal static void Destroy(byte[] sensitiveData)
+ {
+ if(sensitiveData == null || sensitiveData.Length == 0)
+ {
+ // if there is no data, there is nothing to destroy;
+ // don't throw an exception, just return.
+ return;
+ }
+
+ new Random().NextBytes(sensitiveData);
+ }
+ ///
+ /// converts a long into a big endian byte array.
+ /// since: v0.0.0
+ ///
+ ///
+ /// RFC 4226 specifies big endian as the method for converting the counter
+ /// to data and then to hash.
+ ///
+ static internal byte[] GetBigEndianBytes(long input)
+ {
+ // Since .net uses little endian numbers, we need to reverse the byte order to get big endian.
+ var data = BitConverter.GetBytes(input);
+ Array.Reverse(data);
+ return data;
+ }
+ ///
+ /// converts an int into a big endian byte array.
+ /// since: v0.0.0
+ ///
+ ///
+ /// RFC 4226 specifies big endian as the method for converting
+ /// the counter to data and then to hash.
+ ///
+ static internal byte[] GetBigEndianBytes(int input)
+ {
+ // Since .net uses little endian numbers, we need to reverse the byte order to get big endian.
+ var data = BitConverter.GetBytes(input);
+ Array.Reverse(data);
+ return data;
+ }
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/Otp.cs b/Socialvoid/Security/Otp/Otp.cs
new file mode 100644
index 0000000..7a1d4c6
--- /dev/null
+++ b/Socialvoid/Security/Otp/Otp.cs
@@ -0,0 +1,194 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+using System;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// An abstract class that contains common OTP calculations.
+ /// since: v0.0.0
+ ///
+ ///
+ /// https://tools.ietf.org/html/rfc4226
+ ///
+ public abstract class Otp
+ {
+ //-------------------------------------------------
+ #region field's Region
+ ///
+ /// the secret key.
+ /// since: v0.0.0
+ ///
+ protected readonly IKeyProvider _secretKey;
+
+ ///
+ /// The hash mode to use.
+ /// since: v0.0.0
+ ///
+ protected readonly OtpHashMode _hashMode;
+ #endregion
+ //-------------------------------------------------
+ #region Constructor's Region
+ ///
+ /// Constructor for the abstract class using an explicit secret key.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The secret key.
+ ///
+ ///
+ /// The hash mode to use.
+ ///
+ public Otp(byte[] secretKey, OtpHashMode mode)
+ {
+ if(secretKey == null || secretKey.Length == 0)
+ {
+ throw new ArgumentNullException(nameof(secretKey),
+ "Secret key cannot be null or empty");
+ }
+
+ // when passing a key into the constructor the caller may depend on
+ // the reference to the key remaining intact.
+ _secretKey = new InMemoryKey(secretKey);
+ _hashMode = mode;
+ }
+ ///
+ /// Constructor for the abstract class using a generic key provider.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The hash mode to use
+ public Otp(IKeyProvider key, OtpHashMode mode)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), "key cannot be null");
+ }
+
+ _hashMode = mode;
+ _secretKey = key;
+ }
+ #endregion
+ //-------------------------------------------------
+ #region Get Method's Region
+ ///
+ /// An abstract definition of a compute method.
+ /// Takes a counter and runs it through the derived algorithm.
+ /// since: v0.0.0
+ ///
+ /// Counter or step
+ /// The hash mode to use
+ /// OTP calculated code
+ protected abstract string Compute(long counter, OtpHashMode mode);
+ ///
+ /// Helper method that calculates OTPs.
+ /// since: v0.0.0
+ ///
+ protected internal long CalculateOtp(byte[] data, OtpHashMode mode)
+ {
+ byte[] hmacComputedHash = _secretKey.ComputeHmac(mode, data);
+
+ // The RFC has a hard coded index 19 in this value.
+ // This is the same thing but also accomodates SHA256 and SHA512
+ // hmacComputedHash[19] => hmacComputedHash[hmacComputedHash.Length - 1]
+
+ int offset = hmacComputedHash[hmacComputedHash.Length - 1] & 0x0F;
+ return (hmacComputedHash[offset] & 0x7f) << 24
+ | (hmacComputedHash[offset + 1] & 0xff) << 16
+ | (hmacComputedHash[offset + 2] & 0xff) << 8
+ | (hmacComputedHash[offset + 3] & 0xff) % 1000000;
+ }
+ ///
+ /// truncates a number down to the specified number of digits.
+ /// since: v0.0.0
+ ///
+ protected internal static string Digits(long input, int digitCount)
+ {
+ var truncatedValue = ((int)input % (int)Math.Pow(10, digitCount));
+ return truncatedValue.ToString().PadLeft(digitCount, '0');
+ }
+ ///
+ /// Verify an OTP value.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The initial step to try.
+ ///
+ ///
+ /// The value to verify
+ ///
+ ///
+ /// Output parameter that provides the step
+ /// where the match was found. If no match was found it will be 0
+ ///
+ ///
+ /// The window to verify.
+ ///
+ ///
+ /// true if a match is found; otherwise false.
+ ///
+ protected bool Verify(long initialStep, string valueToVerify, out long matchedStep, VerificationWindow window)
+ {
+ window ??= new();
+ foreach(var frame in window.ValidationCandidates(initialStep))
+ {
+ var comparisonValue = this.Compute(frame, _hashMode);
+ if(ValuesEqual(comparisonValue, valueToVerify))
+ {
+ matchedStep = frame;
+ return true;
+ }
+ }
+
+ matchedStep = 0;
+ return false;
+ }
+ ///
+ /// Constant time comparison of two values.
+ /// since: v0.0.0
+ ///
+ private bool ValuesEqual(string a, string b)
+ {
+ if (string.IsNullOrWhiteSpace(a) && string.IsNullOrWhiteSpace(b))
+ {
+ return true;
+ }
+
+ if(a.Length != b.Length)
+ {
+ return false;
+ }
+
+ var result = 0;
+ for(int i = 0; i < a.Length; i++)
+ {
+ result |= a[i] ^ b[i];
+ }
+
+ return result == 0;
+ }
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/OtpHashMode.cs b/Socialvoid/Security/Otp/OtpHashMode.cs
new file mode 100644
index 0000000..82ce4e9
--- /dev/null
+++ b/Socialvoid/Security/Otp/OtpHashMode.cs
@@ -0,0 +1,52 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Indicates which HMAC hashing algorithm should be used.
+ /// since: v0.0.0
+ ///
+ public enum OtpHashMode
+ {
+ //-------------------------------------------------
+ #region SHA region
+ ///
+ /// Sha1 is used as the HMAC hashing algorithm.
+ /// since: v0.0.0
+ ///
+ Sha1,
+ ///
+ /// Sha256 is used as the HMAC hashing algorithm.
+ /// since: v0.0.0
+ ///
+ Sha256,
+ ///
+ /// Sha512 is used as the HMAC hashing algorithm.
+ /// since: v0.0.0
+ ///
+ Sha512,
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/TimeCorrection.cs b/Socialvoid/Security/Otp/TimeCorrection.cs
new file mode 100644
index 0000000..af80431
--- /dev/null
+++ b/Socialvoid/Security/Otp/TimeCorrection.cs
@@ -0,0 +1,144 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+using System;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Class to apply a correction factor to the system time.
+ /// since: v0.0.0
+ ///
+ ///
+ /// In cases where the local system time is incorrect it is preferable to simply correct the system time.
+ /// This class is provided to handle cases where it isn't possible for the client, the server, or both, to be on the correct time.
+ ///
+ /// This library provides limited facilities to to ping NIST for a correct network time. This class can be used manually however in cases where a server's time is off
+ /// and the consumer of this library can't control it. In that case create an instance of this class and provide the current server time as the correct time parameter
+ ///
+ /// This class is immutable and therefore threadsafe.
+ ///
+ public class TimeCorrection
+ {
+ //-------------------------------------------------
+ #region Properties Region
+ ///
+ /// Applies the correction factor to the current system UTC time and
+ /// returns a corrected time.
+ /// since: v0.0.0
+ ///
+ public DateTime CorrectedUtcNow
+ {
+ get => GetCorrectedTime(DateTime.UtcNow);
+ }
+ ///
+ /// The timespan that is used to calculate a corrected time.
+ /// since: v0.0.0
+ ///
+ public TimeSpan CorrectionFactor
+ {
+ get => _timeCorrectionFactor;
+ }
+ #endregion
+ //-------------------------------------------------
+ #region static field's Region
+ ///
+ /// An instance that provides no correction factor.
+ /// since: v0.0.0
+ ///
+ public static readonly TimeCorrection UncorrectedInstance = new();
+ #endregion
+ //-------------------------------------------------
+ #region field's Region
+ ///
+ /// The timespan that is used as a correction factor.
+ /// since: v0.0.0
+ ///
+ private readonly TimeSpan _timeCorrectionFactor;
+ #endregion
+ //-------------------------------------------------
+ #region Constructor's Region
+ ///
+ /// Constructor used solely for the static
+ /// field to provide an instance without a correction factor.
+ /// since: v0.0.0
+ ///
+ private TimeCorrection()
+ {
+ _timeCorrectionFactor = TimeSpan.FromSeconds(0);
+ }
+
+ ///
+ /// Creates a corrected time object by providing the known correct
+ /// current UTC time.
+ /// The current system UTC time will be used as the reference.
+ /// since: v0.0.0
+ ///
+ ///
+ /// This overload assumes UTC.
+ /// If a base and reference time other than UTC are required then use the
+ /// other overlaod.
+ ///
+ /// The current correct UTC time
+ public TimeCorrection(DateTime correctUtc)
+ {
+ _timeCorrectionFactor = DateTime.UtcNow - correctUtc;
+ }
+
+ ///
+ /// Creates a corrected time object by providing the known correct current time
+ /// and the current reference time that needs correction.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The current correct time.
+ ///
+ ///
+ /// The current reference time (time that will have the correction factor
+ /// applied in subsequent calls).
+ ///
+ public TimeCorrection(DateTime correctTime, DateTime referenceTime)
+ {
+ _timeCorrectionFactor = referenceTime - correctTime;
+ }
+ #endregion
+ //-------------------------------------------------
+ #region Get Method's Region
+ ///
+ /// Applies the correction factor to the reference time and returns a
+ /// corrected time.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The reference time.
+ ///
+ ///
+ /// The reference time with the correction factor applied.
+ ///
+ public DateTime GetCorrectedTime(DateTime referenceTime) =>
+ referenceTime - _timeCorrectionFactor;
+
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/Totp.cs b/Socialvoid/Security/Otp/Totp.cs
new file mode 100644
index 0000000..0614aa8
--- /dev/null
+++ b/Socialvoid/Security/Otp/Totp.cs
@@ -0,0 +1,326 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+
+using System;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// Calculate Timed-One-Time-Passwords (TOTP) from a secret key.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The specifications for the methods of this class can be found in RFC 6238:
+ /// http://tools.ietf.org/html/rfc6238
+ ///
+ public sealed class Totp : Otp
+ {
+ //-------------------------------------------------
+ #region Constant's Region
+ ///
+ /// The number of ticks as Measured at Midnight Jan 1st 1970.
+ /// since: v0.0.0
+ ///
+ internal const long unixEpochTicks = 621355968000000000L;
+ ///
+ /// A divisor for converting ticks to seconds.
+ /// since: v0.0.0
+ ///
+ internal const long ticksToSeconds = 10000000L;
+ #endregion
+ //-------------------------------------------------
+ #region field's Region
+ ///
+ /// the step value.
+ /// since: v0.0.0
+ ///
+ private readonly int _step;
+ ///
+ /// the TOTP length.
+ /// since: v0.0.0
+ ///
+ private readonly int _totpSize;
+ ///
+ /// the TOTP corrected time.
+ /// since: v0.0.0
+ ///
+ private readonly TimeCorrection correctedTime;
+ #endregion
+ //-------------------------------------------------
+ #region Constructor's Region
+ ///
+ /// Creates a TOTP instance.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The secret key to use in TOTP calculations
+ ///
+ ///
+ /// The time window step amount to use in calculating time windows.
+ /// The default is 30 as recommended in the RFC
+ ///
+ ///
+ /// The hash mode to use
+ ///
+ ///
+ /// The number of digits that the returning TOTP should have.
+ /// The default value of this argument is 6.
+ ///
+ ///
+ /// If required, a time correction can be specified to compensate of
+ /// an out of sync local clock.
+ ///
+ public Totp(byte[] secretKey,
+ int step = 30,
+ OtpHashMode mode = OtpHashMode.Sha1,
+ int totpSize = 6,
+ TimeCorrection timeCorrection = null)
+ : base(secretKey, mode)
+ {
+
+ if (step < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(step),
+ "Step must be greater than 0");
+ }
+ if (totpSize < 0 || totpSize > 10)
+ {
+ throw new ArgumentOutOfRangeException(nameof(totpSize),
+ "TOTP size must be greater than 0 and less than 10");
+ }
+
+ _step = step;
+ _totpSize = totpSize;
+
+ // we never null check the corrected time object.
+ // Since it's readonly, we'll ensure that it isn't null here
+ // and provide neatral functionality in this case.
+ correctedTime = timeCorrection ?? TimeCorrection.UncorrectedInstance;
+ }
+
+ ///
+ /// Creates a TOTP instance.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The secret key to use in TOTP calculations.
+ ///
+ ///
+ /// The time window step amount to use in calculating time windows.
+ /// The default is 30 as recommended in the RFC
+ ///
+ ///
+ /// The hash mode to use.
+ ///
+ ///
+ /// The number of digits that the returning TOTP should have.
+ /// The default is 6.
+ ///
+ /// If required, a time correction can be specified to compensate of an
+ /// out of sync local clock.
+ public Totp(IKeyProvider key,
+ int step = 30,
+ OtpHashMode mode = OtpHashMode.Sha1,
+ int totpSize = 6,
+ TimeCorrection timeCorrection = null)
+ : base(key, mode)
+ {
+ if (step < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(step),
+ "Step must be greater than 0");
+ }
+ if (totpSize < 0 || totpSize > 10)
+ {
+ throw new ArgumentOutOfRangeException(nameof(totpSize),
+ "TOTP size must be greater than 0 and less than 10");
+ }
+
+ _step = step;
+ _totpSize = totpSize;
+
+ // we never null check the corrected time object.
+ // Since it's readonly, we'll ensure that it isn't null here and
+ // provide neatral functionality in this case.
+ correctedTime = timeCorrection ?? TimeCorrection.UncorrectedInstance;
+ }
+ #endregion
+ //-------------------------------------------------
+ #region overrided Method's Region
+ ///
+ /// Takes a time step and computes a TOTP code.
+ /// since: v0.0.0
+ ///
+ /// time step
+ /// The hash mode to use
+ /// TOTP calculated code
+ protected override string Compute(long counter, OtpHashMode mode)
+ {
+ var data = KeyUtilities.GetBigEndianBytes(counter);
+ var otp = this.CalculateOtp(data, mode);
+ return Digits(otp, _totpSize);
+ }
+ #endregion
+ //-------------------------------------------------
+ #region Get Method's Region
+ ///
+ /// Takes a timestamp and applies correction (if provided) and then computes
+ /// a TOTP value.
+ /// since: v0.0.0
+ ///
+ /// The timestamp to use for the TOTP calculation
+ /// a TOTP value
+ public string ComputeTotp(DateTime timestamp) =>
+ ComputeTotpFromSpecificTime(correctedTime.GetCorrectedTime(timestamp));
+
+ ///
+ /// Takes a timestamp and computes a TOTP value for corrected UTC now.
+ /// since: v0.0.0
+ ///
+ ///
+ /// It will be corrected against a corrected UTC time using the provided time correction. If none was provided then simply the current UTC will be used.
+ ///
+ /// a TOTP value
+ public string ComputeTotp() =>
+ ComputeTotpFromSpecificTime(this.correctedTime.CorrectedUtcNow);
+
+ ///
+ /// Verify a value that has been provided with the calculated value.
+ /// since: v0.0.0
+ ///
+ ///
+ /// It will be corrected against a corrected UTC time using the provided time correction. If none was provided then simply the current UTC will be used.
+ ///
+ ///
+ /// the trial TOTP value
+ ///
+ ///
+ /// This is an output parameter that gives that time step that was used to find a match.
+ /// This is useful in cases where a TOTP value should only be used once. This value is a unique identifier of the
+ /// time step (not the value) that can be used to prevent the same step from being used multiple times
+ ///
+ ///
+ /// The window of steps to verify.
+ ///
+ /// True if there is a match.
+ public bool VerifyTotp(string totp,
+ out long timeStepMatched,
+ VerificationWindow window = null)
+ {
+ return this.VerifyTotpForSpecificTime(correctedTime.CorrectedUtcNow,
+ totp, window, out timeStepMatched);
+ }
+
+ ///
+ /// Verify a value that has been provided with the calculated value.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The timestamp to use.
+ ///
+ ///
+ /// The trial TOTP value.
+ ///
+ ///
+ /// This is an output parameter that gives that time step that was
+ /// used to find a match. This is usefule in cases where a TOTP value
+ /// should only be used once. This value is a unique identifier of the
+ /// time step (not the value) that can be used to prevent the same step
+ /// from being used multiple times.
+ ///
+ /// The window of steps to verify
+ /// True if there is a match.
+ public bool VerifyTotp(DateTime timestamp,
+ string totp,
+ out long timeStepMatched, VerificationWindow window = null)
+ {
+ return this.VerifyTotpForSpecificTime(
+ this.correctedTime.GetCorrectedTime(timestamp),
+ totp, window, out timeStepMatched);
+ }
+
+ ///
+ /// Remaining seconds in current window based on UtcNow.
+ /// since: v0.0.0
+ ///
+ ///
+ /// It will be corrected against a corrected UTC time using the provided time correction. If none was provided then simply the current UTC will be used.
+ ///
+ /// Number of remaining seconds
+ public int GetRemainingSeconds() =>
+ RemainingSecondsForSpecificTime(correctedTime.CorrectedUtcNow);
+
+ ///
+ /// Remaining seconds in current window.
+ /// since: v0.0.0
+ ///
+ /// The timestamp
+ /// Number of remaining seconds
+ public int GetRemainingSeconds(DateTime timestamp) =>
+ RemainingSecondsForSpecificTime(correctedTime.GetCorrectedTime(timestamp));
+ ///
+ /// The Remaining seconds in the current window based on
+ /// the provided timestamp using value.
+ /// since: v0.0.0
+ ///
+ private int RemainingSecondsForSpecificTime(DateTime timestamp)
+ {
+ return _step -
+ (int)(((timestamp.Ticks - unixEpochTicks) / ticksToSeconds) % _step);
+ }
+ ///
+ /// Verify a value that has been provided with the calculated value.
+ /// since: v0.0.0
+ ///
+ private bool VerifyTotpForSpecificTime(DateTime timestamp,
+ string totp, VerificationWindow window, out long timeStepMatched)
+ {
+ var initialStep = CalculateTimeStepFromTimestamp(timestamp);
+ return this.Verify(initialStep, totp, out timeStepMatched, window);
+ }
+
+ ///
+ /// Takes a timestamp and calculates a time step.
+ /// since: v0.0.0
+ ///
+ private long CalculateTimeStepFromTimestamp(DateTime timestamp)
+ {
+ var unixTimestamp = (timestamp.Ticks - unixEpochTicks) / ticksToSeconds;
+ var window = unixTimestamp / (long)_step;
+ return window;
+ }
+ ///
+ /// Takes a timestamp and computes a TOTP value for corrected UTC value.
+ /// since: v0.0.0
+ ///
+ private string ComputeTotpFromSpecificTime(DateTime timestamp)
+ {
+ var window = CalculateTimeStepFromTimestamp(timestamp);
+ return this.Compute(window, _hashMode);
+ }
+
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Security/Otp/VerificationWindow.cs b/Socialvoid/Security/Otp/VerificationWindow.cs
new file mode 100644
index 0000000..9f441d8
--- /dev/null
+++ b/Socialvoid/Security/Otp/VerificationWindow.cs
@@ -0,0 +1,100 @@
+/*
+ * This file is part of Socialvoid.NET Project (https://github.com/Intellivoid/Socialvoid.NET).
+ * Copyright (c) 2021 Socialvoid.NET Authors.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source code of library.
+ * If not, see .
+ */
+
+/*
+ * Credits to Devin Martin and the original OtpSharp library:
+ * https://github.com/kspearrin/Otp.NET
+ */
+
+
+using System.Collections.Generic;
+
+namespace Socialvoid.Security.Otp
+{
+ ///
+ /// A verification window.
+ /// since: v0.0.0
+ ///
+ public sealed class VerificationWindow
+ {
+ //-------------------------------------------------
+ #region static field's Region
+ ///
+ /// The verification window that accomodates network delay that is
+ /// recommended in the RFC.
+ /// since: v0.0.0
+ ///
+ public static readonly VerificationWindow RfcSpecifiedNetworkDelay = new(1, 1);
+ #endregion
+ //-------------------------------------------------
+ #region field's Region
+ ///
+ /// the previous value in the window.
+ /// since: v0.0.0
+ ///
+ private readonly int _previous;
+ ///
+ /// the future value in the window.
+ /// since: v0.0.0
+ ///
+ private readonly int _future;
+ #endregion
+ //-------------------------------------------------
+ #region Constructor's Region
+ ///
+ /// Creates an instance of a verification window.
+ /// since: v0.0.0
+ ///
+ /// The number of previous frames to accept
+ /// The number of future frames to accept
+ public VerificationWindow(int previous = 0, int future = 0)
+ {
+ _previous = previous;
+ _future = future;
+ }
+ #endregion
+ //-------------------------------------------------
+ #region Get Method's Region
+ ///
+ /// Gets an enumberable of all the possible validation candidates.
+ /// since: v0.0.0
+ ///
+ ///
+ /// The initial frame to validate.
+ ///
+ ///
+ /// Enumberable of all possible frames that need to be validated.
+ ///
+ public IEnumerable ValidationCandidates(long initialFrame)
+ {
+ yield return initialFrame;
+ for(int i = 1; i <= _previous; i++)
+ {
+ var val = initialFrame - i;
+ if(val < 0)
+ break;
+ yield return val;
+ }
+
+ for(int i = 1; i <= _future; i++)
+ yield return initialFrame + i;
+ }
+ #endregion
+ //-------------------------------------------------
+ }
+}
diff --git a/Socialvoid/Socialvoid.csproj b/Socialvoid/Socialvoid.csproj
index 530b1a3..bf66576 100644
--- a/Socialvoid/Socialvoid.csproj
+++ b/Socialvoid/Socialvoid.csproj
@@ -83,18 +83,9 @@
-
+