From 2fcf4fcaef3008bca038a9412830a0304a3cd7f2 Mon Sep 17 00:00:00 2001 From: harrison314 Date: Sat, 12 Dec 2020 16:41:48 +0100 Subject: [PATCH] #3 Add tests to PKCS11 data provider. --- ...ison314.EntityFrameworkCore.Encryption.sln | 9 +- .../CryptoProviders/Pkcs11Data/DataInfo.cs | 29 +++ .../Pkcs11Data/Pkcs11DataGenerator.cs | 11 +- .../Pkcs11Data/Pkcs11DataProvider.cs | 57 ++-- .../Pkcs11Data/Pkcs11DataProviderOptions.cs | 2 +- .../EndpointsExtensions.cs | 104 ++++++++ .../Pkcs11Data/Pkcs11DataProviderTest.cs | 243 ++++++++++++++++++ ...4.EntityFrameworkCore.Contrib.Tests.csproj | 23 ++ 8 files changed, 445 insertions(+), 33 deletions(-) create mode 100644 src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/DataInfo.cs create mode 100644 src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/EndpointsExtensions.cs create mode 100644 src/test/Harrison314.EntityFrameworkCore.Contrib.Tests/CryptoProviders/Pkcs11Data/Pkcs11DataProviderTest.cs create mode 100644 src/test/Harrison314.EntityFrameworkCore.Contrib.Tests/Harrison314.EntityFrameworkCore.Contrib.Tests.csproj diff --git a/src/Harrison314.EntityFrameworkCore.Encryption.sln b/src/Harrison314.EntityFrameworkCore.Encryption.sln index 9fac550..370bef8 100644 --- a/src/Harrison314.EntityFrameworkCore.Encryption.sln +++ b/src/Harrison314.EntityFrameworkCore.Encryption.sln @@ -20,7 +20,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleWebApiProject", "samp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Harrison314.EntityFrameworkCore.Encryption.Tests", "test\Harrison314.EntityFrameworkCore.Encryption.Tests\Harrison314.EntityFrameworkCore.Encryption.Tests.csproj", "{1597ACD3-CB40-438F-8B84-8449A93930EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harrison314.EntityFrameworkCore.Encryption.Contrib", "src\Harrison314.EntityFrameworkCore.Encryption.Contrib\Harrison314.EntityFrameworkCore.Encryption.Contrib.csproj", "{97F417DA-B277-42F4-973E-2299711D5F41}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Harrison314.EntityFrameworkCore.Encryption.Contrib", "src\Harrison314.EntityFrameworkCore.Encryption.Contrib\Harrison314.EntityFrameworkCore.Encryption.Contrib.csproj", "{97F417DA-B277-42F4-973E-2299711D5F41}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harrison314.EntityFrameworkCore.Contrib.Tests", "test\Harrison314.EntityFrameworkCore.Contrib.Tests\Harrison314.EntityFrameworkCore.Contrib.Tests.csproj", "{EB712EF3-DC52-4209-9FC0-8CCA028EC8BE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -44,6 +46,10 @@ Global {97F417DA-B277-42F4-973E-2299711D5F41}.Debug|Any CPU.Build.0 = Debug|Any CPU {97F417DA-B277-42F4-973E-2299711D5F41}.Release|Any CPU.ActiveCfg = Release|Any CPU {97F417DA-B277-42F4-973E-2299711D5F41}.Release|Any CPU.Build.0 = Release|Any CPU + {EB712EF3-DC52-4209-9FC0-8CCA028EC8BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB712EF3-DC52-4209-9FC0-8CCA028EC8BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB712EF3-DC52-4209-9FC0-8CCA028EC8BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB712EF3-DC52-4209-9FC0-8CCA028EC8BE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -53,6 +59,7 @@ Global {074639A4-FF0F-482D-A2E0-2A48D12FB5EE} = {A968397F-6358-47B8-B0F1-793E669947F8} {1597ACD3-CB40-438F-8B84-8449A93930EE} = {9075C7DF-76A9-4080-ACDE-E650B0BA2E12} {97F417DA-B277-42F4-973E-2299711D5F41} = {BF96E918-3D85-4AB6-81C7-C70B85D08746} + {EB712EF3-DC52-4209-9FC0-8CCA028EC8BE} = {9075C7DF-76A9-4080-ACDE-E650B0BA2E12} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {824006CF-B329-40CA-B72C-CA9ED5C28DFA} diff --git a/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/DataInfo.cs b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/DataInfo.cs new file mode 100644 index 0000000..48f2abe --- /dev/null +++ b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/DataInfo.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Harrison314.EntityFrameworkCore.Encryption.Contrib.CryptoProviders.Pkcs11Data +{ + public struct DataInfo + { + public string Id + { + get; + private set; + } + + public string Label + { + get; + private set; + } + + public DataInfo(string id, string label) + { + this.Id = id; + this.Label = label; + } + } +} diff --git a/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataGenerator.cs b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataGenerator.cs index e61f5d8..5c191a9 100644 --- a/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataGenerator.cs +++ b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataGenerator.cs @@ -49,10 +49,10 @@ public Pkcs11DataGenerator(string pkcs11LibPath, string tokenLabel, SecureString }); } - public void GenerateDataObject(string label, string ckaId, int dataSize = 32) + public void GenerateDataObject(string application, string objectId, int dataSize = 32) { - if (label == null) throw new ArgumentNullException(nameof(label)); - if (ckaId == null) throw new ArgumentNullException(nameof(ckaId)); + if (application == null) throw new ArgumentNullException(nameof(application)); + if (objectId == null) throw new ArgumentNullException(nameof(objectId)); using ISession session = this.slot.OpenSession(SessionType.ReadWrite); @@ -71,9 +71,8 @@ public void GenerateDataObject(string label, string ckaId, int dataSize = 32) session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_DATA), session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true), session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PRIVATE, true), - session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId), - session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, label), - session.Factories.ObjectAttributeFactory.Create(CKA.CKA_APPLICATION, "Harrison314.Encryption"), + session.Factories.ObjectAttributeFactory.Create(CKA.CKA_OBJECT_ID, objectId), + session.Factories.ObjectAttributeFactory.Create(CKA.CKA_APPLICATION, application), session.Factories.ObjectAttributeFactory.Create(CKA.CKA_VALUE, data) }; diff --git a/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataProvider.cs b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataProvider.cs index 8238ef4..889f20d 100644 --- a/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataProvider.cs +++ b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataProvider.cs @@ -65,13 +65,13 @@ public async ValueTask DecryptMasterKey(MasterKeyData masterKeyData, Can session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_DATA), session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true), session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PRIVATE, true), - session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, masterKeyData.KeyId), + session.Factories.ObjectAttributeFactory.Create(CKA.CKA_OBJECT_ID, masterKeyData.KeyId), }; List findAttributesTempalte = new List() { - CKA.CKA_ID, - CKA.CKA_LABEL, + CKA.CKA_OBJECT_ID, + CKA.CKA_APPLICATION, CKA.CKA_VALUE }; @@ -84,7 +84,7 @@ public async ValueTask DecryptMasterKey(MasterKeyData masterKeyData, Can string ckaId = values[0].GetValueAsString(); string ckaLabel = values[1].GetValueAsString(); - if (this.pkcs11Options.Value.DataObjectFilter((ckaId, ckaLabel))) + if (this.pkcs11Options.Value.DataObjectFilter(new DataInfo(ckaId, ckaLabel))) { byte[] data = values[2].GetValueAsByteArray(); Pkcs11SecritData pkcs11SecritData = System.Text.Json.JsonSerializer.Deserialize(masterKeyData.Parameters); @@ -131,13 +131,13 @@ public async ValueTask EncryptMasterKey(byte[] masterKey, Cancell session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_DATA), session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true), session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PRIVATE, true), - session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, this.pkcs11Options.Value.MainDataKeyId), + session.Factories.ObjectAttributeFactory.Create(CKA.CKA_OBJECT_ID, this.pkcs11Options.Value.MainDataKeyId), }; List findAttributesTempalte = new List() { - CKA.CKA_ID, - CKA.CKA_LABEL, + CKA.CKA_OBJECT_ID, + CKA.CKA_APPLICATION, CKA.CKA_VALUE }; @@ -147,10 +147,10 @@ public async ValueTask EncryptMasterKey(byte[] masterKey, Cancell List values = session.GetAttributeValue(dataHandle, findAttributesTempalte); - string ckaId = values[0].GetValueAsString(); - string ckaLabel = values[1].GetValueAsString(); + string ckaObjectId = values[0].GetValueAsString(); + string ckaApplication = values[1].GetValueAsString(); - if (this.pkcs11Options.Value.DataObjectFilter((ckaId, ckaLabel))) + if (this.pkcs11Options.Value.DataObjectFilter(new DataInfo(ckaObjectId, ckaApplication))) { byte[] data = values[2].GetValueAsByteArray(); byte[] key = this.DerieveKey(data, pkcs11SeecritData); @@ -163,7 +163,7 @@ public async ValueTask EncryptMasterKey(byte[] masterKey, Cancell MasterKeyData masterKeyData = new MasterKeyData() { Data = encryptedKey, - KeyId = ckaId, + KeyId = ckaObjectId, Parameters = System.Text.Json.JsonSerializer.Serialize(pkcs11SeecritData) }; @@ -171,7 +171,7 @@ public async ValueTask EncryptMasterKey(byte[] masterKey, Cancell } else { - this.logger.LogDebug("Skip data object witj keyId: {keyId} label: {label}. Not match data object filter.", ckaId, ckaLabel); + this.logger.LogDebug("Skip data object witj keyId: {keyId} label: {label}. Not match data object filter.", ckaObjectId, ckaApplication); } } @@ -197,8 +197,8 @@ public async ValueTask FilterAcceptKeyIds(List keyIds, Cancellat List findAttributesTempalte = new List() { - CKA.CKA_ID, - CKA.CKA_LABEL + CKA.CKA_OBJECT_ID, + CKA.CKA_APPLICATION }; foreach (IObjectHandle dataHandle in session.FindAllObjects(attributesTemplate)) @@ -207,20 +207,20 @@ public async ValueTask FilterAcceptKeyIds(List keyIds, Cancellat List values = session.GetAttributeValue(dataHandle, findAttributesTempalte); - string ckaId = values[0].GetValueAsString(); - string ckaLabel = values[1].GetValueAsString(); + string ckaObjectId = values[0].GetValueAsString(); + string ckaApplication = values[1].GetValueAsString(); - if (this.pkcs11Options.Value.DataObjectFilter((ckaId, ckaLabel))) + if (this.pkcs11Options.Value.DataObjectFilter(new DataInfo(ckaObjectId, ckaApplication))) { - if (keyIds.Any(t => string.Equals(t, ckaId))) + if (keyIds.Any(t => string.Equals(t, ckaObjectId))) { - this.logger.LogTrace("Found keyId: {keyId}", ckaId); - return ckaId; + this.logger.LogTrace("Found ckaObjectId: {ckaObjectId}", ckaObjectId); + return ckaObjectId; } } } - this.logger.LogDebug("Not found supported keyId."); + this.logger.LogDebug("Not found supported ckaObjectId."); return null; } @@ -247,7 +247,7 @@ private void ValidateKeyId(string keyId) throw new ArgumentNullException(nameof(keyId)); } - if (!Regex.IsMatch(keyId, "^[A-Za-z0-9_]{5,32}$", RegexOptions.Singleline, TimeSpan.FromMilliseconds(200))) + if (!Regex.IsMatch(keyId, "^[A-Za-z0-9_-]{5,36}$", RegexOptions.Singleline | RegexOptions.Multiline, TimeSpan.FromMilliseconds(200))) { this.logger.LogError("Key id is invalid. KeyId:{0}", keyId); throw new EfEncryptionException("Invalid keyId."); @@ -288,10 +288,17 @@ private async ValueTask EnshureLogged(CancellationToken cancellationToken) } SecureString pin = await pinProvider.Invoke(this.serviceProvider, cancellationToken); - PkcsExtensions.SecureStringHelper.ExecuteWithSecureString(pin, Encoding.UTF8, rawPin => + try { - this.masterSession.Login(CKU.CKU_USER, rawPin); - }); + PkcsExtensions.SecureStringHelper.ExecuteWithSecureString(pin, Encoding.UTF8, rawPin => + { + this.masterSession.Login(CKU.CKU_USER, rawPin); + }); + } + finally + { + pin?.Dispose(); + } this.logger.LogDebug("Sucessfull loged to PKCS11 device."); } diff --git a/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataProviderOptions.cs b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataProviderOptions.cs index 06bb5c1..a704ad1 100644 --- a/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataProviderOptions.cs +++ b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/CryptoProviders/Pkcs11Data/Pkcs11DataProviderOptions.cs @@ -22,7 +22,7 @@ public string TokenLabel set; } - public Predicate<(string, string)> DataObjectFilter + public Predicate DataObjectFilter { get; set; diff --git a/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/EndpointsExtensions.cs b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/EndpointsExtensions.cs new file mode 100644 index 0000000..3295a07 --- /dev/null +++ b/src/src/Harrison314.EntityFrameworkCore.Encryption.Contrib/EndpointsExtensions.cs @@ -0,0 +1,104 @@ +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Builder; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Harrison314.EntityFrameworkCore.Encryption.CryptoProviders.Remote; +using Microsoft.Extensions.DependencyInjection; + +namespace Harrison314.EntityFrameworkCore.Encryption.Contrib +{ + public static class EndpointsExtensions + { + public static void MapRemoteEncryptedCryptoProvider(this IEndpointRouteBuilder endpoints, string startUrl) + where T : IDbContextEncryptedCryptoProvider + { + endpoints.MapPost(string.Concat(startUrl.TrimEnd('/'), "/EncryptMasterKey"), async context => + { + if (!context.Request.HasJsonContentType()) + { + context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; + return; + } + + EncryptMasterKeyRequest request = await context.Request.ReadFromJsonAsync(context.RequestAborted); + //TODO: Validate + T provider = context.RequestServices.GetRequiredService(); + + MasterKeyData data = await provider.EncryptMasterKey(request.MasterKey, context.RequestAborted); + EncryptMasterKeyResponse response = new EncryptMasterKeyResponse() + { + Data = data.Data, + KeyId = data.KeyId, + Parameters = data.Parameters + }; + + await context.Response.WriteAsJsonAsync(response, context.RequestAborted); + context.Response.StatusCode = 200; + + //TODO: Error handling + }); + //TODO: additional actions + + endpoints.MapPost(string.Concat(startUrl.TrimEnd('/'), "/FilterAcceptKeyIds"), async context => + { + if (!context.Request.HasJsonContentType()) + { + context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; + return; + } + + FilterAcceptKeyIdsRequest request = await context.Request.ReadFromJsonAsync(context.RequestAborted); + //TODO: Validate + T provider = context.RequestServices.GetRequiredService(); + + string selectedKeyId = await provider.FilterAcceptKeyIds(request.KeyIds, context.RequestAborted); + FilterAcceptKeyIdsResponse response = new FilterAcceptKeyIdsResponse() + { + SelectedKeyId = selectedKeyId + }; + + await context.Response.WriteAsJsonAsync(response, context.RequestAborted); + context.Response.StatusCode = 200; + + //TODO: Error handling + }); + //TODO: additional actions + + endpoints.MapPost(string.Concat(startUrl.TrimEnd('/'), "/DecryptMasterKey"), async context => + { + if (!context.Request.HasJsonContentType()) + { + context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; + return; + } + + DecryptMasterKeyRequest request = await context.Request.ReadFromJsonAsync(context.RequestAborted); + //TODO: Validate + T provider = context.RequestServices.GetRequiredService(); + + MasterKeyData data = new MasterKeyData() + { + Data = request.Data, + KeyId = request.KeyId, + Parameters = request.Parameters + }; + + byte[] masterKey = await provider.DecryptMasterKey(data, context.RequestAborted); + DecryptMasterKeyResponse response = new DecryptMasterKeyResponse() + { + MasterKey = masterKey + }; + + await context.Response.WriteAsJsonAsync(response, context.RequestAborted); + context.Response.StatusCode = 200; + + //TODO: Error handling + }); + //TODO: additional actions + } + } +} diff --git a/src/test/Harrison314.EntityFrameworkCore.Contrib.Tests/CryptoProviders/Pkcs11Data/Pkcs11DataProviderTest.cs b/src/test/Harrison314.EntityFrameworkCore.Contrib.Tests/CryptoProviders/Pkcs11Data/Pkcs11DataProviderTest.cs new file mode 100644 index 0000000..66a1c43 --- /dev/null +++ b/src/test/Harrison314.EntityFrameworkCore.Contrib.Tests/CryptoProviders/Pkcs11Data/Pkcs11DataProviderTest.cs @@ -0,0 +1,243 @@ +using FluentAssertions; +using Harrison314.EntityFrameworkCore.Encryption.Contrib.CryptoProviders.Pkcs11Data; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SoftHSMv2ForTesting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security; +using System.Text; +using System.Threading.Tasks; + +namespace Harrison314.EntityFrameworkCore.Contrib.Tests.CryptoProviders.Pkcs11Data +{ + [TestClass] + public class Pkcs11DataProviderTest + { + public const string TokenName = "TestCardToken"; + public const string TokenSoPin = "abcdef"; + public const string TokenUserPin = "abc123*!~"; + + private static SoftHsmContext softHsmContext = null; + + public static string Pkcs11LibPath + { + get => softHsmContext.Pkcs11LibPath; + } + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + string deployPath = Path.Combine(Path.GetTempPath(), $"SoftHSMv2-{Guid.NewGuid():D}"); + softHsmContext = SoftHsmInitializer.Init(opt => + { + opt.DeployFolder = deployPath; + + opt.LabelName = TokenName; + opt.Pin = TokenUserPin; + opt.SoPin = TokenSoPin; + }); + + context.WriteLine("Deploy SoftHSMv2 to {0}", deployPath); + } + + [ClassCleanup] + public static void ClassCleanup() + { + softHsmContext?.Dispose(); + } + + [TestMethod] + public void GenerateData() + { + using Pkcs11DataGenerator pkcs11DataGenerator = new Pkcs11DataGenerator(Pkcs11LibPath, + TokenName, + this.GetPin()); + + pkcs11DataGenerator.GenerateDataObject("TestGenerateData", Guid.NewGuid().ToString("D")); + } + + [TestMethod] + public async Task EncryptMasterKey() + { + string masterKeyId = Guid.NewGuid().ToString("D"); + string label = "TestLabel"; + + Mock serviceProviderMock = new Mock(MockBehavior.Loose); + Mock> loggerMock = new Mock>(MockBehavior.Loose); + Pkcs11DataProviderOptions options = new Pkcs11DataProviderOptions() + { + DataObjectFilter = info => string.Equals(info.Label, label, StringComparison.Ordinal), + MainDataKeyId = masterKeyId, + PinProvider = (_, _) => new ValueTask(this.GetPin()), + Pkcs11LibPath = Pkcs11LibPath, + TokenLabel = TokenName + }; + + using (Pkcs11DataGenerator pkcs11DataGenerator = new Pkcs11DataGenerator(Pkcs11LibPath, + TokenName, + this.GetPin())) + { + pkcs11DataGenerator.GenerateDataObject(label, masterKeyId); + } + + using Pkcs11DataProvider provider = new Pkcs11DataProvider(serviceProviderMock.Object, + Options.Create(options), + loggerMock.Object); + + byte[] masterKey = new byte[32]; + this.DeterministicFill(masterKey); + + Encryption.MasterKeyData masterKeyData = await provider.EncryptMasterKey(masterKey, default); + + masterKeyData.Should().NotBeNull(); + masterKeyData.Data.Should().NotBeNullOrEmpty(); + masterKeyData.KeyId.Should().NotBeNullOrEmpty().And.BeEquivalentTo(masterKeyId); + masterKeyData.Parameters.Should().NotBeNullOrWhiteSpace(); + } + + [TestMethod] + public async Task DecryptMasterKey() + { + string masterKeyId = Guid.NewGuid().ToString("D"); + string label = "TestLabel"; + + Mock serviceProviderMock = new Mock(MockBehavior.Loose); + Mock> loggerMock = new Mock>(MockBehavior.Loose); + Pkcs11DataProviderOptions options = new Pkcs11DataProviderOptions() + { + DataObjectFilter = info => string.Equals(info.Label, label, StringComparison.Ordinal), + MainDataKeyId = masterKeyId, + PinProvider = (_, _) => new ValueTask(this.GetPin()), + Pkcs11LibPath = Pkcs11LibPath, + TokenLabel = TokenName + }; + + using (Pkcs11DataGenerator pkcs11DataGenerator = new Pkcs11DataGenerator(Pkcs11LibPath, + TokenName, + this.GetPin())) + { + pkcs11DataGenerator.GenerateDataObject(label, masterKeyId); + } + + using Pkcs11DataProvider provider = new Pkcs11DataProvider(serviceProviderMock.Object, + Options.Create(options), + loggerMock.Object); + + byte[] masterKey = new byte[32]; + this.DeterministicFill(masterKey); + + Encryption.MasterKeyData masterKeyData = await provider.EncryptMasterKey(masterKey, default); + + byte[] decrypted = await provider.DecryptMasterKey(masterKeyData, default); + + decrypted.Should().NotBeNull().And.NotBeEmpty().And.BeEquivalentTo(masterKey); + } + + [TestMethod] + public async Task FilterAcceptKeyIds_Success() + { + string masterKeyId = Guid.NewGuid().ToString("D"); + string label = "TestLabel"; + + Mock serviceProviderMock = new Mock(MockBehavior.Loose); + Mock> loggerMock = new Mock>(MockBehavior.Loose); + Pkcs11DataProviderOptions options = new Pkcs11DataProviderOptions() + { + DataObjectFilter = info => string.Equals(info.Label, label, StringComparison.Ordinal), + MainDataKeyId = masterKeyId, + PinProvider = (_, _) => new ValueTask(this.GetPin()), + Pkcs11LibPath = Pkcs11LibPath, + TokenLabel = TokenName + }; + + using (Pkcs11DataGenerator pkcs11DataGenerator = new Pkcs11DataGenerator(Pkcs11LibPath, + TokenName, + this.GetPin())) + { + pkcs11DataGenerator.GenerateDataObject(label, Guid.NewGuid().ToString("D")); + pkcs11DataGenerator.GenerateDataObject(label, masterKeyId); + pkcs11DataGenerator.GenerateDataObject(label, Guid.NewGuid().ToString("D")); + } + + List potencialKeys = new List() + { + "TestKey1", + "TestKey2", + masterKeyId, + "TestKey3" + }; + + using Pkcs11DataProvider provider = new Pkcs11DataProvider(serviceProviderMock.Object, + Options.Create(options), + loggerMock.Object); + + string selectedKeyId = await provider.FilterAcceptKeyIds(potencialKeys, default); + + selectedKeyId.Should().BeEquivalentTo(masterKeyId); + } + + [TestMethod] + public async Task FilterAcceptKeyIds_Failed() + { + string masterKeyId = Guid.NewGuid().ToString("D"); + string label = "TestLabel"; + + Mock serviceProviderMock = new Mock(MockBehavior.Loose); + Mock> loggerMock = new Mock>(MockBehavior.Loose); + Pkcs11DataProviderOptions options = new Pkcs11DataProviderOptions() + { + DataObjectFilter = info => string.Equals(info.Label, label, StringComparison.Ordinal), + MainDataKeyId = masterKeyId, + PinProvider = (_, _) => new ValueTask(this.GetPin()), + Pkcs11LibPath = Pkcs11LibPath, + TokenLabel = TokenName + }; + + using (Pkcs11DataGenerator pkcs11DataGenerator = new Pkcs11DataGenerator(Pkcs11LibPath, + TokenName, + this.GetPin())) + { + pkcs11DataGenerator.GenerateDataObject(label, Guid.NewGuid().ToString("D")); + pkcs11DataGenerator.GenerateDataObject(label, masterKeyId); + pkcs11DataGenerator.GenerateDataObject(label, Guid.NewGuid().ToString("D")); + } + + List potencialKeys = new List() + { + "TestKey1", + "TestKey2", + "TestKey3" + }; + + using Pkcs11DataProvider provider = new Pkcs11DataProvider(serviceProviderMock.Object, + Options.Create(options), + loggerMock.Object); + + string selectedKeyId = await provider.FilterAcceptKeyIds(potencialKeys, default); + + selectedKeyId.Should().BeNull(); + } + + private SecureString GetPin() + { + SecureString secureString = new SecureString(); + foreach (char c in TokenUserPin) + { + secureString.AppendChar(c); + } + + return secureString; + } + + private void DeterministicFill(byte[] data) + { + Random rand = new Random(42); + rand.NextBytes(data); + } + } +} diff --git a/src/test/Harrison314.EntityFrameworkCore.Contrib.Tests/Harrison314.EntityFrameworkCore.Contrib.Tests.csproj b/src/test/Harrison314.EntityFrameworkCore.Contrib.Tests/Harrison314.EntityFrameworkCore.Contrib.Tests.csproj new file mode 100644 index 0000000..43505a4 --- /dev/null +++ b/src/test/Harrison314.EntityFrameworkCore.Contrib.Tests/Harrison314.EntityFrameworkCore.Contrib.Tests.csproj @@ -0,0 +1,23 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + +