Support rotation of master key.

The EncryptionService now loads a set of keys and uses the active one to encrypt and selects key based on keyid during decryption. Introduced EncryptedValue to hold keyId and encrypted data.

(There are no intermeddiate keys yet)
This commit is contained in:
eelke 2026-02-24 06:32:58 +01:00
parent 4201d0240d
commit 644b005f2a
19 changed files with 259 additions and 72 deletions

View file

@ -0,0 +1,61 @@
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": "first",
"Active": true,
"Algorithm": "AES",
"Key": "yoQ4W7EaNjo7s3FBYkWo5BLyX1BnLyWd7BlSaDIrkzo="
},
{
"Id": "second",
"Active": false,
"Algorithm": "AES",
"Key": "YSWK6vTJXCJOGLpCo+TtZ6anKNzvA1VT2xXLHbmq4M0="
}
]
}
}
""";
ConfigurationSecretProvider sut = new(BuildConfigFromJson(jsonConfig));
var keys = sut.GetKeys("master");
Assert.Equal(2, keys.Length);
var active = keys.Single(k => k.Active);
Assert.Equal("first", active.Id);
Assert.Equal("AES", active.Algorithm);
Assert.Equal(Convert.FromBase64String("yoQ4W7EaNjo7s3FBYkWo5BLyX1BnLyWd7BlSaDIrkzo="), active.Key);
var inactive = keys.Single(k => !k.Active);
Assert.Equal("second", inactive.Id);
}
}