5-improve-encrypted-storage (#6)

Added the use of DEK's for encryption of secrets. Both the KEK's and DEK's are stored in a way that you can have multiple key of which one is active. But the others are still available for decrypting. This allows for implementing key rotation.

Co-authored-by: eelke <eelke@eelkeklein.nl>
Co-authored-by: Eelke76 <31384324+Eelke76@users.noreply.github.com>
Reviewed-on: #6
This commit is contained in:
eelke 2026-02-27 17:57:42 +00:00
parent 138f335af0
commit 07393f57fc
87 changed files with 1903 additions and 533 deletions

View file

@ -1,21 +0,0 @@
using System.Security.Cryptography;
using System.Text;
using IdentityShroud.Core.Security;
namespace IdentityShroud.Core.Tests.Security;
public class AesGcmHelperTests
{
[Fact]
public void EncryptDecryptCycleWorks()
{
string input = "Hello, world!";
var encryptionKey = RandomNumberGenerator.GetBytes(32);
var cypher = AesGcmHelper.EncryptAesGcm(Encoding.UTF8.GetBytes(input), encryptionKey);
var output = AesGcmHelper.DecryptAesGcm(cypher, encryptionKey);
Assert.Equal(input, Encoding.UTF8.GetString(output));
}
}

View file

@ -0,0 +1,63 @@
using System.Text;
using IdentityShroud.Core.Security;
using Microsoft.Extensions.Configuration;
namespace IdentityShroud.Core.Tests.Security;
public class ConfigurationSecretProviderTests
{
private static IConfiguration BuildConfigFromJson(string json)
{
// Convert the JSON string into a stream that the config builder can read.
var jsonBytes = Encoding.UTF8.GetBytes(json);
using var stream = new MemoryStream(jsonBytes);
// Build the configuration just like the real app does, but from the stream.
var config = new ConfigurationBuilder()
.AddJsonStream(stream) // <-- reads from the inmemory JSON
.Build();
return config;
}
[Fact]
public void Test()
{
string jsonConfig = """
{
"secrets": {
"master": [
{
"Id": "5676d159-5495-4945-aa84-59ee694aa8a2",
"Active": true,
"Algorithm": "AES",
"Key": "yoQ4W7EaNjo7s3FBYkWo5BLyX1BnLyWd7BlSaDIrkzo="
},
{
"Id": "b82489e7-a05a-4d64-b9a5-58d2f2c0dc39",
"Active": false,
"Algorithm": "AES",
"Key": "YSWK6vTJXCJOGLpCo+TtZ6anKNzvA1VT2xXLHbmq4M0="
}
]
}
}
""";
ConfigurationSecretProvider sut = new(BuildConfigFromJson(jsonConfig));
// act
var keys = sut.GetKeys("master");
// verify
Assert.Equal(2, keys.Length);
var active = keys.Single(k => k.Active);
Assert.Equal(new Guid("5676d159-5495-4945-aa84-59ee694aa8a2"), active.Id.Id);
Assert.Equal("AES", active.Algorithm);
Assert.Equal(Convert.FromBase64String("yoQ4W7EaNjo7s3FBYkWo5BLyX1BnLyWd7BlSaDIrkzo="), active.Key);
var inactive = keys.Single(k => !k.Active);
Assert.Equal(new Guid("b82489e7-a05a-4d64-b9a5-58d2f2c0dc39"), inactive.Id.Id);
}
}