Encrypt realm data with dek which is encrypted with kek. The signing keys are also encrypted with the kek.
This commit is contained in:
parent
644b005f2a
commit
650fe99990
36 changed files with 399 additions and 129 deletions
|
|
@ -28,13 +28,13 @@ public class ConfigurationSecretProviderTests
|
|||
"secrets": {
|
||||
"master": [
|
||||
{
|
||||
"Id": "first",
|
||||
"Id": "5676d159-5495-4945-aa84-59ee694aa8a2",
|
||||
"Active": true,
|
||||
"Algorithm": "AES",
|
||||
"Key": "yoQ4W7EaNjo7s3FBYkWo5BLyX1BnLyWd7BlSaDIrkzo="
|
||||
},
|
||||
{
|
||||
"Id": "second",
|
||||
"Id": "b82489e7-a05a-4d64-b9a5-58d2f2c0dc39",
|
||||
"Active": false,
|
||||
"Algorithm": "AES",
|
||||
"Key": "YSWK6vTJXCJOGLpCo+TtZ6anKNzvA1VT2xXLHbmq4M0="
|
||||
|
|
@ -47,15 +47,17 @@ public class ConfigurationSecretProviderTests
|
|||
|
||||
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("first", active.Id);
|
||||
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("second", inactive.Id);
|
||||
Assert.Equal(new Guid("b82489e7-a05a-4d64-b9a5-58d2f2c0dc39"), inactive.Id.Id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Services;
|
||||
using IdentityShroud.Core.Tests.Fixtures;
|
||||
using IdentityShroud.TestUtils.Substitutes;
|
||||
|
|
@ -10,12 +11,17 @@ namespace IdentityShroud.Core.Tests.Services;
|
|||
public class ClientServiceTests : IClassFixture<DbFixture>
|
||||
{
|
||||
private readonly DbFixture _dbFixture;
|
||||
private readonly IEncryptionService _encryptionService = EncryptionServiceSubstitute.CreatePassthrough();
|
||||
//private readonly IDekEncryptionService _dekEncryptionService = EncryptionServiceSubstitute.CreatePassthrough();
|
||||
private readonly IDataEncryptionService _dataEncryptionService = Substitute.For<IDataEncryptionService>();
|
||||
|
||||
private readonly IClock _clock = Substitute.For<IClock>();
|
||||
private readonly Guid _realmId = new("a1b2c3d4-0000-0000-0000-000000000001");
|
||||
|
||||
public ClientServiceTests(DbFixture dbFixture)
|
||||
{
|
||||
_dataEncryptionService.Encrypt(Arg.Any<ReadOnlyMemory<byte>>())
|
||||
.Returns(x => new EncryptedValue(DekId.NewId(), x.ArgAt<ReadOnlyMemory<byte>>(0).ToArray()));
|
||||
|
||||
_dbFixture = dbFixture;
|
||||
using Db db = dbFixture.CreateDbContext();
|
||||
if (!db.Database.EnsureCreated())
|
||||
|
|
@ -51,7 +57,7 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
await using (var db = _dbFixture.CreateDbContext())
|
||||
{
|
||||
// Act
|
||||
ClientService sut = new(db, _encryptionService, _clock);
|
||||
ClientService sut = new(db, _dataEncryptionService, _clock);
|
||||
var response = await sut.Create(
|
||||
_realmId,
|
||||
new ClientCreateRequest
|
||||
|
|
@ -107,7 +113,7 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
|
||||
await using var actContext = _dbFixture.CreateDbContext();
|
||||
// Act
|
||||
ClientService sut = new(actContext, _encryptionService, _clock);
|
||||
ClientService sut = new(actContext, _dataEncryptionService, _clock);
|
||||
Client? result = await sut.GetByClientId(_realmId, clientId, TestContext.Current.CancellationToken);
|
||||
|
||||
// Verify
|
||||
|
|
@ -142,7 +148,7 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
|
||||
await using var actContext = _dbFixture.CreateDbContext();
|
||||
// Act
|
||||
ClientService sut = new(actContext, _encryptionService, _clock);
|
||||
ClientService sut = new(actContext, _dataEncryptionService, _clock);
|
||||
Client? result = await sut.FindById(_realmId, searchId, TestContext.Current.CancellationToken);
|
||||
|
||||
// Verify
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Services;
|
||||
|
||||
namespace IdentityShroud.Core.Tests.Services;
|
||||
|
||||
public class EncryptionServiceTests
|
||||
public class DekEncryptionServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void RoundtripWorks()
|
||||
|
|
@ -13,9 +14,9 @@ public class EncryptionServiceTests
|
|||
// setup
|
||||
byte[] keyValue = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw=");
|
||||
var secretProvider = Substitute.For<ISecretProvider>();
|
||||
EncryptionKey[] keys =
|
||||
KeyEncryptionKey[] keys =
|
||||
[
|
||||
new EncryptionKey("1", true, "AES", keyValue)
|
||||
new KeyEncryptionKey(KekId.NewId(), true, "AES", keyValue)
|
||||
];
|
||||
secretProvider.GetKeys("master").Returns(keys);
|
||||
|
||||
|
|
@ -23,68 +24,38 @@ public class EncryptionServiceTests
|
|||
ReadOnlySpan<byte> input = "Hello, World!"u8;
|
||||
|
||||
// act
|
||||
EncryptionService sut = new(secretProvider);
|
||||
EncryptedValue cipher = sut.Encrypt(input.ToArray());
|
||||
DekEncryptionService sut = new(secretProvider);
|
||||
EncryptedDek cipher = sut.Encrypt(input.ToArray());
|
||||
byte[] result = sut.Decrypt(cipher);
|
||||
|
||||
// verify
|
||||
Assert.Equal(input, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DecodeV1_Success()
|
||||
{
|
||||
// When introducing a new version we need version specific tests to
|
||||
// make sure decoding of legacy data still works.
|
||||
|
||||
// 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("kid", cipher);
|
||||
|
||||
byte[] keyValue = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw=");
|
||||
var secretProvider = Substitute.For<ISecretProvider>();
|
||||
EncryptionKey[] keys =
|
||||
[
|
||||
new EncryptionKey("kid", true, "AES", keyValue)
|
||||
];
|
||||
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 DetectsCorruptInput()
|
||||
{
|
||||
// When introducing a new version we need version specific tests to
|
||||
// make sure decoding of legacy data still works.
|
||||
|
||||
KekId kid = KekId.NewId();
|
||||
// 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);
|
||||
EncryptedDek secret = new(kid, cipher);
|
||||
|
||||
byte[] keyValue = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw=");
|
||||
var secretProvider = Substitute.For<ISecretProvider>();
|
||||
EncryptionKey[] keys =
|
||||
KeyEncryptionKey[] keys =
|
||||
[
|
||||
new EncryptionKey("kid", true, "AES", keyValue)
|
||||
new KeyEncryptionKey(kid, true, "AES", keyValue)
|
||||
];
|
||||
secretProvider.GetKeys("master").Returns(keys);
|
||||
|
||||
// act
|
||||
EncryptionService sut = new(secretProvider);
|
||||
DekEncryptionService sut = new(secretProvider);
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => sut.Decrypt(secret),
|
||||
ex => ex.Message.Contains("Decryption failed") ? null : "Expected Decryption failed in message");
|
||||
|
|
@ -96,25 +67,28 @@ public class EncryptionServiceTests
|
|||
// The key is marked inactive also it is the second key
|
||||
|
||||
// setup
|
||||
KekId kid1 = KekId.NewId();
|
||||
KekId kid2 = KekId.NewId();
|
||||
|
||||
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);
|
||||
EncryptedDek secret = new(kid1, cipher);
|
||||
|
||||
byte[] keyValue1 = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw=");
|
||||
byte[] keyValue2 = Convert.FromBase64String("Dat1RwRvuLX3wdKMMP4NwHdBl8tJJsKfp01qikyo8aw=");
|
||||
var secretProvider = Substitute.For<ISecretProvider>();
|
||||
EncryptionKey[] keys =
|
||||
KeyEncryptionKey[] keys =
|
||||
[
|
||||
new EncryptionKey("2", true, "AES", keyValue2),
|
||||
new EncryptionKey("1", false, "AES", keyValue1),
|
||||
new KeyEncryptionKey(kid2, true, "AES", keyValue2),
|
||||
new KeyEncryptionKey(kid1, false, "AES", keyValue1),
|
||||
];
|
||||
secretProvider.GetKeys("master").Returns(keys);
|
||||
|
||||
// act
|
||||
EncryptionService sut = new(secretProvider);
|
||||
DekEncryptionService sut = new(secretProvider);
|
||||
byte[] result = sut.Decrypt(secret);
|
||||
|
||||
// verify
|
||||
|
|
@ -125,22 +99,25 @@ public class EncryptionServiceTests
|
|||
public void EncryptionUsesActiveKey()
|
||||
{
|
||||
// setup
|
||||
KekId kid1 = KekId.NewId();
|
||||
KekId kid2 = KekId.NewId();
|
||||
|
||||
byte[] keyValue1 = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw=");
|
||||
byte[] keyValue2 = Convert.FromBase64String("Dat1RwRvuLX3wdKMMP4NwHdBl8tJJsKfp01qikyo8aw=");
|
||||
var secretProvider = Substitute.For<ISecretProvider>();
|
||||
EncryptionKey[] keys =
|
||||
KeyEncryptionKey[] keys =
|
||||
[
|
||||
new EncryptionKey("1", false, "AES", keyValue1),
|
||||
new EncryptionKey("2", true, "AES", keyValue2),
|
||||
new KeyEncryptionKey(kid1, false, "AES", keyValue1),
|
||||
new KeyEncryptionKey(kid2, true, "AES", keyValue2),
|
||||
];
|
||||
secretProvider.GetKeys("master").Returns(keys);
|
||||
|
||||
ReadOnlySpan<byte> input = "Hello, World!"u8;
|
||||
// act
|
||||
EncryptionService sut = new(secretProvider);
|
||||
EncryptedValue cipher = sut.Encrypt(input.ToArray());
|
||||
DekEncryptionService sut = new(secretProvider);
|
||||
EncryptedDek cipher = sut.Encrypt(input.ToArray());
|
||||
|
||||
// Verify
|
||||
Assert.Equal("2", cipher.KeyId);
|
||||
Assert.Equal(kid2, cipher.KekId);
|
||||
}
|
||||
}
|
||||
30
IdentityShroud.Core.Tests/Services/EncryptionTests.cs
Normal file
30
IdentityShroud.Core.Tests/Services/EncryptionTests.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Services;
|
||||
|
||||
namespace IdentityShroud.Core.Tests.Services;
|
||||
|
||||
public class EncryptionTests
|
||||
{
|
||||
[Fact]
|
||||
public void DecodeV1_Success()
|
||||
{
|
||||
// When introducing a new version we need version specific tests to
|
||||
// make sure decoding of legacy data still works.
|
||||
|
||||
// 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
|
||||
];
|
||||
byte[] keyValue = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw=");
|
||||
|
||||
// act
|
||||
byte[] result = Encryption.Decrypt(cipher, keyValue);
|
||||
|
||||
// verify
|
||||
Assert.Equal("Hello, World!"u8, result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Security.Keys;
|
||||
using IdentityShroud.Core.Services;
|
||||
using IdentityShroud.Core.Tests.Fixtures;
|
||||
|
|
@ -43,7 +44,7 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
{
|
||||
Id = Guid.NewGuid(),
|
||||
KeyType = "TST",
|
||||
Key = new("kid", [21]),
|
||||
Key = new(KekId.NewId(), [21]),
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ public class UnitTest1
|
|||
|
||||
// Option 3: Generate a new key for testing
|
||||
rsa.KeySize = 2048;
|
||||
|
||||
// Your already encoded header and payload
|
||||
string header = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJybVZ3TU5rM0o1WHlmMWhyS3NVbEVYN1BNUm42dlZKY0h3U3FYMUVQRnFJIn0";
|
||||
string payload = "eyJleHAiOjE3Njk5MzY5MDksImlhdCI6MTc2OTkzNjYwOSwianRpIjoiMjNiZDJmNjktODdhYi00YmM2LWE0MWQtZGZkNzkxNDc4ZDM0IiwiaXNzIjoiaHR0cHM6Ly9pYW0ua2Fzc2FjbG91ZC5ubC9hdXRoL3JlYWxtcy9tcGx1c2thc3NhIiwiYXVkIjpbImthc3NhLW1hbmFnZW1lbnQtc2VydmljZSIsImFwYWNoZTItaW50cmFuZXQtYXV0aCIsImFjY291bnQiXSwic3ViIjoiMDkzY2NmMTUtYzRhOS00YWI0LTk3MWYtZDVhMDIyMzZkODVhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoibXBvYmFja2VuZCIsInNpZCI6IjI2NmUyNjJiLTU5NjMtNDUyZi04ZTI3LWIwZTkzMjBkNTZkNiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLW1wbHVza2Fzc2EiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZGVhbGVyLW1lZGV3ZXJrZXItcm9sZSIsIm1wbHVza2Fzc2EtbWVkZXdlcmtlci1yb2xlIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYXBhY2hlMi1pbnRyYW5ldC1hdXRoIjp7InJvbGVzIjpbImludHJhbmV0IiwicmVsZWFzZW5vdGVzX3dyaXRlIl19LCJrYXNzYS1tYW5hZ2VtZW50LXNlcnZpY2UiOnsicm9sZXMiOlsicG9zYWNjb3VudF9wYXNzd29yZHJlc2V0IiwiZHJhZnRfbGljZW5zZV93cml0ZSIsImxpY2Vuc2VfcmVhZCIsImtub3dsZWRnZUl0ZW1fcmVhZCIsIm1haWxpbmdfcmVhZCIsIm1wbHVzYXBpX3JlYWQiLCJkYXRhYmFzZV91c2VyX3dyaXRlIiwiZW52aXJvbm1lbnRfd3JpdGUiLCJna3NfYXV0aGNvZGVfcmVhZCIsImVtcGxveWVlX3JlYWQiLCJkYXRhYmFzZV91c2VyX3JlYWQiLCJhcGlhY2NvdW50X3Bhc3N3b3JkcmVzZXQiLCJtcGx1c2FwaV93cml0ZSIsImVudmlyb25tZW50X3JlYWQiLCJrbm93bGVkZ2VJdGVtX3dyaXRlIiwiZGF0YWJhc2VfdXNlcl9wYXNzd29yZF9yZWFkIiwibGljZW5zZV93cml0ZSIsImN1c3RvbWVyX3dyaXRlIiwiZGVhbGVyX3JlYWQiLCJlbXBsb3llZV93cml0ZSIsImRhdGFiYXNlX2NvbmZpZ3VyYXRpb25fd3JpdGUiLCJyZWxhdGlvbnNfcmVhZCIsImRhdGFiYXNlX3VzZXJfcGFzc3dvcmRfbXBsdXNfZW5jcnlwdGVkX3JlYWQiLCJkcmFmdF9saWNlbnNlX3JlYWQiLCJkYXRhYmFzZV9jb25maWd1cmF0aW9uX3JlYWQiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoia21zIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZGVhbGVySWQiOjEsIm5hbWUiOiJFZWxrZSBLbGVpbiIsInByZWZlcnJlZF91c2VybmFtZSI6ImVlbGtlQGJvbHQubmwiLCJsb2NhbGUiOiJlbiIsImdpdmVuX25hbWUiOiJFZWxrZSIsImZhbWlseV9uYW1lIjoiS2xlaW4iLCJlbWFpbCI6ImVlbGtlQGJvbHQubmwiLCJlbXBsb3llZU51bWJlciI6NTR9";
|
||||
|
|
@ -51,6 +50,15 @@ public class UnitTest1
|
|||
// Or generate complete JWT
|
||||
// string completeJwt = JwtSignatureGenerator.GenerateCompleteJwt(header, payload, rsa);
|
||||
// Console.WriteLine($"Complete JWT: {completeJwt}");
|
||||
|
||||
rsa.ExportRSAPublicKey(); // PKCS#1
|
||||
}
|
||||
|
||||
using (ECDsa dsa = ECDsa.Create())
|
||||
{
|
||||
dsa.ExportPkcs8PrivateKey();
|
||||
|
||||
dsa.ExportSubjectPublicKeyInfo(); // x509
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue