diff --git a/IdentityShroud.Api.Tests/Apis/RealmApisTests.cs b/IdentityShroud.Api.Tests/Apis/RealmApisTests.cs index 8d08a27..a91ea62 100644 --- a/IdentityShroud.Api.Tests/Apis/RealmApisTests.cs +++ b/IdentityShroud.Api.Tests/Apis/RealmApisTests.cs @@ -114,7 +114,7 @@ public class RealmApisTests : IClassFixture { // act var client = _factory.CreateClient(); - var response = await client.GetAsync("/realms/bar/.well-known/openid-configuration", + var response = await client.GetAsync($"/realms/{slug}/.well-known/openid-configuration", TestContext.Current.CancellationToken); // verify @@ -130,18 +130,20 @@ public class RealmApisTests : IClassFixture using var rsa = RSA.Create(2048); RSAParameters parameters = rsa.ExportParameters(includePrivateParameters: false); - RealmKey realmKey = new( - Guid.NewGuid(), - "RSA", - encryptionService.Encrypt(rsa.ExportPkcs8PrivateKey()), - DateTime.UtcNow); + RealmKey realmKey = new() + { + Id = Guid.NewGuid(), + KeyType = "RSA", + Key = encryptionService.Encrypt(rsa.ExportPkcs8PrivateKey()), + CreatedAt = DateTime.UtcNow, + }; await ScopedContextAsync(async db => { db.Realms.Add(new Realm() { Slug = "foo", Name = "Foo", Keys = [ realmKey ]}); await db.SaveChangesAsync(TestContext.Current.CancellationToken); }); - + // act var client = _factory.CreateClient(); var response = await client.GetAsync("/auth/realms/foo/openid-connect/jwks", diff --git a/IdentityShroud.Api.Tests/Fixtures/ApplicationFactory.cs b/IdentityShroud.Api.Tests/Fixtures/ApplicationFactory.cs index 2a2ae76..2a2be31 100644 --- a/IdentityShroud.Api.Tests/Fixtures/ApplicationFactory.cs +++ b/IdentityShroud.Api.Tests/Fixtures/ApplicationFactory.cs @@ -28,7 +28,10 @@ public class ApplicationFactory : WebApplicationFactory, IAsyncLifetime new Dictionary { ["Db:ConnectionString"] = _postgresqlServer.GetConnectionString(), - ["secrets:Master"] = "GVd07qW0frRX9quPX/X62L88BeRR7+IzgRJHtG7ZzHw=", + ["secrets:master:0:Id"] = "key1", + ["secrets:master:0:Active"] = "true", + ["secrets:master:0:Algorithm"] = "AES", + ["secrets:master:0:Key"] = "GVd07qW0frRX9quPX/X62L88BeRR7+IzgRJHtG7ZzHw=", }); }); diff --git a/IdentityShroud.Api.Tests/Mappers/KeyServiceTests.cs b/IdentityShroud.Api.Tests/Mappers/KeyServiceTests.cs index 196b15d..0df74a3 100644 --- a/IdentityShroud.Api.Tests/Mappers/KeyServiceTests.cs +++ b/IdentityShroud.Api.Tests/Mappers/KeyServiceTests.cs @@ -21,12 +21,12 @@ public class KeyServiceTests RSAParameters parameters = rsa.ExportParameters(includePrivateParameters: false); - RealmKey realmKey = new( - new("60bb79cf-4bac-4521-87f2-ac87cc15541f"), - "RSA", - rsa.ExportPkcs8PrivateKey(), - DateTime.UtcNow) + RealmKey realmKey = new() { + Id = new("60bb79cf-4bac-4521-87f2-ac87cc15541f"), + KeyType = "RSA", + Key = new("", rsa.ExportPkcs8PrivateKey()), + CreatedAt = DateTime.UtcNow, Priority = 10, }; @@ -34,10 +34,11 @@ public class KeyServiceTests KeyService sut = new(_encryptionService, new KeyProviderFactory(), new ClockService()); var jwk = sut.CreateJsonWebKey(realmKey); + Assert.NotNull(jwk); Assert.Equal("RSA", jwk.KeyType); Assert.Equal(realmKey.Id.ToString(), jwk.KeyId); Assert.Equal("sig", jwk.Use); Assert.Equal(parameters.Exponent, Base64Url.DecodeFromChars(jwk.Exponent)); Assert.Equal(parameters.Modulus, Base64Url.DecodeFromChars(jwk.Modulus)); } -} \ No newline at end of file +} diff --git a/IdentityShroud.Api/IdentityShroud.Api.csproj b/IdentityShroud.Api/IdentityShroud.Api.csproj index 860fbeb..31f88b2 100644 --- a/IdentityShroud.Api/IdentityShroud.Api.csproj +++ b/IdentityShroud.Api/IdentityShroud.Api.csproj @@ -17,7 +17,6 @@ - diff --git a/IdentityShroud.Core.Tests/Security/ConfigurationSecretProviderTests.cs b/IdentityShroud.Core.Tests/Security/ConfigurationSecretProviderTests.cs new file mode 100644 index 0000000..180732b --- /dev/null +++ b/IdentityShroud.Core.Tests/Security/ConfigurationSecretProviderTests.cs @@ -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 in‑memory 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); + } +} \ No newline at end of file diff --git a/IdentityShroud.Core.Tests/Services/EncryptionServiceTests.cs b/IdentityShroud.Core.Tests/Services/EncryptionServiceTests.cs index 68ab90d..7a7be2c 100644 --- a/IdentityShroud.Core.Tests/Services/EncryptionServiceTests.cs +++ b/IdentityShroud.Core.Tests/Services/EncryptionServiceTests.cs @@ -1,5 +1,3 @@ -using System.Buffers.Text; -using System.Security.Cryptography; using IdentityShroud.Core.Contracts; using IdentityShroud.Core.Services; @@ -11,16 +9,22 @@ public class EncryptionServiceTests public void RoundtripWorks() { // Note this code will tend to only test the latest verion. - + // setup + byte[] keyValue = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw="); var secretProvider = Substitute.For(); - secretProvider.GetSecret("Master").Returns("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw="); + EncryptionKey[] keys = + [ + new EncryptionKey("1", true, "AES", keyValue) + ]; + secretProvider.GetKeys("master").Returns(keys); + ReadOnlySpan input = "Hello, World!"u8; // act EncryptionService sut = new(secretProvider); - byte[] cipher = sut.Encrypt(input.ToArray()); + EncryptedValue cipher = sut.Encrypt(input.ToArray()); byte[] result = sut.Decrypt(cipher); // verify @@ -34,19 +38,109 @@ public class EncryptionServiceTests // make sure decoding of legacy data still works. // setup - Span cipher = + byte[] cipher = [ 1, 198, 55, 58, 56, 110, 238, 59, 158, 214, 85, 241, 26, 44, 140, 229, 128, 111, 167, 154, 160, 177, 152, 193, 74, 4, 235, 82, 207, 87, 32, 10, 239, 4, 246, 25, 21, 249, 25, 59, 160, 101 ]; + EncryptedValue secret = new("kid", cipher); + + byte[] keyValue = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw="); var secretProvider = Substitute.For(); - secretProvider.GetSecret("Master").Returns("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw="); + EncryptionKey[] keys = + [ + new EncryptionKey("kid", true, "AES", keyValue) + ]; + secretProvider.GetKeys("master").Returns(keys); // act EncryptionService sut = new(secretProvider); - byte[] result = sut.Decrypt(cipher.ToArray()); + byte[] result = sut.Decrypt(secret); // verify Assert.Equal("Hello, World!"u8, result); } + + [Fact] + public void DetectsCorruptInput() + { + // When introducing a new version we need version specific tests to + // make sure decoding of legacy data still works. + + // setup + byte[] cipher = // NOTE INCORRECT CIPHER DO NOT USE IN OTHER TESTS + [ + 1, 198, 55, 58, 56, 110, 238, 59, 158, 214, 85, 241, 26, 44, 140, 229, 128, 111, 167, 154, 160, 177, 152, + 193, 75, 4, 235, 82, 207, 87, 32, 10, 239, 4, 246, 25, 21, 249, 25, 59, 160, 101 + ]; + EncryptedValue secret = new("kid", cipher); + + byte[] keyValue = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw="); + var secretProvider = Substitute.For(); + EncryptionKey[] keys = + [ + new EncryptionKey("kid", true, "AES", keyValue) + ]; + secretProvider.GetKeys("master").Returns(keys); + + // act + EncryptionService sut = new(secretProvider); + Assert.Throws( + () => sut.Decrypt(secret), + ex => ex.Message.Contains("Decryption failed") ? null : "Expected Decryption failed in message"); + } + + [Fact] + public void DecodeSelectsRightKey() + { + // The key is marked inactive also it is the second key + + // setup + byte[] cipher = + [ + 1, 198, 55, 58, 56, 110, 238, 59, 158, 214, 85, 241, 26, 44, 140, 229, 128, 111, 167, 154, 160, 177, 152, + 193, 74, 4, 235, 82, 207, 87, 32, 10, 239, 4, 246, 25, 21, 249, 25, 59, 160, 101 + ]; + EncryptedValue secret = new("1", cipher); + + byte[] keyValue1 = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw="); + byte[] keyValue2 = Convert.FromBase64String("Dat1RwRvuLX3wdKMMP4NwHdBl8tJJsKfp01qikyo8aw="); + var secretProvider = Substitute.For(); + EncryptionKey[] keys = + [ + new EncryptionKey("2", true, "AES", keyValue2), + new EncryptionKey("1", false, "AES", keyValue1), + ]; + secretProvider.GetKeys("master").Returns(keys); + + // act + EncryptionService sut = new(secretProvider); + byte[] result = sut.Decrypt(secret); + + // verify + Assert.Equal("Hello, World!"u8, result); + } + + [Fact] + public void EncryptionUsesActiveKey() + { + // setup + byte[] keyValue1 = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw="); + byte[] keyValue2 = Convert.FromBase64String("Dat1RwRvuLX3wdKMMP4NwHdBl8tJJsKfp01qikyo8aw="); + var secretProvider = Substitute.For(); + EncryptionKey[] keys = + [ + new EncryptionKey("1", false, "AES", keyValue1), + new EncryptionKey("2", true, "AES", keyValue2), + ]; + secretProvider.GetKeys("master").Returns(keys); + + ReadOnlySpan input = "Hello, World!"u8; + // act + EncryptionService sut = new(secretProvider); + EncryptedValue cipher = sut.Encrypt(input.ToArray()); + + // Verify + Assert.Equal("2", cipher.KeyId); + } } \ No newline at end of file diff --git a/IdentityShroud.Core.Tests/Services/RealmServiceTests.cs b/IdentityShroud.Core.Tests/Services/RealmServiceTests.cs index acbc3bf..ea34ca8 100644 --- a/IdentityShroud.Core.Tests/Services/RealmServiceTests.cs +++ b/IdentityShroud.Core.Tests/Services/RealmServiceTests.cs @@ -39,7 +39,13 @@ public class RealmServiceTests : IClassFixture await using (var db = _dbFixture.CreateDbContext()) { _keyService.CreateKey(Arg.Any()) - .Returns(new RealmKey(Guid.NewGuid(), "TST", [21], DateTime.UtcNow)); + .Returns(new RealmKey() + { + Id = Guid.NewGuid(), + KeyType = "TST", + Key = new("kid", [21]), + CreatedAt = DateTime.UtcNow + }); // Act RealmService sut = new(db, _keyService); var response = await sut.Create( diff --git a/IdentityShroud.Core/Contracts/IEncryptionService.cs b/IdentityShroud.Core/Contracts/IEncryptionService.cs index 388304b..2fa7e9c 100644 --- a/IdentityShroud.Core/Contracts/IEncryptionService.cs +++ b/IdentityShroud.Core/Contracts/IEncryptionService.cs @@ -2,6 +2,6 @@ namespace IdentityShroud.Core.Contracts; public interface IEncryptionService { - byte[] Encrypt(ReadOnlyMemory plain); - byte[] Decrypt(ReadOnlyMemory cipher); + EncryptedValue Encrypt(ReadOnlyMemory plain); + byte[] Decrypt(EncryptedValue input); } \ No newline at end of file diff --git a/IdentityShroud.Core/Contracts/ISecretProvider.cs b/IdentityShroud.Core/Contracts/ISecretProvider.cs index 2a8e9e6..a586fe7 100644 --- a/IdentityShroud.Core/Contracts/ISecretProvider.cs +++ b/IdentityShroud.Core/Contracts/ISecretProvider.cs @@ -3,4 +3,10 @@ namespace IdentityShroud.Core.Contracts; public interface ISecretProvider { string GetSecret(string name); + + /// + /// Should return one active key, might return inactive keys. + /// + /// + EncryptionKey[] GetKeys(string name); } diff --git a/IdentityShroud.Core/Model/ClientSecret.cs b/IdentityShroud.Core/Model/ClientSecret.cs index bd57d37..0b0122d 100644 --- a/IdentityShroud.Core/Model/ClientSecret.cs +++ b/IdentityShroud.Core/Model/ClientSecret.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using IdentityShroud.Core.Contracts; namespace IdentityShroud.Core.Model; @@ -11,5 +12,5 @@ public class ClientSecret public Guid ClientId { get; set; } public DateTime CreatedAt { get; set; } public DateTime? RevokedAt { get; set; } - public required byte[] SecretEncrypted { get; set; } + public required EncryptedValue Secret { get; set; } } \ No newline at end of file diff --git a/IdentityShroud.Core/Model/RealmKey.cs b/IdentityShroud.Core/Model/RealmKey.cs index 14c7c9c..038f853 100644 --- a/IdentityShroud.Core/Model/RealmKey.cs +++ b/IdentityShroud.Core/Model/RealmKey.cs @@ -1,15 +1,19 @@ using System.ComponentModel.DataAnnotations.Schema; +using IdentityShroud.Core.Contracts; +using Microsoft.EntityFrameworkCore; namespace IdentityShroud.Core.Model; [Table("realm_key")] -public record RealmKey(Guid Id, string KeyType, byte[] KeyDataEncrypted, DateTime CreatedAt) +public record RealmKey { - public Guid Id { get; private set; } = Id; - public string KeyType { get; private set; } = KeyType; - public byte[] KeyDataEncrypted { get; private set; } = KeyDataEncrypted; - public DateTime CreatedAt { get; private set; } = CreatedAt; + public required Guid Id { get; init; } + public required string KeyType { get; init; } + + + public required EncryptedValue Key { get; init; } + public required DateTime CreatedAt { get; init; } public DateTime? RevokedAt { get; set; } /// diff --git a/IdentityShroud.Core/Security/ConfigurationSecretProvider.cs b/IdentityShroud.Core/Security/ConfigurationSecretProvider.cs index ab77ef1..dd616b1 100644 --- a/IdentityShroud.Core/Security/ConfigurationSecretProvider.cs +++ b/IdentityShroud.Core/Security/ConfigurationSecretProvider.cs @@ -14,4 +14,9 @@ public class ConfigurationSecretProvider(IConfiguration configuration) : ISecret { return secrets.GetValue(name) ?? ""; } + + public EncryptionKey[] GetKeys(string name) + { + return secrets.GetSection(name).Get() ?? []; + } } \ No newline at end of file diff --git a/IdentityShroud.Core/Security/EncryptedValue.cs b/IdentityShroud.Core/Security/EncryptedValue.cs new file mode 100644 index 0000000..655ab13 --- /dev/null +++ b/IdentityShroud.Core/Security/EncryptedValue.cs @@ -0,0 +1,6 @@ +using Microsoft.EntityFrameworkCore; + +namespace IdentityShroud.Core.Contracts; + +[Owned] +public record EncryptedValue(string KeyId, byte[] Value); \ No newline at end of file diff --git a/IdentityShroud.Core/Security/EncryptionKey.cs b/IdentityShroud.Core/Security/EncryptionKey.cs new file mode 100644 index 0000000..2e857a1 --- /dev/null +++ b/IdentityShroud.Core/Security/EncryptionKey.cs @@ -0,0 +1,4 @@ +namespace IdentityShroud.Core.Contracts; + +// Contains an encryption key and associated relevant data +public record EncryptionKey(string Id, bool Active, string Algorithm, byte[] Key); \ No newline at end of file diff --git a/IdentityShroud.Core/Services/ClientService.cs b/IdentityShroud.Core/Services/ClientService.cs index ed85daf..e6b5c32 100644 --- a/IdentityShroud.Core/Services/ClientService.cs +++ b/IdentityShroud.Core/Services/ClientService.cs @@ -57,7 +57,7 @@ public class ClientService( return new ClientSecret() { CreatedAt = clock.UtcNow(), - SecretEncrypted = cryptor.Encrypt(secret), + Secret = cryptor.Encrypt(secret), }; } diff --git a/IdentityShroud.Core/Services/EncryptionService.cs b/IdentityShroud.Core/Services/EncryptionService.cs index 8aa5bed..a6b39c0 100644 --- a/IdentityShroud.Core/Services/EncryptionService.cs +++ b/IdentityShroud.Core/Services/EncryptionService.cs @@ -1,6 +1,5 @@ using System.Security.Cryptography; using IdentityShroud.Core.Contracts; -using IdentityShroud.Core.Security; namespace IdentityShroud.Core.Services; @@ -17,16 +16,21 @@ public class EncryptionService : IEncryptionService new (12, 16), // version 1 ]; - private readonly byte[] _encryptionKey; + // Note this array is expected to have one item in it most of the during key rotation it will have two + // until it is ensured the old key can safely be removed. More then two will work but is not really expected. + private readonly EncryptionKey[] _encryptionKeys; + + private EncryptionKey ActiveKey => _encryptionKeys.Single(k => k.Active); + private EncryptionKey GetKey(string keyId) => _encryptionKeys.Single(k => k.Id == keyId); public EncryptionService(ISecretProvider secretProvider) { - _encryptionKey = Convert.FromBase64String(secretProvider.GetSecret("Master")); - if (_encryptionKey.Length != 32) // 256‑bit key - throw new Exception("Key must be 256 bits (32 bytes) for AES‑256‑GCM."); + _encryptionKeys = secretProvider.GetKeys("master"); + // if (_encryptionKey.Length != 32) // 256‑bit key + // throw new Exception("Key must be 256 bits (32 bytes) for AES‑256‑GCM."); } - public byte[] Encrypt(ReadOnlyMemory plaintext) + public EncryptedValue Encrypt(ReadOnlyMemory plaintext) { const int versionNumber = 1; AlgVersion versionParams = _versions[versionNumber]; @@ -44,26 +48,21 @@ public class EncryptionService : IEncryptionService // use the spans to place the data directly in its place RandomNumberGenerator.Fill(nonce); - using var aes = new AesGcm(_encryptionKey, versionParams.TagSize); + var encryptionKey = ActiveKey; + using var aes = new AesGcm(encryptionKey.Key, versionParams.TagSize); aes.Encrypt(nonce, plaintext.Span, cipher, tag); - return result; + return new (encryptionKey.Id, result); } - public byte[] Decrypt(ReadOnlyMemory input) + public byte[] Decrypt(EncryptedValue input) { - - // ---------------------------------------------------------------- - // 1️⃣ Extract the three components. - // ---------------------------------------------------------------- - // AesGcm.NonceByteSizes.MaxSize = 12 bytes (standard GCM nonce length) - // AesGcm.TagByteSizes.MaxSize = 16 bytes (128‑bit authentication tag) - //int nonceSize = AesGcm.NonceByteSizes.MaxSize; // 12 - //int tagSize = AesGcm.TagByteSizes.MaxSize; // 16 - var payload = input.Span; + var encryptionKey = GetKey(input.KeyId); + + var payload = input.Value.AsSpan(); int versionNumber = (int)payload[0]; if (versionNumber != 1) - throw new ArgumentException("Invalid payloag"); + throw new ArgumentException("Invalid payload"); AlgVersion versionParams = _versions[versionNumber]; @@ -77,7 +76,7 @@ public class EncryptionService : IEncryptionService byte[] plaintext = new byte[cipher.Length]; - using var aes = new AesGcm(_encryptionKey, versionParams.TagSize); + using var aes = new AesGcm(encryptionKey.Key, versionParams.TagSize); try { aes.Decrypt(nonce, cipher, tag, plaintext); diff --git a/IdentityShroud.Core/Services/KeyService.cs b/IdentityShroud.Core/Services/KeyService.cs index 6c5e828..16af5a4 100644 --- a/IdentityShroud.Core/Services/KeyService.cs +++ b/IdentityShroud.Core/Services/KeyService.cs @@ -29,23 +29,18 @@ public class KeyService( IKeyProvider provider = keyProviderFactory.CreateProvider(realmKey.KeyType); provider.SetJwkParameters( - cryptor.Decrypt(realmKey.KeyDataEncrypted), + cryptor.Decrypt(realmKey.Key), jwk); return jwk; } private RealmKey CreateKey(string keyType, byte[] plainKey) => - new RealmKey( - Guid.NewGuid(), - keyType, - cryptor.Encrypt(plainKey), - clock.UtcNow()); - - // public byte[] GetPrivateKey(IEncryptionService encryptionService) - // { - // if (_privateKeyDecrypted.Length == 0 && PrivateKeyEncrypted.Length > 0) - // _privateKeyDecrypted = encryptionService.Decrypt(PrivateKeyEncrypted); - // return _privateKeyDecrypted; - // } + new RealmKey() + { + Id = Guid.NewGuid(), + KeyType = keyType, + Key = cryptor.Encrypt(plainKey), + CreatedAt = clock.UtcNow(), + }; } diff --git a/IdentityShroud.TestUtils/Substitutes/EncryptionServiceSubstitute.cs b/IdentityShroud.TestUtils/Substitutes/EncryptionServiceSubstitute.cs index 5a81240..36045ae 100644 --- a/IdentityShroud.TestUtils/Substitutes/EncryptionServiceSubstitute.cs +++ b/IdentityShroud.TestUtils/Substitutes/EncryptionServiceSubstitute.cs @@ -8,11 +8,11 @@ public static class EncryptionServiceSubstitute { var encryptionService = Substitute.For(); encryptionService - .Encrypt(Arg.Any()) - .Returns(x => x.ArgAt(0)); + .Encrypt(Arg.Any>()) + .Returns(x => new EncryptedValue("kid", x.ArgAt>(0).ToArray())); encryptionService - .Decrypt(Arg.Any>()) - .Returns(x => x.ArgAt>(0).ToArray()); + .Decrypt(Arg.Any()) + .Returns(x => x.ArgAt(0).Value); return encryptionService; } } \ No newline at end of file diff --git a/IdentityShroud.sln.DotSettings.user b/IdentityShroud.sln.DotSettings.user index d90a7ba..795f362 100644 --- a/IdentityShroud.sln.DotSettings.user +++ b/IdentityShroud.sln.DotSettings.user @@ -20,10 +20,10 @@ ForceIncluded ForceIncluded ForceIncluded - /home/eelke/.cache/JetBrains/Rider2025.3/resharper-host/temp/Rider/vAny/CoverageData/_IdentityShroud.-1277985570/Snapshot/snapshot.utdcvr + /home/eelke/.dotnet/dotnet /home/eelke/.dotnet/sdk/10.0.102/MSBuild.dll - <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Solution /> </SessionState> @@ -36,4 +36,5 @@ + \ No newline at end of file