5-improve-encrypted-storage #6

Merged
eelke merged 17 commits from 5-improve-encrypted-storage into main 2026-02-27 17:57:44 +00:00
5 changed files with 130 additions and 26 deletions
Showing only changes of commit 1cd7fb659a - Show all commits

View 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));
}
}

View file

@ -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,
};
}

View file

@ -1,7 +1,5 @@
using System.Buffers;
using System.Buffers.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using IdentityShroud.Core.Helpers;
namespace IdentityShroud.Core.Messages;
@ -49,25 +47,3 @@ public class JsonWebKey
// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
// 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);
}
}

View 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);
}
}

View file

@ -33,7 +33,7 @@ public class Realm
public record RealmDek
{
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 EncryptedDek KeyData { get; init; }
public required Guid RealmId { get; init; }