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:
parent
4201d0240d
commit
644b005f2a
19 changed files with 259 additions and 72 deletions
|
|
@ -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<ISecretProvider>();
|
||||
secretProvider.GetSecret("Master").Returns("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw=");
|
||||
EncryptionKey[] keys =
|
||||
[
|
||||
new EncryptionKey("1", true, "AES", keyValue)
|
||||
];
|
||||
secretProvider.GetKeys("master").Returns(keys);
|
||||
|
||||
|
||||
ReadOnlySpan<byte> 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<byte> 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<ISecretProvider>();
|
||||
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<ISecretProvider>();
|
||||
EncryptionKey[] keys =
|
||||
[
|
||||
new EncryptionKey("kid", true, "AES", keyValue)
|
||||
];
|
||||
secretProvider.GetKeys("master").Returns(keys);
|
||||
|
||||
// act
|
||||
EncryptionService sut = new(secretProvider);
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => 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<ISecretProvider>();
|
||||
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<ISecretProvider>();
|
||||
EncryptionKey[] keys =
|
||||
[
|
||||
new EncryptionKey("1", false, "AES", keyValue1),
|
||||
new EncryptionKey("2", 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());
|
||||
|
||||
// Verify
|
||||
Assert.Equal("2", cipher.KeyId);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue