5-improve-encrypted-storage #6
5 changed files with 130 additions and 26 deletions
36
IdentityShroud.Core.Tests/Helpers/Base64UrlConverterTests.cs
Normal file
36
IdentityShroud.Core.Tests/Helpers/Base64UrlConverterTests.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using IdentityShroud.Core.Helpers;
|
||||||
|
|
||||||
|
namespace IdentityShroud.Core.Tests.Helpers;
|
||||||
|
|
||||||
|
public class Base64UrlConverterTests
|
||||||
|
{
|
||||||
|
internal class Data
|
||||||
|
{
|
||||||
|
[JsonConverter(typeof(Base64UrlConverter))]
|
||||||
|
public byte[]? X { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Serialize()
|
||||||
|
{
|
||||||
|
Data d = new() { X = ">>>???"u8.ToArray() };
|
||||||
|
string s = JsonSerializer.Serialize(d);
|
||||||
|
|
||||||
|
Assert.Contains("\"Pj4-Pz8_\"", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Deerialize()
|
||||||
|
{
|
||||||
|
var jsonstring = """
|
||||||
|
{ "X": "Pj4-Pz8_" }
|
||||||
|
""";
|
||||||
|
var d = JsonSerializer.Deserialize<Data>(jsonstring);
|
||||||
|
|
||||||
|
Assert.Equal(">>>???", Encoding.UTF8.GetString(d.X));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using IdentityShroud.Core.Contracts;
|
||||||
|
using IdentityShroud.Core.Model;
|
||||||
|
using IdentityShroud.Core.Security;
|
||||||
|
using IdentityShroud.Core.Services;
|
||||||
|
using IdentityShroud.TestUtils.Substitutes;
|
||||||
|
|
||||||
|
namespace IdentityShroud.Core.Tests.Services;
|
||||||
|
|
||||||
|
public class DataEncryptionServiceTests
|
||||||
|
{
|
||||||
|
private readonly IRealmContext _realmContext = Substitute.For<IRealmContext>();
|
||||||
|
private readonly IDekEncryptionService _dekCryptor = new NullDekEncryptionService();// Substitute.For<IDekEncryptionService>();
|
||||||
|
|
||||||
|
private readonly DekId _activeDekId = DekId.NewId();
|
||||||
|
private readonly DekId _secondDekId = DekId.NewId();
|
||||||
|
private DataEncryptionService CreateSut()
|
||||||
|
=> new(_realmContext, _dekCryptor);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Encrypt_UsesActiveKey()
|
||||||
|
{
|
||||||
|
_realmContext.GetDeks(Arg.Any<CancellationToken>()).Returns([
|
||||||
|
CreateRealmDek(_secondDekId, false),
|
||||||
|
CreateRealmDek(_activeDekId, true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var cipher = CreateSut().Encrypt("Hello"u8);
|
||||||
|
|
||||||
|
Assert.Equal(_activeDekId, cipher.DekId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Decrypt_UsesCorrectKey()
|
||||||
|
{
|
||||||
|
var first = CreateRealmDek(_activeDekId, true);
|
||||||
|
_realmContext.GetDeks(Arg.Any<CancellationToken>()).Returns([ first ]);
|
||||||
|
|
||||||
|
var sut = CreateSut();
|
||||||
|
var cipher = sut.Encrypt("Hello"u8);
|
||||||
|
|
||||||
|
// Deactivate original key
|
||||||
|
first.Active = false;
|
||||||
|
// Make new active
|
||||||
|
var second = CreateRealmDek(_secondDekId, true);
|
||||||
|
// Return both
|
||||||
|
_realmContext.GetDeks(Arg.Any<CancellationToken>()).Returns([ first, second ]);
|
||||||
|
|
||||||
|
|
||||||
|
var decoded = sut.Decrypt(cipher);
|
||||||
|
|
||||||
|
Assert.Equal("Hello"u8, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RealmDek CreateRealmDek(DekId id, bool active)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Active = active,
|
||||||
|
Algorithm = "AES",
|
||||||
|
KeyData = new(KekId.NewId(), RandomNumberGenerator.GetBytes(32)),
|
||||||
|
RealmId = default,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using System.Buffers;
|
|
||||||
using System.Buffers.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using IdentityShroud.Core.Helpers;
|
||||||
|
|
||||||
namespace IdentityShroud.Core.Messages;
|
namespace IdentityShroud.Core.Messages;
|
||||||
|
|
||||||
|
|
@ -49,25 +47,3 @@ public class JsonWebKey
|
||||||
// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
// public string? X509CertificateThumbprint { get; set; }
|
// public string? X509CertificateThumbprint { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Base64UrlConverter : JsonConverter<byte[]>
|
|
||||||
{
|
|
||||||
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
// GetValueSpan gives you the raw UTF-8 bytes of the JSON string value
|
|
||||||
if (reader.HasValueSequence)
|
|
||||||
{
|
|
||||||
var valueSequence = reader.ValueSequence.ToArray();
|
|
||||||
return Base64Url.DecodeFromUtf8(valueSequence);
|
|
||||||
}
|
|
||||||
return Base64Url.DecodeFromUtf8(reader.ValueSpan);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
int encodedLength = Base64Url.GetEncodedLength(value.Length);
|
|
||||||
Span<byte> buffer = encodedLength <= 256 ? stackalloc byte[encodedLength] : new byte[encodedLength];
|
|
||||||
Base64Url.EncodeToUtf8(value, buffer);
|
|
||||||
writer.WriteStringValue(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
IdentityShroud.Core/Helpers/Base64UrlConverter.cs
Normal file
28
IdentityShroud.Core/Helpers/Base64UrlConverter.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Buffers.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace IdentityShroud.Core.Helpers;
|
||||||
|
|
||||||
|
public class Base64UrlConverter : JsonConverter<byte[]>
|
||||||
|
{
|
||||||
|
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
// GetValueSpan gives you the raw UTF-8 bytes of the JSON string value
|
||||||
|
if (reader.HasValueSequence)
|
||||||
|
{
|
||||||
|
var valueSequence = reader.ValueSequence.ToArray();
|
||||||
|
return Base64Url.DecodeFromUtf8(valueSequence);
|
||||||
|
}
|
||||||
|
return Base64Url.DecodeFromUtf8(reader.ValueSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
int encodedLength = Base64Url.GetEncodedLength(value.Length);
|
||||||
|
Span<byte> buffer = encodedLength <= 256 ? stackalloc byte[encodedLength] : new byte[encodedLength];
|
||||||
|
Base64Url.EncodeToUtf8(value, buffer);
|
||||||
|
writer.WriteStringValue(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ public class Realm
|
||||||
public record RealmDek
|
public record RealmDek
|
||||||
{
|
{
|
||||||
public required DekId Id { get; init; }
|
public required DekId Id { get; init; }
|
||||||
public required bool Active { get; init; }
|
public required bool Active { get; set; }
|
||||||
public required string Algorithm { get; init; }
|
public required string Algorithm { get; init; }
|
||||||
public required EncryptedDek KeyData { get; init; }
|
public required EncryptedDek KeyData { get; init; }
|
||||||
public required Guid RealmId { get; init; }
|
public required Guid RealmId { get; init; }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue