-
Notifications
You must be signed in to change notification settings - Fork 6
usage with older dotnet versions
To use this library with .NET Framework 4.6.1 - 4.8.1, you need to compile the library targeting .NET Standard 2.0 instead of .NET Standard 2.1, as .NET Standard 2.1 removes support for .NET Framework. .NET Standard 2.0 also lacks some features available in .NET Standard 2.1, which requires some refactoring. For example, .NET Standard 2.0 does not support default interface implementation.
For this you will need to download the source code, make the necessary code changes, compile the solution and then import the library as a DLL into your application.
Set the target framework in WebEid.Security.csproj
file to .Net Standard 2.0.
To do this, open the WebEid.Security.csproj file and modify the TargetFramework
element value as follows:
<TargetFramework>netstandard2.0</TargetFramework>
We need to move CURRENT_TOKEN_FORMAT_VERSION
to AuthTokenValidator
class.
In IAuthTokenValidator
interface we need to remove the following line (ln32):
const string CURRENT_TOKEN_FORMAT_VERSION = "web-eid:1";
Then add the constant to the AuthTokenValidator
class and change the references:
...
public sealed class AuthTokenValidator : IAuthTokenValidator
{
private readonly ILogger logger;
private readonly AuthTokenValidationConfiguration configuration;
private readonly AuthTokenSignatureValidator authTokenSignatureValidator;
private readonly SubjectCertificateValidatorBatch simpleSubjectCertificateValidators;
private readonly OcspClient ocspClient;
private readonly OcspServiceProvider ocspServiceProvider;
private const int TokenMinLength = 100;
private const int TokenMaxLength = 10000;
/// <summary>
/// The current token format version
/// </summary>
public const string CURRENT_TOKEN_FORMAT_VERSION = "web-eid:1";
...
private async Task<X509Certificate2> ValidateToken(WebEidAuthToken token, string currentChallengeNonce)
{
if (token.Format == null || !token.Format.StartsWith(CURRENT_TOKEN_FORMAT_VERSION))
{
throw new AuthTokenParseException($"Only token format version '{CURRENT_TOKEN_FORMAT_VERSION}' is currently supported");
}
if (string.IsNullOrEmpty(token.UnverifiedCertificate))
{
throw new AuthTokenParseException("'unverifiedCertificate' field is missing, null or empty");
}
...
We need to move NonceLength
from IChallengeNonceGenerator
to ChallengeNonceGenerator
class.
In IChallengeNonceGenerator
interface we need to remove the following lines (ln31-34):
/// <summary>
/// Challenge Nonce length in bytes.
/// </summary>
const int NonceLength = 32;
Then add the constant to the ChallengeNonceGenerator
class and change the references:
...
public sealed class ChallengeNonceGenerator : IChallengeNonceGenerator
{
private readonly IChallengeNonceStore store;
private readonly RandomNumberGenerator randomNumberGenerator;
/// <summary>
/// Challenge Nonce length in bytes.
/// </summary>
public const int NonceLength = 32;
...
public ChallengeNonce GenerateAndStoreNonce(TimeSpan ttl)
{
if (ttl.IsNegativeOrZero())
{
throw new ArgumentOutOfRangeException(nameof(ttl), "Nonce time-to-live duration must be greater than zero");
}
var nonceBytes = new byte[NonceLength];
this.randomNumberGenerator.GetBytes(nonceBytes);
var base64StringNonce = Convert.ToBase64String(nonceBytes);
var expirationTime = DateTimeProvider.UtcNow.Add(ttl);
var challengeNonce = new ChallengeNonce(base64StringNonce, expirationTime);
this.store.Put(challengeNonce);
return challengeNonce;
}
}
}
2.3. Change the IChallengeNonceStore
interface implementation and create an abstract ChallengeNonceStoreBase
class
Since .Net Standard 2.0 does not support default interface implementation, we need to create a new abstract class, that will be referenced by our application instead.
Change the IChallengeNonceStore
interface as follows:
/// <summary>
/// Interface representing a store for storing generated challenge nonces and accessing their generation time.
/// </summary>
public interface IChallengeNonceStore
{
/// <summary>
/// Inserts given <see cref="ChallengeNonce"/> object into the store.
/// </summary>
/// <param name="challengeNonce">The nonce object to be stored.</param>
void Put(ChallengeNonce challengeNonce);
/// <summary>
/// Internal implementation of GetAndRemove method.
/// </summary>
/// <returns>Stored <see cref="ChallengeNonce"/> object.</returns>
ChallengeNonce GetAndRemoveImpl();
/// <summary>
/// Removes and returns the <see cref="ChallengeNonce"/> object being stored.
/// </summary>
/// <returns>Stored <see cref="ChallengeNonce"/> object.</returns>
/// <exception cref="ChallengeNonceNotFoundException">Thrown if the stored is empty.</exception>
/// <exception cref="ChallengeNonceExpiredException">Thrown if the stored <see cref="ChallengeNonce"/> object has been expired.</exception>
/// <remarks>
/// The method checks if there is any <see cref="ChallengeNonce"/> stored, and if so then it also check if it is not expired before returning it.
/// If the Challenge Nonce has been expired then <see cref="ChallengeNonceExpiredException"/> exception is thrown and it is removed from the store.
/// If there is no Challenge Nonce stored, either no <see cref="Put(ChallengeNonce)"/> method was not called or it was removed previously
/// due expiration then ChallengeNonceNotFoundException exception is thrown.
/// </remarks>
ChallengeNonce GetAndRemove();
}
We are removing the GetAndRemove()
implementation from the interface and moving it to a new abstract class ChallengeNonceStoreBase
:
namespace WebEid.Security.Challenge
{
using WebEid.Security.Exceptions;
using WebEid.Security.Util;
/// <summary>
/// Base class for challenge nonce store.
/// </summary>
public abstract class ChallengeNonceStoreBase : IChallengeNonceStore
{
/// <summary>
/// Internal implementation of <see cref="GetAndRemove"/> method.
/// </summary>
/// <returns>Stored <see cref="ChallengeNonce"/> object.</returns>
public abstract ChallengeNonce GetAndRemoveImpl();
/// <summary>
/// Inserts given <see cref="ChallengeNonce"/> object into the store.
/// </summary>
/// <param name="challengeNonce">The nonce object to be stored.</param>
public abstract void Put(ChallengeNonce challengeNonce);
/// <summary>
/// Removes and returns the <see cref="ChallengeNonce"/> object being stored.
/// </summary>
/// <returns>Stored <see cref="ChallengeNonce"/> object.</returns>
/// <exception cref="ChallengeNonceNotFoundException">Thrown if the stored is empty.</exception>
/// <exception cref="ChallengeNonceExpiredException">Thrown if the stored <see cref="ChallengeNonce"/> object has been expired.</exception>
/// <remarks>
/// The method checks if there is any <see cref="ChallengeNonce"/> stored, and if so then it also check if it is not expired before returning it.
/// If the Challenge Nonce has been expired then <see cref="ChallengeNonceExpiredException"/> exception is thrown and it is removed from the store.
/// If there is no Challenge Nonce stored, either no <see cref="Put(ChallengeNonce)"/> method was not called or it was removed previously
/// due expiration then ChallengeNonceNotFoundException exception is thrown.
/// </remarks>
public ChallengeNonce GetAndRemove()
{
var challengeNonce = GetAndRemoveImpl() ?? throw new ChallengeNonceNotFoundException();
if (DateTimeProvider.UtcNow >= challengeNonce.ExpirationTime)
{
throw new ChallengeNonceExpiredException();
}
return challengeNonce;
}
}
}
In this abstract class we want to make sure, that the GetAndRemove()
implementation is not overridable while requiring the GetAndRemoveImpl()
and Put(ChallengeNonce challengeNonce)
to be overridden.
In X509CertificateExtensions.cs
file we need to modify the line nr 159 as follows:
return valueList.Count == 0 ? null : string.Join(" ", valueList.Cast<object>().Select(i => i.ToString()));
We are change the ' '
to " "
, since .Net Standard 2.0 requires the string.Join
separator argument to be of string
type and not char
.
In AuthTokenSignatureValidator.cs
file we need to modify the line nr 105 as follows:
var hashAlgorithmName = "SHA" + algorithmName.Substring(algorithmName.Length - 3);
.Net Standard 2.0 does not support the ^
(hat) and ..
(range) operators.
4. Changing the InMemoryChallengeNonceStore
implementation to ensure that tests are working as intended
In the test project we need the test Challenge Nonce Store InMemoryChallengeNonceStore
to use the newly created base class instead of the interface. Change the code as follows:
namespace WebEid.Security.Tests.Nonce
{
using WebEid.Security.Challenge;
internal class InMemoryChallengeNonceStore : ChallengeNonceStoreBase
{
private ChallengeNonce challengeNonce;
public override ChallengeNonce GetAndRemoveImpl()
{
var result = this.challengeNonce;
this.challengeNonce = null;
return result;
}
public override void Put(ChallengeNonce challengeNonce) => this.challengeNonce = challengeNonce;
}
}
Instead of adding the Web eID GitLab package source to NuGet Package Manager and installing the library from there as described in the Quickstart guide, we need to compile the modified solution and import the output DLL.
Compile the WebEid.Security
project with Release
profile.
After a successful compilation copy the output files from src\WebEid.Security\bin\Release\netstandard2.0
to your solution.
We recommend making a new folder WebEid
in your solution folder and paste the output files there.
Then import the WebEid.Security.dll
from the newly created folder to your solution.
You can achieve this in MS Visual Studio by adding a new reference to the project. This can be achieved by first selecting the project you want to add the reference to in Solution Explorer. When the project is selected, you can go to the Project menu from the top bar and click on the Add reference... option.
From there you can select Browse and browse to the WebEid.Security.dll we copied before. The DLL will then be visible in the list with a checked checkbox in front of it. Next, you can complete adding the reference by clicking OK.
If you are not using MS Visual Studio, you can achieve the same result by adding the following to your .csproj
file under the ItemGroup
with references:
<Reference Include="WebEid.Security">
<HintPath>..\WebEid\WebEid.Security.dll</HintPath>
</Reference>
The HintPath
needs to match the location of the WebEid.Security.dll
file path.
The validation library needs a store for saving the issued challenge nonces. As it must be guaranteed that the authentication token is received from the same browser to which the corresponding challenge nonce was issued, using a session-backed challenge nonce store is the most natural choice.
Implement the session-backed challenge nonce store as follows, using the new abstract base class ChallengeNonceStoreBase
instead of the IChallengeNonceStore
interface:
using System.Web;
using Newtonsoft.Json;
using WebEid.Security.Challenge;
public class SessionBackedChallengeNonceStore : ChallengeNonceStoreBase
{
private const string ChallengeNonceKey = "challenge-nonce";
private readonly HttpContext httpContextAccessor;
public override void Put(ChallengeNonce challengeNonce)
{
HttpContext.Current.Session[ChallengeNonceKey] = JsonConvert.SerializeObject(challengeNonce);
}
public override ChallengeNonce GetAndRemoveImpl()
{
var challenceNonceJson = HttpContext.Current.Session[ChallengeNonceKey] as string;
if (!string.IsNullOrWhiteSpace(challenceNonceJson))
{
HttpContext.Current.Session.Remove(ChallengeNonceKey);
return JsonConvert.DeserializeObject<ChallengeNonce>(challenceNonceJson);
}
return null;
}
}