Skip to content

Commit

Permalink
chore: merge
Browse files Browse the repository at this point in the history
  • Loading branch information
brunobritodev committed May 3, 2023
2 parents 2c3d1d5 + 6f7e9ff commit 3a0078e
Show file tree
Hide file tree
Showing 11 changed files with 89 additions and 15 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# [7.1.0](https://github.com/NetDevPack/Security.Jwt/compare/v7.0.2...v7.1.0) (2023-04-14)


### Features

* new demos [skip ci] ([a0a5f62](https://github.com/NetDevPack/Security.Jwt/commit/a0a5f622f48dc98361ffbe327905feef2a816722))
* Revoked Reason ([c7f10bc](https://github.com/NetDevPack/Security.Jwt/commit/c7f10bcdb392ebef79140b9c3dff00b49c2aab7a))
* **revoke:** revoke key with given reason ([350fcaa](https://github.com/NetDevPack/Security.Jwt/commit/350fcaa26ba3645344d3d060a42787aa55f98fa1))

## [7.0.2](https://github.com/NetDevPack/Security.Jwt/compare/v7.0.1...v7.0.2) (2022-12-05)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ internal class DataProtectionStore : IJsonWebKeyStore
private IXmlRepository KeyRepository => _keyManagementOptions.Value.XmlRepository ?? GetFallbackKeyRepositoryEncryptorPair();

private const string Name = "NetDevPackSecurityJwt";
internal const string DefaultRevocationReason = "Revoked";

public DataProtectionStore(
ILoggerFactory loggerFactory,
Expand Down Expand Up @@ -98,7 +99,7 @@ private IReadOnlyCollection<KeyMaterial> GetKeys()
{
var allElements = KeyRepository.GetAllElements();
var keys = new List<KeyMaterial>();
var revokedKeys = new List<string>();
var revokedKeys = new List<RevokedKeyInfo>();
foreach (var element in allElements)
{
if (element.Name == Name)
Expand All @@ -124,21 +125,22 @@ private IReadOnlyCollection<KeyMaterial> GetKeys()
if (key.IsExpired(_options.Value.DaysUntilExpire))
{
//Revoke(key).Wait();
revokedKeys.Add(key.Id.ToString());
revokedKeys.Add(new RevokedKeyInfo(key.Id.ToString()));
}

keys.Add(key);
}
else if (element.Name == RevocationElementName)
{
var keyIdAsString = (string)element.Element(Name)!.Attribute(IdAttributeName)!;
revokedKeys.Add(keyIdAsString);
var reason = (string)element.Element(ReasonElementName);
revokedKeys.Add(new RevokedKeyInfo(keyIdAsString, reason));
}
}

foreach (var revokedKey in revokedKeys)
{
keys.FirstOrDefault(a => a.Id.ToString().Equals(revokedKey))?.Revoke();
keys.FirstOrDefault(a => a.Id.ToString().Equals(revokedKey.Id))?.Revoke(revokedKey.RevokedReason);
}
return keys.ToList();
}
Expand Down Expand Up @@ -181,7 +183,7 @@ public async Task Clear()
}


public async Task Revoke(KeyMaterial keyMaterial)
public async Task Revoke(KeyMaterial keyMaterial, string reason = null)
{
if(keyMaterial == null)
return;
Expand All @@ -193,12 +195,13 @@ public async Task Revoke(KeyMaterial keyMaterial)
return;

keyMaterial.Revoke();
var revokeReason = reason ?? DefaultRevocationReason;
var revocationElement = new XElement(RevocationElementName,
new XAttribute(VersionAttributeName, 1),
new XElement(RevocationDateElementName, DateTimeOffset.UtcNow),
new XElement(Name,
new XAttribute(IdAttributeName, keyMaterial.Id)),
new XElement(ReasonElementName, "Revoked"));
new XElement(ReasonElementName, revokeReason));


// Persist it to the underlying repository and trigger the cancellation token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace NetDevPack.Security.Jwt.Core.DefaultStore;

internal class InMemoryStore : IJsonWebKeyStore
{

internal const string DefaultRevocationReason = "Revoked";
private static readonly List<KeyMaterial> _store = new();
private readonly SemaphoreSlim _slim = new(1);
public Task Store(KeyMaterial keyMaterial)
Expand All @@ -23,12 +23,12 @@ public Task<KeyMaterial> GetCurrent()
return Task.FromResult(_store.OrderByDescending(s => s.CreationDate).FirstOrDefault());
}

public async Task Revoke(KeyMaterial keyMaterial)
public async Task Revoke(KeyMaterial keyMaterial, string reason = null)
{
if(keyMaterial == null)
return;

keyMaterial.Revoke();
var revokeReason = reason ?? DefaultRevocationReason;
keyMaterial.Revoke(revokeReason);
var oldOne = _store.Find(f => f.Id == keyMaterial.Id);
if (oldOne != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public interface IJsonWebKeyStore
{
Task Store(KeyMaterial keyMaterial);
Task<KeyMaterial> GetCurrent();
Task Revoke(KeyMaterial keyMaterial);
Task Revoke(KeyMaterial keyMaterial, string reason=default);
Task<ReadOnlyCollection<KeyMaterial>> GetLastKeys(int quantity);
Task<KeyMaterial> Get(string keyId);
Task Clear();
Expand Down
6 changes: 5 additions & 1 deletion src/NetDevPack.Security.Jwt.Core/Model/Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public KeyMaterial(CryptographicKey cryptographicKey)
public string Type { get; set; }
public string Parameters { get; set; }
public bool IsRevoked { get; set; }
public string? RevokedReason { get; set; }
public DateTime CreationDate { get; set; }
public DateTime? ExpiredAt { get; set; }

Expand All @@ -30,15 +31,18 @@ public JsonWebKey GetSecurityKey()
return JsonSerializer.Deserialize<JsonWebKey>(Parameters);
}

public void Revoke()
public void Revoke(string reason=default)
{
var jsonWebKey = GetSecurityKey();
var publicWebKey = PublicJsonWebKey.FromJwk(jsonWebKey);
ExpiredAt = DateTime.UtcNow;
IsRevoked = true;
RevokedReason = reason;
Parameters = JsonSerializer.Serialize(publicWebKey.ToNativeJwk(), new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault });
}



public bool IsExpired(int valueDaysUntilExpire)
{
return CreationDate.AddDays(valueDaysUntilExpire) < DateTime.UtcNow.Date;
Expand Down
7 changes: 7 additions & 0 deletions src/NetDevPack.Security.Jwt.Core/Model/RevokedKeyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NetDevPack.Security.Jwt.Core.Model;

record RevokedKeyInfo(string Id, string? RevokedReason=default)
{
public string Id { get; } = Id;
public string? RevokedReason { get; } = RevokedReason;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="7.0.0" />
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>NetDevPack.Security.Jwt.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<Target Name="CopyHook" AfterTargets="AfterBuild" Condition="'$(Configuration)' == 'Debug'">
<ItemGroup>
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public bool NeedsUpdate()
return !File.Exists(GetCurrentFile()) || File.GetCreationTimeUtc(GetCurrentFile()).AddDays(_options.Value.DaysUntilExpire) < DateTime.UtcNow.Date;
}

public async Task Revoke(KeyMaterial? securityKeyWithPrivate)
public async Task Revoke(KeyMaterial securityKeyWithPrivate, string reason = null)
{
if (securityKeyWithPrivate == null)
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
using NetDevPack.Security.Jwt.Tests.Warmups;
using System.Linq;
using Microsoft.IdentityModel.Tokens;
using NetDevPack.Security.Jwt.Tests.Warmups;
using System.Threading.Tasks;
using FluentAssertions;
using NetDevPack.Security.Jwt.Core.DefaultStore;
using NetDevPack.Security.Jwt.Core.Jwa;
using NetDevPack.Security.Jwt.Core.Model;
using Xunit;

namespace NetDevPack.Security.Jwt.Tests.StoreTests;
Expand All @@ -9,4 +16,6 @@ public class DataProtectionStoreTest : GenericStoreServiceTest<WarmupDataProtect
public DataProtectionStoreTest(WarmupDataProtectionStore unifiedContext) : base(unifiedContext)
{
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using NetDevPack.Security.Jwt.Core;
using NetDevPack.Security.Jwt.Core.DefaultStore;
using NetDevPack.Security.Jwt.Core.Interfaces;
using NetDevPack.Security.Jwt.Core.Jwa;
using NetDevPack.Security.Jwt.Core.Model;
Expand All @@ -21,7 +22,7 @@ public abstract class GenericStoreServiceTest<TWarmup> : IClassFixture<TWarmup>
where TWarmup : class, IWarmupTest
{
private static SemaphoreSlim TestSync = new(1);
private readonly IJsonWebKeyStore _store;
protected readonly IJsonWebKeyStore _store;
private readonly IOptions<JwtOptions> _options;
public TWarmup WarmupData { get; }

Expand Down Expand Up @@ -498,6 +499,42 @@ public async Task ShouldGenerateAndValidateJweAndJws()

}

[Fact]
public async Task Should_Read_Default_Revocation_Reason()
{
var keyMaterial = await StoreRandomKey();
/*Revoke*/
await _store.Revoke(keyMaterial);
await CheckRevocationReasonIsStored(keyMaterial.KeyId, DataProtectionStore.DefaultRevocationReason);
}

[Theory]
[InlineData("ManualRevocation")]
[InlineData("StolenKey")]
public async Task Should_Read_NonDefault_Revocation_Reason(string reason)
{
var keyMaterial = await StoreRandomKey();
/*Revoke with reason*/
await _store.Revoke(keyMaterial, reason);
await CheckRevocationReasonIsStored(keyMaterial.KeyId, reason);
}

private async Task CheckRevocationReasonIsStored(string keyId, string revocationReason)
{
var dbKey = (await _store.GetLastKeys(5)).First(w => w.KeyId == keyId);
dbKey.Type.Should().NotBeNullOrEmpty();
dbKey.RevokedReason.Should().BeEquivalentTo(revocationReason);
}

private async Task<KeyMaterial> StoreRandomKey()
{
var alg = Algorithm.Create(DigitalSignaturesAlgorithm.RsaSha512);
var key = new CryptographicKey(alg);
var keyMaterial = new KeyMaterial(key);
await _store.Store(keyMaterial);
return keyMaterial;
}



private Task GenerateKey()
Expand Down

0 comments on commit 3a0078e

Please sign in to comment.