Skip to content

Commit

Permalink
Merge pull request #96 from mdsol/feature/VerifySignatureTime
Browse files Browse the repository at this point in the history
Add time constraint between signature and verification
  • Loading branch information
Jman420 committed Mar 22, 2023
2 parents c6cdecc + bf7b848 commit ba0c7bf
Show file tree
Hide file tree
Showing 15 changed files with 307 additions and 32 deletions.
@@ -1,4 +1,4 @@
name: Build and Test (.NET 5.0)
name: Build and Test (.NET 6.0)
on: [push]
jobs:
Test:
Expand All @@ -9,6 +9,6 @@ jobs:
with:
submodules: 'recursive'
- name: Run the Core tests
run: dotnet test $GITHUB_WORKSPACE/tests/Medidata.MAuth.CoreTests --framework net5.0
run: dotnet test $GITHUB_WORKSPACE/tests/Medidata.MAuth.CoreTests --framework net6.0
- name: Run the ASP.NET Core tests
run: dotnet test $GITHUB_WORKSPACE/tests/Medidata.MAuth.AspNetCoreTests --framework net5.0
run: dotnet test $GITHUB_WORKSPACE/tests/Medidata.MAuth.AspNetCoreTests --framework net6.0
15 changes: 15 additions & 0 deletions src/Medidata.MAuth.Core/DateTimeOffsetWrapper.cs
@@ -0,0 +1,15 @@
using System;

namespace Medidata.MAuth.Core
{
internal class DateTimeOffsetWrapper : IDateTimeOffsetWrapper
{
/// <summary>
/// A facade around DatetimeOffset.UtcNow
/// </summary>
/// <returns>
/// The value of DateTimeOffset.UtcNow
/// </returns>
public DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow;
}
}
15 changes: 15 additions & 0 deletions src/Medidata.MAuth.Core/IDateTimeOffsetWrapper.cs
@@ -0,0 +1,15 @@
using System;

namespace Medidata.MAuth.Core;

/// <summary>
/// A facade for <see cref="DateTimeOffset" />
/// </summary>
public interface IDateTimeOffsetWrapper
{
/// <summary>
/// A facade for the UtcNow property.
/// </summary>
/// <returns>A <see cref="DateTimeOffset" /> value</returns>
DateTimeOffset GetUtcNow();
}
30 changes: 29 additions & 1 deletion src/Medidata.MAuth.Core/MAuthAuthenticator.cs
Expand Up @@ -13,9 +13,13 @@ namespace Medidata.MAuth.Core
{
internal class MAuthAuthenticator
{
private const int AllowedDriftSeconds = 300;
private static readonly TimeSpan AllowedDriftTimeSpan = TimeSpan.FromSeconds(AllowedDriftSeconds);

private readonly ICacheService _cache;
private readonly MAuthOptionsBase _options;
private readonly ILogger _logger;
private readonly IDateTimeOffsetWrapper _dateTimeOffsetWrapper;
private readonly Lazy<HttpClient> _lazyHttpClient;

public Guid ApplicationUuid => _options.ApplicationUuid;
Expand All @@ -35,6 +39,7 @@ public MAuthAuthenticator(MAuthOptionsBase options, ILogger logger, ICacheServic
_options = options;
_logger = logger;
_lazyHttpClient = new Lazy<HttpClient>(() => CreateHttpClient(options));
_dateTimeOffsetWrapper = options.DateTimeOffsetWrapper;
}

/// <summary>
Expand Down Expand Up @@ -104,15 +109,38 @@ private async Task<bool> Authenticate(HttpRequestMessage request, MAuthVersion v

var mAuthCore = MAuthCoreFactory.Instantiate(version);
var authInfo = GetAuthenticationInfo(request, mAuthCore);


if (!IsSignatureTimeValid(authInfo.SignedTime))
{
return false;
}

var appInfo = await _cache.GetOrCreateWithLock(
authInfo.ApplicationUuid.ToString(),
() => SendApplicationInfoRequest(authInfo.ApplicationUuid)).ConfigureAwait(false);

var signature = await mAuthCore.GetSignature(request, authInfo).ConfigureAwait(false);
return mAuthCore.Verify(authInfo.Payload, signature, appInfo.PublicKey);
}

private bool IsSignatureTimeValid(DateTimeOffset signedTime)
{
var now = _dateTimeOffsetWrapper.GetUtcNow();
var lowerBound = now - AllowedDriftTimeSpan;
var upperBound = now + AllowedDriftTimeSpan;
var isValid = signedTime >= lowerBound && signedTime <= upperBound;

if (!isValid)
{
_logger.LogInformation(
"Time verification failed. {signedTime} is not within {AllowedDriftSeconds} seconds of #{now}",
signedTime,
AllowedDriftSeconds,
now);
}

return isValid;
}

private async Task<CacheResult<ApplicationInfo>> SendApplicationInfoRequest(Guid applicationUuid)
{
Expand Down
8 changes: 4 additions & 4 deletions src/Medidata.MAuth.Core/Medidata.MAuth.Core.csproj
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>A core package for Medidata HMAC protocol implementation. This package contains the core functionality which used by the MAuth authentication protocol-specific components. This package also can be used standalone if you want to sign HTTP/HTTPS requests with Medidata MAuth keys using the .NET HttpClient message handler mechanism.</Description>
<AssemblyTitle>Medidata.MAuth.Core</AssemblyTitle>
<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<AssemblyName>Medidata.MAuth.Core</AssemblyName>
<PackageTags>medidata;mauth;hmac;authentication;core;httpclient;messagehandler</PackageTags>
</PropertyGroup>
Expand All @@ -12,11 +12,11 @@
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
</ItemGroup>
</Project>
5 changes: 5 additions & 0 deletions src/Medidata.MAuth.Core/Options/MAuthOptionsBase.cs
Expand Up @@ -50,5 +50,10 @@ public string PrivateKey
/// Determines the boolean value if V1 option of signing should be disabled or not with default value of false.
/// </summary>
public bool DisableV1 { get; set; } = false;

/// <summary>
/// Allow injection of a DateTimeOffset wrapper for testing purposes.
/// </summary>
public IDateTimeOffsetWrapper DateTimeOffsetWrapper { get; set; } = new DateTimeOffsetWrapper();
}
}
9 changes: 9 additions & 0 deletions tests/Medidata.MAuth.AspNetCoreTests/MAuthAspNetCoreTests.cs
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Medidata.MAuth.AspNetCore;
using Medidata.MAuth.Core;
using Medidata.MAuth.Tests.Common.Infrastructure;
using Medidata.MAuth.Tests.Infrastructure;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
Expand All @@ -23,6 +24,7 @@ public async Task MAuthMiddleware_WithValidRequest_WillAuthenticate(string metho
{
// Arrange
var testData = await method.FromResourceV2();
var mockDateTimeOffsetWrapper = new MockDateTimeOffsetWrapper { MockedValue = testData.SignedTime };
var serverHandler = await MAuthServerHandler.CreateAsync();

using (var server = new TestServer(new WebHostBuilder().Configure(app =>
Expand All @@ -34,6 +36,7 @@ public async Task MAuthMiddleware_WithValidRequest_WillAuthenticate(string metho
options.PrivateKey = TestExtensions.ServerPrivateKey;
options.MAuthServerHandler = serverHandler;
options.HideExceptionsAndReturnUnauthorized = false;
options.DateTimeOffsetWrapper = mockDateTimeOffsetWrapper;
});
app.Run(async context => await new StreamWriter(context.Response.Body).WriteAsync("Done."));
Expand All @@ -56,6 +59,7 @@ public async Task MAuthMiddleware_WithoutMAuthHeader_WillNotAuthenticate(string
{
// Arrange
var testData = await method.FromResource();
var mockDateTimeOffsetWrapper = new MockDateTimeOffsetWrapper { MockedValue = testData.SignedTime };
var serverHandler = await MAuthServerHandler.CreateAsync();

using (var server = new TestServer(new WebHostBuilder().Configure(app =>
Expand All @@ -66,6 +70,7 @@ public async Task MAuthMiddleware_WithoutMAuthHeader_WillNotAuthenticate(string
options.MAuthServiceUrl = TestExtensions.TestUri;
options.PrivateKey = TestExtensions.ServerPrivateKey;
options.MAuthServerHandler = serverHandler;
options.DateTimeOffsetWrapper = mockDateTimeOffsetWrapper;
});
app.Run(async context => await new StreamWriter(context.Response.Body).WriteAsync("Done."));
Expand All @@ -89,6 +94,7 @@ public async Task MAuthMiddleware_WithEnabledExceptions_WillThrowException(strin
{
// Arrange
var testData = await method.FromResource();
var mockDateTimeOffsetWrapper = new MockDateTimeOffsetWrapper { MockedValue = testData.SignedTime };
var serverHandler = await MAuthServerHandler.CreateAsync();

using (var server = new TestServer(new WebHostBuilder().Configure(app =>
Expand All @@ -100,6 +106,7 @@ public async Task MAuthMiddleware_WithEnabledExceptions_WillThrowException(strin
options.PrivateKey = TestExtensions.ServerPrivateKey;
options.MAuthServerHandler = serverHandler;
options.HideExceptionsAndReturnUnauthorized = false;
options.DateTimeOffsetWrapper = mockDateTimeOffsetWrapper;
});
})))
{
Expand All @@ -122,6 +129,7 @@ public async Task MAuthMiddleware_WithNonSeekableBodyStream_WillRestoreBodyStrea
{
// Arrange
var testData = await method.FromResourceV2();
var mockDateTimeOffsetWrapper = new MockDateTimeOffsetWrapper { MockedValue = testData.SignedTime };
var canSeek = false;
var body = string.Empty;
var serverHandler = await MAuthServerHandler.CreateAsync();
Expand All @@ -136,6 +144,7 @@ public async Task MAuthMiddleware_WithNonSeekableBodyStream_WillRestoreBodyStrea
options.MAuthServiceUrl = TestExtensions.TestUri;
options.PrivateKey = TestExtensions.ServerPrivateKey;
options.MAuthServerHandler = serverHandler;
options.DateTimeOffsetWrapper = mockDateTimeOffsetWrapper;
})
.Run(async context =>
{
Expand Down
@@ -1,14 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net5.0</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<Description>Unit tests for the Medidata.MAuth.AspNetCore package.</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.15" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit ba0c7bf

Please sign in to comment.