From cde3b96aa7d1e51414ab02dc2c8f22649b595da3 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Tue, 9 Aug 2016 11:10:42 -0700 Subject: [PATCH] [Fixes #134] Refactored DI support - Refactored builder extensions and service collection extensions - Refactored Settings/Configuration/Descriptor - Removed ConfigurationCommon/AuthenticatedEncryptorConfigurationExtensions - Added IAuthenticatedEncryptorFactory and implementations - Refactored IKey to have Descriptor instead of CreateEncryptorInstance() - Handled Repository/Encryptor special logic - Added samples - Updated tests --- .gitignore | 3 +- DataProtection.sln | 81 ++++-- samples/AzureBlob/Program.cs | 5 +- .../CustomBuilderExtensions.cs | 31 +++ .../CustomEncryptorSample.csproj | 18 ++ .../CustomXmlDecryptor.cs | 32 +++ .../CustomXmlEncryptor.cs | 38 +++ samples/CustomEncryptorSample/Program.cs | 38 +++ .../Properties/launchSettings.json | 22 ++ .../KeyManagementSample.csproj | 16 ++ samples/KeyManagementSample/Program.cs | 64 +++++ .../Properties/launchSettings.json | 22 ++ samples/NonDISample/NonDISample.csproj | 16 ++ samples/NonDISample/Program.cs | 41 +++ .../Properties/launchSettings.json | 22 ++ samples/Redis/Program.cs | 5 +- ...gs.cs => AuthenticatedEncryptorFactory.cs} | 135 +++++----- .../CngCbcAuthenticatedEncryptionSettings.cs | 184 -------------- .../CngCbcAuthenticatedEncryptorFactory.cs | 124 ++++++++++ .../CngGcmAuthenticatedEncryptionSettings.cs | 125 ---------- .../CngGcmAuthenticatedEncryptorFactory.cs | 89 +++++++ ...iguration.cs => AlgorithmConfiguration.cs} | 11 +- .../AuthenticatedEncryptorConfiguration.cs | 59 +++-- .../AuthenticatedEncryptorDescriptor.cs | 31 +-- ...nticatedEncryptorDescriptorDeserializer.cs | 22 +- ...gCbcAuthenticatedEncryptorConfiguration.cs | 97 ++++++-- .../CngCbcAuthenticatedEncryptorDescriptor.cs | 40 +-- ...nticatedEncryptorDescriptorDeserializer.cs | 26 +- ...gGcmAuthenticatedEncryptorConfiguration.cs | 73 ++++-- .../CngGcmAuthenticatedEncryptorDescriptor.cs | 33 +-- ...nticatedEncryptorDescriptorDeserializer.cs | 21 +- .../ConfigurationModel/ConfigurationCommon.cs | 20 -- .../IAuthenticatedEncryptorDescriptor.cs | 11 - ....cs => IInternalAlgorithmConfiguration.cs} | 23 +- ...agedAuthenticatedEncryptorConfiguration.cs | 98 ++++++-- ...ManagedAuthenticatedEncryptorDescriptor.cs | 32 +-- ...nticatedEncryptorDescriptorDeserializer.cs | 22 +- .../EncryptionAlgorithm.cs | 2 - .../IAuthenticatedEncryptorFactory.cs | 22 ++ ...InternalAuthenticatedEncryptionSettings.cs | 25 -- .../ManagedAuthenticatedEncryptionSettings.cs | 166 ------------- .../ManagedAuthenticatedEncryptorFactory.cs | 130 ++++++++++ .../DataProtectionBuilderExtensions.cs | 175 ++++++++----- .../DataProtectionProviderFactory.cs | 71 +----- ...taProtectionServiceCollectionExtensions.cs | 51 +++- .../DataProtectionServiceDescriptors.cs | 136 ---------- .../DataProtectionServices.cs | 156 ------------ .../EphemeralDataProtectionProvider.cs | 53 ++-- .../Error.cs | 10 +- .../IDataProtectionBuilder.cs | 8 +- .../Internal/DataProtectionBuilder.cs | 11 - .../Internal/DataProtectionOptionsSetup.cs | 23 ++ .../Internal/KeyManagementOptionsSetup.cs | 66 +++++ .../KeyManagement/DefaultKeyResolver.cs | 38 ++- .../KeyManagement/DeferredKey.cs | 7 +- .../KeyManagement/IKey.cs | 7 +- .../Internal/CacheableKeyRing.cs | 5 +- .../Internal/DefaultKeyResolution.cs | 6 +- .../KeyManagement/Key.cs | 2 +- .../KeyManagement/KeyBase.cs | 14 +- .../KeyManagement/KeyManagementOptions.cs | 49 +++- .../KeyManagement/KeyRing.cs | 21 +- .../KeyRingBasedDataProtectionProvider.cs | 4 +- .../KeyRingBasedDataProtector.cs | 6 +- .../KeyManagement/KeyRingProvider.cs | 66 +++-- .../KeyManagement/XmlKeyManager.cs | 219 ++++++++++------ .../Properties/Resources.Designer.cs | 16 ++ .../RC1ForwardingActivator.cs | 6 +- .../RegistryPolicy.cs | 28 +++ .../RegistryPolicyResolver.cs | 87 +++---- .../Repositories/EphemeralXmlRepository.cs | 6 +- .../Repositories/FileSystemXmlRepository.cs | 31 +-- .../Repositories/RegistryXmlRepository.cs | 38 +-- .../Resources.resx | 3 + .../StringInterpolation.cs | 43 ---- .../XmlEncryption/CertificateXmlEncryptor.cs | 48 +--- .../XmlEncryption/DpapiNGXmlEncryptor.cs | 25 +- .../XmlEncryption/DpapiXmlEncryptor.cs | 22 +- ...pNetCore.Cryptography.Internal.Test.csproj | 4 + ...ore.Cryptography.KeyDerivation.Test.csproj | 4 + ...re.DataProtection.Abstractions.Test.csproj | 4 + ...re.DataProtection.AzureStorage.Test.csproj | 4 + ...Core.DataProtection.Extensions.Test.csproj | 4 + .../TimeLimitedDataProtectorTests.cs | 3 +- ...spNetCore.DataProtection.Redis.Test.csproj | 4 + ...CngCbcAuthenticatedEncryptorFactoryTest.cs | 52 ++++ ...CngGcmAuthenticatedEncryptorFactoryTest.cs | 52 ++++ ...tedEncryptorDescriptorDeserializerTests.cs | 26 +- .../AuthenticatedEncryptorDescriptorTests.cs | 25 +- ...uthenticatedEncryptorConfigurationTests.cs | 6 +- ...tedEncryptorDescriptorDeserializerTests.cs | 26 +- ...bcAuthenticatedEncryptorDescriptorTests.cs | 4 +- ...uthenticatedEncryptorConfigurationTests.cs | 6 +- ...tedEncryptorDescriptorDeserializerTests.cs | 26 +- ...cmAuthenticatedEncryptorDescriptorTests.cs | 4 +- ...uthenticatedEncryptorConfigurationTests.cs | 6 +- ...tedEncryptorDescriptorDeserializerTests.cs | 40 ++- ...edAuthenticatedEncryptorDescriptorTests.cs | 8 +- ...anagedAuthenticatedEncryptorFactoryTest.cs | 50 ++++ .../EphemeralDataProtectionProviderTests.cs | 9 +- .../Internal/KeyManagementOptionsSetupTest.cs | 155 ++++++++++++ .../KeyManagement/DefaultKeyResolverTests.cs | 73 +++--- .../KeyManagement/DeferredKeyTests.cs | 46 ++-- ...KeyEscrowServiceProviderExtensionsTests.cs | 6 +- .../KeyRingBasedDataProtectorTests.cs | 69 ++++-- .../KeyManagement/KeyRingProviderTests.cs | 149 ++++++----- .../KeyManagement/KeyRingTests.cs | 75 ++++-- .../KeyManagement/KeyTests.cs | 23 +- .../KeyManagement/XmlKeyManagerTests.cs | 234 ++++++++++-------- ...soft.AspNetCore.DataProtection.Test.csproj | 4 + .../MockExtensions.cs | 6 +- .../RegistryPolicyResolverTests.cs | 209 +++++++++------- .../EphemeralXmlRepositoryTests.cs | 5 +- .../FileSystemXmlRepositoryTests.cs | 11 +- .../RegistryXmlRepositoryTests.cs | 11 +- .../StringLoggerFactory.cs | 2 +- .../XmlAssert.cs | 2 +- .../CertificateXmlEncryptionTests.cs | 8 +- .../DpapiNGXmlEncryptionTests.cs | 3 +- .../XmlEncryption/DpapiXmlEncryptionTests.cs | 5 +- .../XmlEncryptionExtensionsTests.cs | 4 +- 121 files changed, 2933 insertions(+), 2166 deletions(-) create mode 100644 samples/CustomEncryptorSample/CustomBuilderExtensions.cs create mode 100644 samples/CustomEncryptorSample/CustomEncryptorSample.csproj create mode 100644 samples/CustomEncryptorSample/CustomXmlDecryptor.cs create mode 100644 samples/CustomEncryptorSample/CustomXmlEncryptor.cs create mode 100644 samples/CustomEncryptorSample/Program.cs create mode 100644 samples/CustomEncryptorSample/Properties/launchSettings.json create mode 100644 samples/KeyManagementSample/KeyManagementSample.csproj create mode 100644 samples/KeyManagementSample/Program.cs create mode 100644 samples/KeyManagementSample/Properties/launchSettings.json create mode 100644 samples/NonDISample/NonDISample.csproj create mode 100644 samples/NonDISample/Program.cs create mode 100644 samples/NonDISample/Properties/launchSettings.json rename src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/{AuthenticatedEncryptionSettings.cs => AuthenticatedEncryptorFactory.cs} (57%) delete mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngCbcAuthenticatedEncryptionSettings.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngCbcAuthenticatedEncryptorFactory.cs delete mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngGcmAuthenticatedEncryptionSettings.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngGcmAuthenticatedEncryptorFactory.cs rename src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/{IAuthenticatedEncryptorConfiguration.cs => AlgorithmConfiguration.cs} (67%) delete mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ConfigurationCommon.cs rename src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/{IInternalAuthenticatedEncryptorConfiguration.cs => IInternalAlgorithmConfiguration.cs} (50%) create mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/IAuthenticatedEncryptorFactory.cs delete mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/IInternalAuthenticatedEncryptionSettings.cs delete mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ManagedAuthenticatedEncryptionSettings.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactory.cs delete mode 100644 src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceDescriptors.cs delete mode 100644 src/Microsoft.AspNetCore.DataProtection/DataProtectionServices.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection/Internal/DataProtectionOptionsSetup.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection/Internal/KeyManagementOptionsSetup.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection/RegistryPolicy.cs delete mode 100644 src/Microsoft.AspNetCore.DataProtection/StringInterpolation.cs create mode 100644 test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/CngCbcAuthenticatedEncryptorFactoryTest.cs create mode 100644 test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/CngGcmAuthenticatedEncryptorFactoryTest.cs create mode 100644 test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactoryTest.cs create mode 100644 test/Microsoft.AspNetCore.DataProtection.Test/Internal/KeyManagementOptionsSetupTest.cs diff --git a/.gitignore b/.gitignore index 0fb89cd8..fd02261e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ nuget.exe project.lock.json .vs .build/ -.testPublish/ \ No newline at end of file +.testPublish/ +samples/**/temp-keys/ \ No newline at end of file diff --git a/DataProtection.sln b/DataProtection.sln index bcc119cb..664c3689 100644 --- a/DataProtection.sln +++ b/DataProtection.sln @@ -1,51 +1,57 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26127.0 +VisualStudioVersion = 15.0.26206.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection", "src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj", "{1E570CD4-6F12-44F4-961E-005EE2002BC2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{60336AB3-948D-4D15-A5FB-F32A2B91E814}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.Test", "test\Microsoft.AspNetCore.DataProtection.Test\Microsoft.AspNetCore.DataProtection.Test.csproj", "{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5A3A5DE3-49AD-431C-971D-B01B62D94AE2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Cryptography.Internal", "src\Microsoft.AspNetCore.Cryptography.Internal\Microsoft.AspNetCore.Cryptography.Internal.csproj", "{E2779976-A28C-4365-A4BB-4AD854FAF23E}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E1D86B1B-41D8-43C9-97FD-C2BF65C414E2}" + ProjectSection(SolutionItems) = preProject + NuGet.config = NuGet.config + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Cryptography.KeyDerivation", "src\Microsoft.AspNetCore.Cryptography.KeyDerivation\Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj", "{421F0383-34B1-402D-807B-A94542513ABA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection", "src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj", "{1E570CD4-6F12-44F4-961E-005EE2002BC2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Cryptography.KeyDerivation.Test", "test\Microsoft.AspNetCore.Cryptography.KeyDerivation.Test\Microsoft.AspNetCore.Cryptography.KeyDerivation.Test.csproj", "{42C97F52-8D56-46BD-A712-4F22BED157A7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Test", "test\Microsoft.AspNetCore.DataProtection.Test\Microsoft.AspNetCore.DataProtection.Test.csproj", "{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Cryptography.Internal.Test", "test\Microsoft.AspNetCore.Cryptography.Internal.Test\Microsoft.AspNetCore.Cryptography.Internal.Test.csproj", "{37053D5F-5B61-47CE-8B72-298CE007FFB0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cryptography.Internal", "src\Microsoft.AspNetCore.Cryptography.Internal\Microsoft.AspNetCore.Cryptography.Internal.csproj", "{E2779976-A28C-4365-A4BB-4AD854FAF23E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.Abstractions", "src\Microsoft.AspNetCore.DataProtection.Abstractions\Microsoft.AspNetCore.DataProtection.Abstractions.csproj", "{4B115BDE-B253-46A6-97BF-A8B37B344FF2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cryptography.KeyDerivation", "src\Microsoft.AspNetCore.Cryptography.KeyDerivation\Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj", "{421F0383-34B1-402D-807B-A94542513ABA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.Abstractions.Test", "test\Microsoft.AspNetCore.DataProtection.Abstractions.Test\Microsoft.AspNetCore.DataProtection.Abstractions.Test.csproj", "{FF650A69-DEE4-4B36-9E30-264EE7CFB478}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cryptography.KeyDerivation.Test", "test\Microsoft.AspNetCore.Cryptography.KeyDerivation.Test\Microsoft.AspNetCore.Cryptography.KeyDerivation.Test.csproj", "{42C97F52-8D56-46BD-A712-4F22BED157A7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.SystemWeb", "src\Microsoft.AspNetCore.DataProtection.SystemWeb\Microsoft.AspNetCore.DataProtection.SystemWeb.csproj", "{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cryptography.Internal.Test", "test\Microsoft.AspNetCore.Cryptography.Internal.Test\Microsoft.AspNetCore.Cryptography.Internal.Test.csproj", "{37053D5F-5B61-47CE-8B72-298CE007FFB0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.Extensions.Test", "test\Microsoft.AspNetCore.DataProtection.Extensions.Test\Microsoft.AspNetCore.DataProtection.Extensions.Test.csproj", "{04AA8E60-A053-4D50-89FE-E76C3DF45200}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Abstractions", "src\Microsoft.AspNetCore.DataProtection.Abstractions\Microsoft.AspNetCore.DataProtection.Abstractions.csproj", "{4B115BDE-B253-46A6-97BF-A8B37B344FF2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.Extensions", "src\Microsoft.AspNetCore.DataProtection.Extensions\Microsoft.AspNetCore.DataProtection.Extensions.csproj", "{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Abstractions.Test", "test\Microsoft.AspNetCore.DataProtection.Abstractions.Test\Microsoft.AspNetCore.DataProtection.Abstractions.Test.csproj", "{FF650A69-DEE4-4B36-9E30-264EE7CFB478}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.Redis", "src\Microsoft.AspNetCore.DataProtection.Redis\Microsoft.AspNetCore.DataProtection.Redis.csproj", "{0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.SystemWeb", "src\Microsoft.AspNetCore.DataProtection.SystemWeb\Microsoft.AspNetCore.DataProtection.SystemWeb.csproj", "{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.AzureStorage", "src\Microsoft.AspNetCore.DataProtection.AzureStorage\Microsoft.AspNetCore.DataProtection.AzureStorage.csproj", "{CC799B57-81E2-4F45-8A32-0D5F49753C3F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Extensions.Test", "test\Microsoft.AspNetCore.DataProtection.Extensions.Test\Microsoft.AspNetCore.DataProtection.Extensions.Test.csproj", "{04AA8E60-A053-4D50-89FE-E76C3DF45200}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5A3A5DE3-49AD-431C-971D-B01B62D94AE2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Extensions", "src\Microsoft.AspNetCore.DataProtection.Extensions\Microsoft.AspNetCore.DataProtection.Extensions.csproj", "{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureBlob", "samples\AzureBlob\AzureBlob.csproj", "{B07435B3-CD81-4E3B-88A5-6384821E1C01}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Redis", "src\Microsoft.AspNetCore.DataProtection.Redis\Microsoft.AspNetCore.DataProtection.Redis.csproj", "{0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.Redis.Test", "test\Microsoft.AspNetCore.DataProtection.Redis.Test\Microsoft.AspNetCore.DataProtection.Redis.Test.csproj", "{ABCF00E5-5B2F-469C-90DC-908C5A04C08D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureStorage", "src\Microsoft.AspNetCore.DataProtection.AzureStorage\Microsoft.AspNetCore.DataProtection.AzureStorage.csproj", "{CC799B57-81E2-4F45-8A32-0D5F49753C3F}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E1D86B1B-41D8-43C9-97FD-C2BF65C414E2}" - ProjectSection(SolutionItems) = preProject - NuGet.config = NuGet.config - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureBlob", "samples\AzureBlob\AzureBlob.csproj", "{B07435B3-CD81-4E3B-88A5-6384821E1C01}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Redis.Test", "test\Microsoft.AspNetCore.DataProtection.Redis.Test\Microsoft.AspNetCore.DataProtection.Redis.Test.csproj", "{ABCF00E5-5B2F-469C-90DC-908C5A04C08D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureStorage.Test", "test\Microsoft.AspNetCore.DataProtection.AzureStorage.Test\Microsoft.AspNetCore.DataProtection.AzureStorage.Test.csproj", "{8C41240E-48F8-402F-9388-74CFE27F4D76}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redis", "samples\Redis\Redis.csproj", "{24AAEC96-DF46-4F61-B2FF-3D5E056685D9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NonDISample", "samples\NonDISample\NonDISample.csproj", "{32CF970B-E2F1-4CD9-8DB3-F5715475373A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DataProtection.AzureStorage.Test", "test\Microsoft.AspNetCore.DataProtection.AzureStorage.Test\Microsoft.AspNetCore.DataProtection.AzureStorage.Test.csproj", "{8C41240E-48F8-402F-9388-74CFE27F4D76}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyManagementSample", "samples\KeyManagementSample\KeyManagementSample.csproj", "{6E066F8D-2910-404F-8949-F58125E28495}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redis", "samples\Redis\Redis.csproj", "{24AAEC96-DF46-4F61-B2FF-3D5E056685D9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomEncryptorSample", "samples\CustomEncryptorSample\CustomEncryptorSample.csproj", "{F4D59BBD-6145-4EE0-BA6E-AD03605BF151}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -187,6 +193,30 @@ Global {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Release|Any CPU.Build.0 = Release|Any CPU {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Release|x86.ActiveCfg = Release|Any CPU {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Release|x86.Build.0 = Release|Any CPU + {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Debug|x86.ActiveCfg = Debug|Any CPU + {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Debug|x86.Build.0 = Debug|Any CPU + {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Release|Any CPU.Build.0 = Release|Any CPU + {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Release|x86.ActiveCfg = Release|Any CPU + {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Release|x86.Build.0 = Release|Any CPU + {6E066F8D-2910-404F-8949-F58125E28495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E066F8D-2910-404F-8949-F58125E28495}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E066F8D-2910-404F-8949-F58125E28495}.Debug|x86.ActiveCfg = Debug|Any CPU + {6E066F8D-2910-404F-8949-F58125E28495}.Debug|x86.Build.0 = Debug|Any CPU + {6E066F8D-2910-404F-8949-F58125E28495}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E066F8D-2910-404F-8949-F58125E28495}.Release|Any CPU.Build.0 = Release|Any CPU + {6E066F8D-2910-404F-8949-F58125E28495}.Release|x86.ActiveCfg = Release|Any CPU + {6E066F8D-2910-404F-8949-F58125E28495}.Release|x86.Build.0 = Release|Any CPU + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Debug|x86.ActiveCfg = Debug|Any CPU + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Debug|x86.Build.0 = Debug|Any CPU + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Release|Any CPU.Build.0 = Release|Any CPU + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Release|x86.ActiveCfg = Release|Any CPU + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -209,5 +239,8 @@ Global {ABCF00E5-5B2F-469C-90DC-908C5A04C08D} = {60336AB3-948D-4D15-A5FB-F32A2B91E814} {8C41240E-48F8-402F-9388-74CFE27F4D76} = {60336AB3-948D-4D15-A5FB-F32A2B91E814} {24AAEC96-DF46-4F61-B2FF-3D5E056685D9} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} + {32CF970B-E2F1-4CD9-8DB3-F5715475373A} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} + {6E066F8D-2910-404F-8949-F58125E28495} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} + {F4D59BBD-6145-4EE0-BA6E-AD03605BF151} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} EndGlobalSection EndGlobal diff --git a/samples/AzureBlob/Program.cs b/samples/AzureBlob/Program.cs index d67432ad..dd1e45b5 100644 --- a/samples/AzureBlob/Program.cs +++ b/samples/AzureBlob/Program.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/samples/CustomEncryptorSample/CustomBuilderExtensions.cs b/samples/CustomEncryptorSample/CustomBuilderExtensions.cs new file mode 100644 index 00000000..faa99a4a --- /dev/null +++ b/samples/CustomEncryptorSample/CustomBuilderExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.XmlEncryption; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace CustomEncryptorSample +{ + public static class CustomBuilderExtensions + { + public static IDataProtectionBuilder UseXmlEncryptor( + this IDataProtectionBuilder builder, + Func factory) + { + builder.Services.AddSingleton>(serviceProvider => + { + var instance = factory(serviceProvider); + return new ConfigureOptions(options => + { + options.XmlEncryptor = instance; + }); + }); + + return builder; + } + } +} diff --git a/samples/CustomEncryptorSample/CustomEncryptorSample.csproj b/samples/CustomEncryptorSample/CustomEncryptorSample.csproj new file mode 100644 index 00000000..b8d21245 --- /dev/null +++ b/samples/CustomEncryptorSample/CustomEncryptorSample.csproj @@ -0,0 +1,18 @@ + + + + net451;netcoreapp1.1 + + win7-x64 + portable + Exe + + + + + + + + + + diff --git a/samples/CustomEncryptorSample/CustomXmlDecryptor.cs b/samples/CustomEncryptorSample/CustomXmlDecryptor.cs new file mode 100644 index 00000000..a8925f12 --- /dev/null +++ b/samples/CustomEncryptorSample/CustomXmlDecryptor.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.XmlEncryption; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace CustomEncryptorSample +{ + public class CustomXmlDecryptor : IXmlDecryptor + { + private readonly ILogger _logger; + + public CustomXmlDecryptor(IServiceProvider services) + { + _logger = services.GetRequiredService().CreateLogger(); + } + + public XElement Decrypt(XElement encryptedElement) + { + if (encryptedElement == null) + { + throw new ArgumentNullException(nameof(encryptedElement)); + } + + return new XElement(encryptedElement.Elements().Single()); + } + } +} diff --git a/samples/CustomEncryptorSample/CustomXmlEncryptor.cs b/samples/CustomEncryptorSample/CustomXmlEncryptor.cs new file mode 100644 index 00000000..f6653f77 --- /dev/null +++ b/samples/CustomEncryptorSample/CustomXmlEncryptor.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.XmlEncryption; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace CustomEncryptorSample +{ + public class CustomXmlEncryptor : IXmlEncryptor + { + private readonly ILogger _logger; + + public CustomXmlEncryptor(IServiceProvider services) + { + _logger = services.GetRequiredService().CreateLogger(); + } + + public EncryptedXmlInfo Encrypt(XElement plaintextElement) + { + if (plaintextElement == null) + { + throw new ArgumentNullException(nameof(plaintextElement)); + } + + _logger.LogInformation("Not encrypting key"); + + var newElement = new XElement("unencryptedKey", + new XComment(" This key is not encrypted. "), + new XElement(plaintextElement)); + var encryptedTextElement = new EncryptedXmlInfo(newElement, typeof(CustomXmlDecryptor)); + + return encryptedTextElement; + } + } +} diff --git a/samples/CustomEncryptorSample/Program.cs b/samples/CustomEncryptorSample/Program.cs new file mode 100644 index 00000000..c79d12c6 --- /dev/null +++ b/samples/CustomEncryptorSample/Program.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace CustomEncryptorSample +{ + public class Program + { + public static void Main(string[] args) + { + var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys"); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo(keysFolder)) + .UseXmlEncryptor(s => new CustomXmlEncryptor(s)); + + var services = serviceCollection.BuildServiceProvider(); + var loggerFactory = services.GetRequiredService(); + loggerFactory.AddConsole(); + + var protector = services.GetDataProtector("SamplePurpose"); + + // protect the payload + var protectedPayload = protector.Protect("Hello World!"); + Console.WriteLine($"Protect returned: {protectedPayload}"); + + // unprotect the payload + var unprotectedPayload = protector.Unprotect(protectedPayload); + Console.WriteLine($"Unprotect returned: {unprotectedPayload}"); + } + } +} diff --git a/samples/CustomEncryptorSample/Properties/launchSettings.json b/samples/CustomEncryptorSample/Properties/launchSettings.json new file mode 100644 index 00000000..c24bc967 --- /dev/null +++ b/samples/CustomEncryptorSample/Properties/launchSettings.json @@ -0,0 +1,22 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:1398/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "CustomEncryptorSample": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/samples/KeyManagementSample/KeyManagementSample.csproj b/samples/KeyManagementSample/KeyManagementSample.csproj new file mode 100644 index 00000000..0769407b --- /dev/null +++ b/samples/KeyManagementSample/KeyManagementSample.csproj @@ -0,0 +1,16 @@ + + + + net451;netcoreapp1.1 + + win7-x64 + portable + Exe + + + + + + + + diff --git a/samples/KeyManagementSample/Program.cs b/samples/KeyManagementSample/Program.cs new file mode 100644 index 00000000..3feefebc --- /dev/null +++ b/samples/KeyManagementSample/Program.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.Extensions.DependencyInjection; + +namespace KeyManagementSample +{ + public class Program + { + public static void Main(string[] args) + { + var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys"); + var serviceCollection = new ServiceCollection(); + var builder = serviceCollection.AddDataProtection() + // point at a specific folder and use DPAPI to encrypt keys + .PersistKeysToFileSystem(new DirectoryInfo(keysFolder)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + builder.ProtectKeysWithDpapi(); + } + + var services = serviceCollection.BuildServiceProvider(); + + // perform a protect operation to force the system to put at least + // one key in the key ring + services.GetDataProtector("Sample.KeyManager.v1").Protect("payload"); + Console.WriteLine("Performed a protect operation."); + + // get a reference to the key manager + var keyManager = services.GetService(); + + // list all keys in the key ring + var allKeys = keyManager.GetAllKeys(); + Console.WriteLine($"The key ring contains {allKeys.Count} key(s)."); + foreach (var key in allKeys) + { + Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}"); + } + + // revoke all keys in the key ring + keyManager.RevokeAllKeys(DateTimeOffset.Now, reason: "Revocation reason here."); + Console.WriteLine("Revoked all existing keys."); + + // add a new key to the key ring with immediate activation and a 1-month expiration + keyManager.CreateNewKey( + activationDate: DateTimeOffset.Now, + expirationDate: DateTimeOffset.Now.AddMonths(1)); + Console.WriteLine("Added a new key."); + + // list all keys in the key ring + allKeys = keyManager.GetAllKeys(); + Console.WriteLine($"The key ring contains {allKeys.Count} key(s)."); + foreach (var key in allKeys) + { + Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}"); + } + } + } +} diff --git a/samples/KeyManagementSample/Properties/launchSettings.json b/samples/KeyManagementSample/Properties/launchSettings.json new file mode 100644 index 00000000..9f2e8074 --- /dev/null +++ b/samples/KeyManagementSample/Properties/launchSettings.json @@ -0,0 +1,22 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:1396/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "KeyManagementSample": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/samples/NonDISample/NonDISample.csproj b/samples/NonDISample/NonDISample.csproj new file mode 100644 index 00000000..0769407b --- /dev/null +++ b/samples/NonDISample/NonDISample.csproj @@ -0,0 +1,16 @@ + + + + net451;netcoreapp1.1 + + win7-x64 + portable + Exe + + + + + + + + diff --git a/samples/NonDISample/Program.cs b/samples/NonDISample/Program.cs new file mode 100644 index 00000000..f9ccd926 --- /dev/null +++ b/samples/NonDISample/Program.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; + +namespace NonDISample +{ + public class Program + { + public static void Main(string[] args) + { + var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys"); + + // instantiate the data protection system at this folder + var dataProtectionProvider = DataProtectionProvider.Create( + new DirectoryInfo(keysFolder), + configuration => + { + configuration.SetApplicationName("my app name"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + configuration.ProtectKeysWithDpapi(); + } + }); + + var protector = dataProtectionProvider.CreateProtector("Program.No-DI"); + + // protect the payload + var protectedPayload = protector.Protect("Hello World!"); + Console.WriteLine($"Protect returned: {protectedPayload}"); + + // unprotect the payload + var unprotectedPayload = protector.Unprotect(protectedPayload); + Console.WriteLine($"Unprotect returned: {unprotectedPayload}"); + } + } +} diff --git a/samples/NonDISample/Properties/launchSettings.json b/samples/NonDISample/Properties/launchSettings.json new file mode 100644 index 00000000..7d362726 --- /dev/null +++ b/samples/NonDISample/Properties/launchSettings.json @@ -0,0 +1,22 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:1394/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "NonDISample": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/samples/Redis/Program.cs b/samples/Redis/Program.cs index a3401c86..94a32c11 100644 --- a/samples/Redis/Program.cs +++ b/samples/Redis/Program.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/AuthenticatedEncryptionSettings.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/AuthenticatedEncryptorFactory.cs similarity index 57% rename from src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/AuthenticatedEncryptionSettings.cs rename to src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/AuthenticatedEncryptorFactory.cs index 093dc3e1..9cff56e7 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/AuthenticatedEncryptionSettings.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/AuthenticatedEncryptorFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -6,103 +6,90 @@ using Microsoft.AspNetCore.Cryptography; using Microsoft.AspNetCore.Cryptography.Cng; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption { - /// - /// Settings for configuring authenticated encryption algorithms. - /// - public sealed class AuthenticatedEncryptionSettings : IInternalAuthenticatedEncryptionSettings + public sealed class AuthenticatedEncryptorFactory : IAuthenticatedEncryptorFactory { - /// - /// The algorithm to use for symmetric encryption (confidentiality). - /// - /// - /// The default value is . - /// - public EncryptionAlgorithm EncryptionAlgorithm { get; set; } = EncryptionAlgorithm.AES_256_CBC; - - /// - /// The algorithm to use for message authentication (tamper-proofing). - /// - /// - /// The default value is . - /// This property is ignored if specifies a 'GCM' algorithm. - /// - public ValidationAlgorithm ValidationAlgorithm { get; set; } = ValidationAlgorithm.HMACSHA256; - - /// - /// Validates that this is well-formed, i.e., - /// that the specified algorithms actually exist and that they can be instantiated properly. - /// An exception will be thrown if validation fails. - /// - public void Validate() + private readonly ILoggerFactory _loggerFactory; + + public AuthenticatedEncryptorFactory(ILoggerFactory loggerFactory) { - // Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly. - var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8)); - try - { - encryptor.PerformSelfTest(); - } - finally - { - (encryptor as IDisposable)?.Dispose(); - } + _loggerFactory = loggerFactory; } - /* - * HELPER ROUTINES - */ - - internal IAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(ISecret secret, IServiceProvider services = null) + public IAuthenticatedEncryptor CreateEncryptorInstance(IKey key) { - return CreateImplementationOptions() - .ToConfiguration(services) - .CreateDescriptorFromSecret(secret) - .CreateEncryptorInstance(); + var descriptor = key.Descriptor as AuthenticatedEncryptorDescriptor; + if (descriptor == null) + { + return null; + } + + return CreateAuthenticatedEncryptorInstance(descriptor.MasterKey, descriptor.Configuration); } - private IInternalAuthenticatedEncryptionSettings CreateImplementationOptions() + internal IAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance( + ISecret secret, + AuthenticatedEncryptorConfiguration authenticatedConfiguration) { - if (IsGcmAlgorithm(EncryptionAlgorithm)) + if (authenticatedConfiguration == null) + { + return null; + } + + if (IsGcmAlgorithm(authenticatedConfiguration.EncryptionAlgorithm)) { // GCM requires CNG, and CNG is only supported on Windows. if (!OSVersionUtil.IsWindows()) { throw new PlatformNotSupportedException(Resources.Platform_WindowsRequiredForGcm); } - return new CngGcmAuthenticatedEncryptionSettings() + + var configuration = new CngGcmAuthenticatedEncryptorConfiguration() { - EncryptionAlgorithm = GetBCryptAlgorithmName(EncryptionAlgorithm), - EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(EncryptionAlgorithm) + EncryptionAlgorithm = GetBCryptAlgorithmNameFromEncryptionAlgorithm(authenticatedConfiguration.EncryptionAlgorithm), + EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(authenticatedConfiguration.EncryptionAlgorithm) }; + + return new CngGcmAuthenticatedEncryptorFactory(_loggerFactory).CreateAuthenticatedEncryptorInstance(secret, configuration); } else { if (OSVersionUtil.IsWindows()) { // CNG preferred over managed implementations if running on Windows - return new CngCbcAuthenticatedEncryptionSettings() + var configuration = new CngCbcAuthenticatedEncryptorConfiguration() { - EncryptionAlgorithm = GetBCryptAlgorithmName(EncryptionAlgorithm), - EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(EncryptionAlgorithm), - HashAlgorithm = GetBCryptAlgorithmName(ValidationAlgorithm) + EncryptionAlgorithm = GetBCryptAlgorithmNameFromEncryptionAlgorithm(authenticatedConfiguration.EncryptionAlgorithm), + EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(authenticatedConfiguration.EncryptionAlgorithm), + HashAlgorithm = GetBCryptAlgorithmNameFromValidationAlgorithm(authenticatedConfiguration.ValidationAlgorithm) }; + + return new CngCbcAuthenticatedEncryptorFactory(_loggerFactory).CreateAuthenticatedEncryptorInstance(secret, configuration); } else { // Use managed implementations as a fallback - return new ManagedAuthenticatedEncryptionSettings() + var configuration = new ManagedAuthenticatedEncryptorConfiguration() { - EncryptionAlgorithmType = GetManagedTypeForAlgorithm(EncryptionAlgorithm), - EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(EncryptionAlgorithm), - ValidationAlgorithmType = GetManagedTypeForAlgorithm(ValidationAlgorithm) + EncryptionAlgorithmType = GetManagedTypeFromEncryptionAlgorithm(authenticatedConfiguration.EncryptionAlgorithm), + EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(authenticatedConfiguration.EncryptionAlgorithm), + ValidationAlgorithmType = GetManagedTypeFromValidationAlgorithm(authenticatedConfiguration.ValidationAlgorithm) }; + + return new ManagedAuthenticatedEncryptorFactory(_loggerFactory).CreateAuthenticatedEncryptorInstance(secret, configuration); } } } + internal static bool IsGcmAlgorithm(EncryptionAlgorithm algorithm) + { + return (EncryptionAlgorithm.AES_128_GCM <= algorithm && algorithm <= EncryptionAlgorithm.AES_256_GCM); + } + private static int GetAlgorithmKeySizeInBits(EncryptionAlgorithm algorithm) { switch (algorithm) @@ -120,11 +107,11 @@ private static int GetAlgorithmKeySizeInBits(EncryptionAlgorithm algorithm) return 256; default: - throw new ArgumentOutOfRangeException(nameof(algorithm)); + throw new ArgumentOutOfRangeException(nameof(EncryptionAlgorithm)); } } - private static string GetBCryptAlgorithmName(EncryptionAlgorithm algorithm) + private static string GetBCryptAlgorithmNameFromEncryptionAlgorithm(EncryptionAlgorithm algorithm) { switch (algorithm) { @@ -137,11 +124,11 @@ private static string GetBCryptAlgorithmName(EncryptionAlgorithm algorithm) return Constants.BCRYPT_AES_ALGORITHM; default: - throw new ArgumentOutOfRangeException(nameof(algorithm)); + throw new ArgumentOutOfRangeException(nameof(EncryptionAlgorithm)); } } - private static string GetBCryptAlgorithmName(ValidationAlgorithm algorithm) + private static string GetBCryptAlgorithmNameFromValidationAlgorithm(ValidationAlgorithm algorithm) { switch (algorithm) { @@ -152,11 +139,11 @@ private static string GetBCryptAlgorithmName(ValidationAlgorithm algorithm) return Constants.BCRYPT_SHA512_ALGORITHM; default: - throw new ArgumentOutOfRangeException(nameof(algorithm)); + throw new ArgumentOutOfRangeException(nameof(ValidationAlgorithm)); } } - private static Type GetManagedTypeForAlgorithm(EncryptionAlgorithm algorithm) + private static Type GetManagedTypeFromEncryptionAlgorithm(EncryptionAlgorithm algorithm) { switch (algorithm) { @@ -169,11 +156,11 @@ private static Type GetManagedTypeForAlgorithm(EncryptionAlgorithm algorithm) return typeof(Aes); default: - throw new ArgumentOutOfRangeException(nameof(algorithm)); + throw new ArgumentOutOfRangeException(nameof(EncryptionAlgorithm)); } } - private static Type GetManagedTypeForAlgorithm(ValidationAlgorithm algorithm) + private static Type GetManagedTypeFromValidationAlgorithm(ValidationAlgorithm algorithm) { switch (algorithm) { @@ -184,18 +171,8 @@ private static Type GetManagedTypeForAlgorithm(ValidationAlgorithm algorithm) return typeof(HMACSHA512); default: - throw new ArgumentOutOfRangeException(nameof(algorithm)); + throw new ArgumentOutOfRangeException(nameof(ValidationAlgorithm)); } } - - internal static bool IsGcmAlgorithm(EncryptionAlgorithm algorithm) - { - return (EncryptionAlgorithm.AES_128_GCM <= algorithm && algorithm <= EncryptionAlgorithm.AES_256_GCM); - } - - IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionSettings.ToConfiguration(IServiceProvider services) - { - return new AuthenticatedEncryptorConfiguration(this, services); - } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngCbcAuthenticatedEncryptionSettings.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngCbcAuthenticatedEncryptionSettings.cs deleted file mode 100644 index d9d4cd1a..00000000 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngCbcAuthenticatedEncryptionSettings.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Cryptography; -using Microsoft.AspNetCore.Cryptography.Cng; -using Microsoft.AspNetCore.Cryptography.SafeHandles; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; -using Microsoft.AspNetCore.DataProtection.Cng; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption -{ - /// - /// Settings for configuring an authenticated encryption mechanism which uses - /// Windows CNG algorithms in CBC encryption + HMAC authentication modes. - /// - public sealed class CngCbcAuthenticatedEncryptionSettings : IInternalAuthenticatedEncryptionSettings - { - /// - /// The name of the algorithm to use for symmetric encryption. - /// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider. - /// This property is required to have a value. - /// - /// - /// The algorithm must support CBC-style encryption and must have a block size of 64 bits - /// or greater. - /// The default value is 'AES'. - /// - [ApplyPolicy] - public string EncryptionAlgorithm { get; set; } = Constants.BCRYPT_AES_ALGORITHM; - - /// - /// The name of the provider which contains the implementation of the symmetric encryption algorithm. - /// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider. - /// This property is optional. - /// - /// - /// The default value is null. - /// - [ApplyPolicy] - public string EncryptionAlgorithmProvider { get; set; } = null; - - /// - /// The length (in bits) of the key that will be used for symmetric encryption. - /// This property is required to have a value. - /// - /// - /// The key length must be 128 bits or greater. - /// The default value is 256. - /// - [ApplyPolicy] - public int EncryptionAlgorithmKeySize { get; set; } = 256; - - /// - /// The name of the algorithm to use for hashing data. - /// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider. - /// This property is required to have a value. - /// - /// - /// The algorithm must support being opened in HMAC mode and must have a digest length - /// of 128 bits or greater. - /// The default value is 'SHA256'. - /// - [ApplyPolicy] - public string HashAlgorithm { get; set; } = Constants.BCRYPT_SHA256_ALGORITHM; - - /// - /// The name of the provider which contains the implementation of the hash algorithm. - /// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider. - /// This property is optional. - /// - /// - /// The default value is null. - /// - [ApplyPolicy] - public string HashAlgorithmProvider { get; set; } = null; - - /// - /// Validates that this is well-formed, i.e., - /// that the specified algorithms actually exist and that they can be instantiated properly. - /// An exception will be thrown if validation fails. - /// - public void Validate() - { - // Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly. - using (var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8))) - { - encryptor.PerformSelfTest(); - } - } - - /* - * HELPER ROUTINES - */ - - internal CbcAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(ISecret secret, ILogger logger = null) - { - return new CbcAuthenticatedEncryptor( - keyDerivationKey: new Secret(secret), - symmetricAlgorithmHandle: GetSymmetricBlockCipherAlgorithmHandle(logger), - symmetricAlgorithmKeySizeInBytes: (uint)(EncryptionAlgorithmKeySize / 8), - hmacAlgorithmHandle: GetHmacAlgorithmHandle(logger)); - } - - private BCryptAlgorithmHandle GetHmacAlgorithmHandle(ILogger logger) - { - // basic argument checking - if (String.IsNullOrEmpty(HashAlgorithm)) - { - throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(HashAlgorithm)); - } - - logger?.OpeningCNGAlgorithmFromProviderWithHMAC(HashAlgorithm, HashAlgorithmProvider); - BCryptAlgorithmHandle algorithmHandle = null; - - // Special-case cached providers - if (HashAlgorithmProvider == null) - { - if (HashAlgorithm == Constants.BCRYPT_SHA1_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA1; } - else if (HashAlgorithm == Constants.BCRYPT_SHA256_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA256; } - else if (HashAlgorithm == Constants.BCRYPT_SHA512_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA512; } - } - - // Look up the provider dynamically if we couldn't fetch a cached instance - if (algorithmHandle == null) - { - algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(HashAlgorithm, HashAlgorithmProvider, hmac: true); - } - - // Make sure we're using a hash algorithm. We require a minimum 128-bit digest. - var digestSize = algorithmHandle.GetHashDigestLength(); - AlgorithmAssert.IsAllowableValidationAlgorithmDigestSize(checked(digestSize * 8)); - - // all good! - return algorithmHandle; - } - - private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle(ILogger logger) - { - // basic argument checking - if (String.IsNullOrEmpty(EncryptionAlgorithm)) - { - throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm)); - } - if (EncryptionAlgorithmKeySize < 0) - { - throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize)); - } - - logger?.OpeningCNGAlgorithmFromProviderWithChainingModeCBC(EncryptionAlgorithm, EncryptionAlgorithmProvider); - - BCryptAlgorithmHandle algorithmHandle = null; - - // Special-case cached providers - if (EncryptionAlgorithmProvider == null) - { - if (EncryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_CBC; } - } - - // Look up the provider dynamically if we couldn't fetch a cached instance - if (algorithmHandle == null) - { - algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(EncryptionAlgorithm, EncryptionAlgorithmProvider); - algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_CBC); - } - - // make sure we're using a block cipher with an appropriate key size & block size - AlgorithmAssert.IsAllowableSymmetricAlgorithmBlockSize(checked(algorithmHandle.GetCipherBlockLength() * 8)); - AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)EncryptionAlgorithmKeySize)); - - // make sure the provided key length is valid - algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)EncryptionAlgorithmKeySize); - - // all good! - return algorithmHandle; - } - - IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionSettings.ToConfiguration(IServiceProvider services) - { - return new CngCbcAuthenticatedEncryptorConfiguration(this, services); - } - } -} diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngCbcAuthenticatedEncryptorFactory.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngCbcAuthenticatedEncryptorFactory.cs new file mode 100644 index 00000000..86fc817e --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngCbcAuthenticatedEncryptorFactory.cs @@ -0,0 +1,124 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Cryptography; +using Microsoft.AspNetCore.Cryptography.Cng; +using Microsoft.AspNetCore.Cryptography.SafeHandles; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Cng; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption +{ + public sealed class CngCbcAuthenticatedEncryptorFactory : IAuthenticatedEncryptorFactory + { + private readonly ILogger _logger; + + public CngCbcAuthenticatedEncryptorFactory(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public IAuthenticatedEncryptor CreateEncryptorInstance(IKey key) + { + var descriptor = key.Descriptor as CngCbcAuthenticatedEncryptorDescriptor; + if (descriptor == null) + { + return null; + } + + return CreateAuthenticatedEncryptorInstance(descriptor.MasterKey, descriptor.Configuration); + } + + internal CbcAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance( + ISecret secret, + CngCbcAuthenticatedEncryptorConfiguration configuration) + { + if (configuration == null) + { + return null; + } + + return new CbcAuthenticatedEncryptor( + keyDerivationKey: new Secret(secret), + symmetricAlgorithmHandle: GetSymmetricBlockCipherAlgorithmHandle(configuration), + symmetricAlgorithmKeySizeInBytes: (uint)(configuration.EncryptionAlgorithmKeySize / 8), + hmacAlgorithmHandle: GetHmacAlgorithmHandle(configuration)); + } + + private BCryptAlgorithmHandle GetHmacAlgorithmHandle(CngCbcAuthenticatedEncryptorConfiguration configuration) + { + // basic argument checking + if (String.IsNullOrEmpty(configuration.HashAlgorithm)) + { + throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(configuration.HashAlgorithm)); + } + + _logger.OpeningCNGAlgorithmFromProviderWithHMAC(configuration.HashAlgorithm, configuration.HashAlgorithmProvider); + BCryptAlgorithmHandle algorithmHandle = null; + + // Special-case cached providers + if (configuration.HashAlgorithmProvider == null) + { + if (configuration.HashAlgorithm == Constants.BCRYPT_SHA1_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA1; } + else if (configuration.HashAlgorithm == Constants.BCRYPT_SHA256_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA256; } + else if (configuration.HashAlgorithm == Constants.BCRYPT_SHA512_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA512; } + } + + // Look up the provider dynamically if we couldn't fetch a cached instance + if (algorithmHandle == null) + { + algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(configuration.HashAlgorithm, configuration.HashAlgorithmProvider, hmac: true); + } + + // Make sure we're using a hash algorithm. We require a minimum 128-bit digest. + uint digestSize = algorithmHandle.GetHashDigestLength(); + AlgorithmAssert.IsAllowableValidationAlgorithmDigestSize(checked(digestSize * 8)); + + // all good! + return algorithmHandle; + } + + private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle(CngCbcAuthenticatedEncryptorConfiguration configuration) + { + // basic argument checking + if (String.IsNullOrEmpty(configuration.EncryptionAlgorithm)) + { + throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm)); + } + if (configuration.EncryptionAlgorithmKeySize < 0) + { + throw Error.Common_PropertyMustBeNonNegative(nameof(configuration.EncryptionAlgorithmKeySize)); + } + + _logger.OpeningCNGAlgorithmFromProviderWithChainingModeCBC(configuration.EncryptionAlgorithm, configuration.EncryptionAlgorithmProvider); + + BCryptAlgorithmHandle algorithmHandle = null; + + // Special-case cached providers + if (configuration.EncryptionAlgorithmProvider == null) + { + if (configuration.EncryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_CBC; } + } + + // Look up the provider dynamically if we couldn't fetch a cached instance + if (algorithmHandle == null) + { + algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(configuration.EncryptionAlgorithm, configuration.EncryptionAlgorithmProvider); + algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_CBC); + } + + // make sure we're using a block cipher with an appropriate key size & block size + AlgorithmAssert.IsAllowableSymmetricAlgorithmBlockSize(checked(algorithmHandle.GetCipherBlockLength() * 8)); + AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)configuration.EncryptionAlgorithmKeySize)); + + // make sure the provided key length is valid + algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)configuration.EncryptionAlgorithmKeySize); + + // all good! + return algorithmHandle; + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngGcmAuthenticatedEncryptionSettings.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngGcmAuthenticatedEncryptionSettings.cs deleted file mode 100644 index 4c3f33d9..00000000 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngGcmAuthenticatedEncryptionSettings.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Cryptography; -using Microsoft.AspNetCore.Cryptography.Cng; -using Microsoft.AspNetCore.Cryptography.SafeHandles; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; -using Microsoft.AspNetCore.DataProtection.Cng; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption -{ - /// - /// Settings for configuring an authenticated encryption mechanism which uses - /// Windows CNG algorithms in GCM encryption + authentication modes. - /// - public sealed class CngGcmAuthenticatedEncryptionSettings : IInternalAuthenticatedEncryptionSettings - { - /// - /// The name of the algorithm to use for symmetric encryption. - /// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider. - /// This property is required to have a value. - /// - /// - /// The algorithm must support CBC-style encryption and must have a block size exactly - /// 128 bits. - /// The default value is 'AES'. - /// - [ApplyPolicy] - public string EncryptionAlgorithm { get; set; } = Constants.BCRYPT_AES_ALGORITHM; - - /// - /// The name of the provider which contains the implementation of the symmetric encryption algorithm. - /// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider. - /// This property is optional. - /// - /// - /// The default value is null. - /// - [ApplyPolicy] - public string EncryptionAlgorithmProvider { get; set; } = null; - - /// - /// The length (in bits) of the key that will be used for symmetric encryption. - /// This property is required to have a value. - /// - /// - /// The key length must be 128 bits or greater. - /// The default value is 256. - /// - [ApplyPolicy] - public int EncryptionAlgorithmKeySize { get; set; } = 256; - - /// - /// Validates that this is well-formed, i.e., - /// that the specified algorithm actually exists and can be instantiated properly. - /// An exception will be thrown if validation fails. - /// - public void Validate() - { - // Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly. - using (var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8))) - { - encryptor.PerformSelfTest(); - } - } - - /* - * HELPER ROUTINES - */ - - internal GcmAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(ISecret secret, ILogger logger = null) - { - return new GcmAuthenticatedEncryptor( - keyDerivationKey: new Secret(secret), - symmetricAlgorithmHandle: GetSymmetricBlockCipherAlgorithmHandle(logger), - symmetricAlgorithmKeySizeInBytes: (uint)(EncryptionAlgorithmKeySize / 8)); - } - - private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle(ILogger logger) - { - // basic argument checking - if (String.IsNullOrEmpty(EncryptionAlgorithm)) - { - throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm)); - } - if (EncryptionAlgorithmKeySize < 0) - { - throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize)); - } - - BCryptAlgorithmHandle algorithmHandle = null; - - logger?.OpeningCNGAlgorithmFromProviderWithChainingModeGCM(EncryptionAlgorithm, EncryptionAlgorithmProvider); - // Special-case cached providers - if (EncryptionAlgorithmProvider == null) - { - if (EncryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_GCM; } - } - - // Look up the provider dynamically if we couldn't fetch a cached instance - if (algorithmHandle == null) - { - algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(EncryptionAlgorithm, EncryptionAlgorithmProvider); - algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_GCM); - } - - // make sure we're using a block cipher with an appropriate key size & block size - CryptoUtil.Assert(algorithmHandle.GetCipherBlockLength() == 128 / 8, "GCM requires a block cipher algorithm with a 128-bit block size."); - AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)EncryptionAlgorithmKeySize)); - - // make sure the provided key length is valid - algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)EncryptionAlgorithmKeySize); - - // all good! - return algorithmHandle; - } - - IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionSettings.ToConfiguration(IServiceProvider services) - { - return new CngGcmAuthenticatedEncryptorConfiguration(this, services); - } - } -} diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngGcmAuthenticatedEncryptorFactory.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngGcmAuthenticatedEncryptorFactory.cs new file mode 100644 index 00000000..fefd2730 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/CngGcmAuthenticatedEncryptorFactory.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Cryptography; +using Microsoft.AspNetCore.Cryptography.Cng; +using Microsoft.AspNetCore.Cryptography.SafeHandles; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Cng; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption +{ + public sealed class CngGcmAuthenticatedEncryptorFactory : IAuthenticatedEncryptorFactory + { + private readonly ILogger _logger; + + public CngGcmAuthenticatedEncryptorFactory(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public IAuthenticatedEncryptor CreateEncryptorInstance(IKey key) + { + var descriptor = key.Descriptor as CngGcmAuthenticatedEncryptorDescriptor; + if (descriptor == null) + { + return null; + } + + return CreateAuthenticatedEncryptorInstance(descriptor.MasterKey, descriptor.Configuration); + } + + internal GcmAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance( + ISecret secret, + CngGcmAuthenticatedEncryptorConfiguration configuration) + { + if (configuration == null) + { + return null; + } + + return new GcmAuthenticatedEncryptor( + keyDerivationKey: new Secret(secret), + symmetricAlgorithmHandle: GetSymmetricBlockCipherAlgorithmHandle(configuration), + symmetricAlgorithmKeySizeInBytes: (uint)(configuration.EncryptionAlgorithmKeySize / 8)); + } + + private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle(CngGcmAuthenticatedEncryptorConfiguration configuration) + { + // basic argument checking + if (String.IsNullOrEmpty(configuration.EncryptionAlgorithm)) + { + throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm)); + } + if (configuration.EncryptionAlgorithmKeySize < 0) + { + throw Error.Common_PropertyMustBeNonNegative(nameof(configuration.EncryptionAlgorithmKeySize)); + } + + BCryptAlgorithmHandle algorithmHandle = null; + + _logger?.OpeningCNGAlgorithmFromProviderWithChainingModeGCM(configuration.EncryptionAlgorithm, configuration.EncryptionAlgorithmProvider); + // Special-case cached providers + if (configuration.EncryptionAlgorithmProvider == null) + { + if (configuration.EncryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_GCM; } + } + + // Look up the provider dynamically if we couldn't fetch a cached instance + if (algorithmHandle == null) + { + algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(configuration.EncryptionAlgorithm, configuration.EncryptionAlgorithmProvider); + algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_GCM); + } + + // make sure we're using a block cipher with an appropriate key size & block size + CryptoUtil.Assert(algorithmHandle.GetCipherBlockLength() == 128 / 8, "GCM requires a block cipher algorithm with a 128-bit block size."); + AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)configuration.EncryptionAlgorithmKeySize)); + + // make sure the provided key length is valid + algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)configuration.EncryptionAlgorithmKeySize); + + // all good! + return algorithmHandle; + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IAuthenticatedEncryptorConfiguration.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AlgorithmConfiguration.cs similarity index 67% rename from src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IAuthenticatedEncryptorConfiguration.cs rename to src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AlgorithmConfiguration.cs index 0d863bf7..4fddb0a7 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IAuthenticatedEncryptorConfiguration.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AlgorithmConfiguration.cs @@ -1,21 +1,20 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel { - /// - /// The basic configuration that serves as a factory for types related to authenticated encryption. - /// - public interface IAuthenticatedEncryptorConfiguration + public abstract class AlgorithmConfiguration { + internal const int KDK_SIZE_IN_BYTES = 512 / 8; + /// /// Creates a new instance based on this /// configuration. The newly-created instance contains unique key material and is distinct /// from all other descriptors created by the method. /// /// A unique . - IAuthenticatedEncryptorDescriptor CreateNewDescriptor(); + public abstract IAuthenticatedEncryptorDescriptor CreateNewDescriptor(); } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorConfiguration.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorConfiguration.cs index 61aaa082..c3972e4e 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorConfiguration.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorConfiguration.cs @@ -2,42 +2,57 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Cryptography; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel { /// /// Represents a generalized authenticated encryption mechanism. /// - public sealed class AuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration, IInternalAuthenticatedEncryptorConfiguration + public sealed class AuthenticatedEncryptorConfiguration : AlgorithmConfiguration, IInternalAlgorithmConfiguration { - private readonly IServiceProvider _services; - - public AuthenticatedEncryptorConfiguration(AuthenticatedEncryptionSettings settings) - : this(settings, services: null) - { - } - - public AuthenticatedEncryptorConfiguration(AuthenticatedEncryptionSettings settings, IServiceProvider services) + /// + /// The algorithm to use for symmetric encryption (confidentiality). + /// + /// + /// The default value is . + /// + public EncryptionAlgorithm EncryptionAlgorithm { get; set; } = EncryptionAlgorithm.AES_256_CBC; + + /// + /// The algorithm to use for message authentication (tamper-proofing). + /// + /// + /// The default value is . + /// This property is ignored if specifies a 'GCM' algorithm. + /// + public ValidationAlgorithm ValidationAlgorithm { get; set; } = ValidationAlgorithm.HMACSHA256; + + public override IAuthenticatedEncryptorDescriptor CreateNewDescriptor() { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - - Settings = settings; - _services = services; + var internalConfiguration = (IInternalAlgorithmConfiguration)this; + return internalConfiguration.CreateDescriptorFromSecret(Secret.Random(KDK_SIZE_IN_BYTES)); } - public AuthenticatedEncryptionSettings Settings { get; } - - public IAuthenticatedEncryptorDescriptor CreateNewDescriptor() + IAuthenticatedEncryptorDescriptor IInternalAlgorithmConfiguration.CreateDescriptorFromSecret(ISecret secret) { - return this.CreateNewDescriptorCore(); + return new AuthenticatedEncryptorDescriptor(this, secret); } - IAuthenticatedEncryptorDescriptor IInternalAuthenticatedEncryptorConfiguration.CreateDescriptorFromSecret(ISecret secret) + void IInternalAlgorithmConfiguration.Validate() { - return new AuthenticatedEncryptorDescriptor(Settings, secret, _services); + var factory = new AuthenticatedEncryptorFactory(DataProtectionProviderFactory.GetDefaultLoggerFactory()); + // Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly. + var encryptor = factory.CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8), this); + try + { + encryptor.PerformSelfTest(); + } + finally + { + (encryptor as IDisposable)?.Dispose(); + } } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptor.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptor.cs index bed3a894..9539c9eb 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptor.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptor.cs @@ -8,22 +8,15 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat { /// /// A descriptor which can create an authenticated encryption system based upon the - /// configuration provided by an object. + /// configuration provided by an object. /// public sealed class AuthenticatedEncryptorDescriptor : IAuthenticatedEncryptorDescriptor { - private readonly IServiceProvider _services; - - public AuthenticatedEncryptorDescriptor(AuthenticatedEncryptionSettings settings, ISecret masterKey) - : this(settings, masterKey, services: null) - { - } - - public AuthenticatedEncryptorDescriptor(AuthenticatedEncryptionSettings settings, ISecret masterKey, IServiceProvider services) + public AuthenticatedEncryptorDescriptor(AuthenticatedEncryptorConfiguration configuration, ISecret masterKey) { - if (settings == null) + if (configuration == null) { - throw new ArgumentNullException(nameof(settings)); + throw new ArgumentNullException(nameof(configuration)); } if (masterKey == null) @@ -31,19 +24,13 @@ public AuthenticatedEncryptorDescriptor(AuthenticatedEncryptionSettings settings throw new ArgumentNullException(nameof(masterKey)); } - Settings = settings; + Configuration = configuration; MasterKey = masterKey; - _services = services; } internal ISecret MasterKey { get; } - internal AuthenticatedEncryptionSettings Settings { get; } - - public IAuthenticatedEncryptor CreateEncryptorInstance() - { - return Settings.CreateAuthenticatedEncryptorInstance(MasterKey, _services); - } + internal AuthenticatedEncryptorConfiguration Configuration { get; } public XmlSerializedDescriptorInfo ExportToXml() { @@ -54,12 +41,12 @@ public XmlSerializedDescriptorInfo ExportToXml() // var encryptionElement = new XElement("encryption", - new XAttribute("algorithm", Settings.EncryptionAlgorithm)); + new XAttribute("algorithm", Configuration.EncryptionAlgorithm)); - var validationElement = (AuthenticatedEncryptionSettings.IsGcmAlgorithm(Settings.EncryptionAlgorithm)) + var validationElement = (AuthenticatedEncryptorFactory.IsGcmAlgorithm(Configuration.EncryptionAlgorithm)) ? (object)new XComment(" AES-GCM includes a 128-bit authentication tag, no extra validation algorithm required. ") : (object)new XElement("validation", - new XAttribute("algorithm", Settings.ValidationAlgorithm)); + new XAttribute("algorithm", Configuration.ValidationAlgorithm)); var outerElement = new XElement("descriptor", encryptionElement, diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorDeserializer.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorDeserializer.cs index 1628cd28..96737b75 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorDeserializer.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorDeserializer.cs @@ -13,18 +13,6 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat /// public sealed class AuthenticatedEncryptorDescriptorDeserializer : IAuthenticatedEncryptorDescriptorDeserializer { - private readonly IServiceProvider _services; - - public AuthenticatedEncryptorDescriptorDeserializer() - : this(services: null) - { - } - - public AuthenticatedEncryptorDescriptorDeserializer(IServiceProvider services) - { - _services = services; - } - /// /// Imports the from serialized XML. /// @@ -41,20 +29,20 @@ public IAuthenticatedEncryptorDescriptor ImportFromXml(XElement element) // ... // - var settings = new AuthenticatedEncryptionSettings(); + var configuration = new AuthenticatedEncryptorConfiguration(); var encryptionElement = element.Element("encryption"); - settings.EncryptionAlgorithm = (EncryptionAlgorithm)Enum.Parse(typeof(EncryptionAlgorithm), (string)encryptionElement.Attribute("algorithm")); + configuration.EncryptionAlgorithm = (EncryptionAlgorithm)Enum.Parse(typeof(EncryptionAlgorithm), (string)encryptionElement.Attribute("algorithm")); // only read if not GCM - if (!AuthenticatedEncryptionSettings.IsGcmAlgorithm(settings.EncryptionAlgorithm)) + if (!AuthenticatedEncryptorFactory.IsGcmAlgorithm(configuration.EncryptionAlgorithm)) { var validationElement = element.Element("validation"); - settings.ValidationAlgorithm = (ValidationAlgorithm)Enum.Parse(typeof(ValidationAlgorithm), (string)validationElement.Attribute("algorithm")); + configuration.ValidationAlgorithm = (ValidationAlgorithm)Enum.Parse(typeof(ValidationAlgorithm), (string)validationElement.Attribute("algorithm")); } Secret masterKey = ((string)element.Elements("masterKey").Single()).ToSecret(); - return new AuthenticatedEncryptorDescriptor(settings, masterKey, _services); + return new AuthenticatedEncryptorDescriptor(configuration, masterKey); } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorConfiguration.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorConfiguration.cs index 71240451..4b741775 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorConfiguration.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorConfiguration.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; +using Microsoft.AspNetCore.Cryptography; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel { @@ -9,36 +9,91 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat /// Represents a configured authenticated encryption mechanism which uses /// Windows CNG algorithms in CBC encryption + HMAC authentication modes. /// - public sealed class CngCbcAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration, IInternalAuthenticatedEncryptorConfiguration + public sealed class CngCbcAuthenticatedEncryptorConfiguration : AlgorithmConfiguration, IInternalAlgorithmConfiguration { - private readonly IServiceProvider _services; + /// + /// The name of the algorithm to use for symmetric encryption. + /// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider. + /// This property is required to have a value. + /// + /// + /// The algorithm must support CBC-style encryption and must have a block size of 64 bits + /// or greater. + /// The default value is 'AES'. + /// + [ApplyPolicy] + public string EncryptionAlgorithm { get; set; } = Constants.BCRYPT_AES_ALGORITHM; - public CngCbcAuthenticatedEncryptorConfiguration(CngCbcAuthenticatedEncryptionSettings settings) - : this(settings, services: null) - { - } + /// + /// The name of the provider which contains the implementation of the symmetric encryption algorithm. + /// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider. + /// This property is optional. + /// + /// + /// The default value is null. + /// + [ApplyPolicy] + public string EncryptionAlgorithmProvider { get; set; } = null; - public CngCbcAuthenticatedEncryptorConfiguration(CngCbcAuthenticatedEncryptionSettings settings, IServiceProvider services) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } + /// + /// The length (in bits) of the key that will be used for symmetric encryption. + /// This property is required to have a value. + /// + /// + /// The key length must be 128 bits or greater. + /// The default value is 256. + /// + [ApplyPolicy] + public int EncryptionAlgorithmKeySize { get; set; } = 256; - Settings = settings; - _services = services; - } + /// + /// The name of the algorithm to use for hashing data. + /// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider. + /// This property is required to have a value. + /// + /// + /// The algorithm must support being opened in HMAC mode and must have a digest length + /// of 128 bits or greater. + /// The default value is 'SHA256'. + /// + [ApplyPolicy] + public string HashAlgorithm { get; set; } = Constants.BCRYPT_SHA256_ALGORITHM; - public CngCbcAuthenticatedEncryptionSettings Settings { get; } + /// + /// The name of the provider which contains the implementation of the hash algorithm. + /// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider. + /// This property is optional. + /// + /// + /// The default value is null. + /// + [ApplyPolicy] + public string HashAlgorithmProvider { get; set; } = null; - public IAuthenticatedEncryptorDescriptor CreateNewDescriptor() + public override IAuthenticatedEncryptorDescriptor CreateNewDescriptor() { - return this.CreateNewDescriptorCore(); + var internalConfiguration = (IInternalAlgorithmConfiguration)this; + return internalConfiguration.CreateDescriptorFromSecret(Secret.Random(KDK_SIZE_IN_BYTES)); } - IAuthenticatedEncryptorDescriptor IInternalAuthenticatedEncryptorConfiguration.CreateDescriptorFromSecret(ISecret secret) + IAuthenticatedEncryptorDescriptor IInternalAlgorithmConfiguration.CreateDescriptorFromSecret(ISecret secret) { - return new CngCbcAuthenticatedEncryptorDescriptor(Settings, secret, _services); + return new CngCbcAuthenticatedEncryptorDescriptor(this, secret); + } + + /// + /// Validates that this is well-formed, i.e., + /// that the specified algorithms actually exist and that they can be instantiated properly. + /// An exception will be thrown if validation fails. + /// + void IInternalAlgorithmConfiguration.Validate() + { + var factory = new CngCbcAuthenticatedEncryptorFactory(DataProtectionProviderFactory.GetDefaultLoggerFactory()); + // Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly. + using (var encryptor = factory.CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8), this)) + { + encryptor.PerformSelfTest(); + } } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptor.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptor.cs index acc6525e..0003f948 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptor.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptor.cs @@ -3,28 +3,20 @@ using System; using System.Xml.Linq; -using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel { /// /// A descriptor which can create an authenticated encryption system based upon the - /// configuration provided by an object. + /// configuration provided by an object. /// public sealed class CngCbcAuthenticatedEncryptorDescriptor : IAuthenticatedEncryptorDescriptor { - private readonly ILogger _log; - - public CngCbcAuthenticatedEncryptorDescriptor(CngCbcAuthenticatedEncryptionSettings settings, ISecret masterKey) - : this(settings, masterKey, services: null) - { - } - - public CngCbcAuthenticatedEncryptorDescriptor(CngCbcAuthenticatedEncryptionSettings settings, ISecret masterKey, IServiceProvider services) + public CngCbcAuthenticatedEncryptorDescriptor(CngCbcAuthenticatedEncryptorConfiguration configuration, ISecret masterKey) { - if (settings == null) + if (configuration == null) { - throw new ArgumentNullException(nameof(settings)); + throw new ArgumentNullException(nameof(configuration)); } if (masterKey == null) @@ -32,19 +24,13 @@ public CngCbcAuthenticatedEncryptorDescriptor(CngCbcAuthenticatedEncryptionSetti throw new ArgumentNullException(nameof(masterKey)); } - Settings = settings; + Configuration = configuration; MasterKey = masterKey; - _log = services.GetLogger(); } internal ISecret MasterKey { get; } - internal CngCbcAuthenticatedEncryptionSettings Settings { get; } - - public IAuthenticatedEncryptor CreateEncryptorInstance() - { - return Settings.CreateAuthenticatedEncryptorInstance(MasterKey, _log); - } + internal CngCbcAuthenticatedEncryptorConfiguration Configuration { get; } public XmlSerializedDescriptorInfo ExportToXml() { @@ -56,18 +42,18 @@ public XmlSerializedDescriptorInfo ExportToXml() // var encryptionElement = new XElement("encryption", - new XAttribute("algorithm", Settings.EncryptionAlgorithm), - new XAttribute("keyLength", Settings.EncryptionAlgorithmKeySize)); - if (Settings.EncryptionAlgorithmProvider != null) + new XAttribute("algorithm", Configuration.EncryptionAlgorithm), + new XAttribute("keyLength", Configuration.EncryptionAlgorithmKeySize)); + if (Configuration.EncryptionAlgorithmProvider != null) { - encryptionElement.SetAttributeValue("provider", Settings.EncryptionAlgorithmProvider); + encryptionElement.SetAttributeValue("provider", Configuration.EncryptionAlgorithmProvider); } var hashElement = new XElement("hash", - new XAttribute("algorithm", Settings.HashAlgorithm)); - if (Settings.HashAlgorithmProvider != null) + new XAttribute("algorithm", Configuration.HashAlgorithm)); + if (Configuration.HashAlgorithmProvider != null) { - hashElement.SetAttributeValue("provider", Settings.HashAlgorithmProvider); + hashElement.SetAttributeValue("provider", Configuration.HashAlgorithmProvider); } var rootElement = new XElement("descriptor", diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorDeserializer.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorDeserializer.cs index b06659c9..53460483 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorDeserializer.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorDeserializer.cs @@ -12,18 +12,6 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat /// public sealed class CngCbcAuthenticatedEncryptorDescriptorDeserializer : IAuthenticatedEncryptorDescriptorDeserializer { - private readonly IServiceProvider _services; - - public CngCbcAuthenticatedEncryptorDescriptorDeserializer() - : this(services: null) - { - } - - public CngCbcAuthenticatedEncryptorDescriptorDeserializer(IServiceProvider services) - { - _services = services; - } - /// /// Imports the from serialized XML. /// @@ -41,20 +29,20 @@ public IAuthenticatedEncryptorDescriptor ImportFromXml(XElement element) // ... // - var settings = new CngCbcAuthenticatedEncryptionSettings(); + var configuration = new CngCbcAuthenticatedEncryptorConfiguration(); var encryptionElement = element.Element("encryption"); - settings.EncryptionAlgorithm = (string)encryptionElement.Attribute("algorithm"); - settings.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength"); - settings.EncryptionAlgorithmProvider = (string)encryptionElement.Attribute("provider"); // could be null + configuration.EncryptionAlgorithm = (string)encryptionElement.Attribute("algorithm"); + configuration.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength"); + configuration.EncryptionAlgorithmProvider = (string)encryptionElement.Attribute("provider"); // could be null var hashElement = element.Element("hash"); - settings.HashAlgorithm = (string)hashElement.Attribute("algorithm"); - settings.HashAlgorithmProvider = (string)hashElement.Attribute("provider"); // could be null + configuration.HashAlgorithm = (string)hashElement.Attribute("algorithm"); + configuration.HashAlgorithmProvider = (string)hashElement.Attribute("provider"); // could be null Secret masterKey = ((string)element.Element("masterKey")).ToSecret(); - return new CngCbcAuthenticatedEncryptorDescriptor(settings, masterKey, _services); + return new CngCbcAuthenticatedEncryptorDescriptor(configuration, masterKey); } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorConfiguration.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorConfiguration.cs index 980feff3..9cf6e951 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorConfiguration.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorConfiguration.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; +using Microsoft.AspNetCore.Cryptography; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel { @@ -9,36 +9,67 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat /// Represents a configured authenticated encryption mechanism which uses /// Windows CNG algorithms in GCM encryption + authentication modes. /// - public sealed class CngGcmAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration, IInternalAuthenticatedEncryptorConfiguration + public sealed class CngGcmAuthenticatedEncryptorConfiguration : AlgorithmConfiguration, IInternalAlgorithmConfiguration { - private readonly IServiceProvider _services; + /// + /// The name of the algorithm to use for symmetric encryption. + /// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider. + /// This property is required to have a value. + /// + /// + /// The algorithm must support GCM-style encryption and must have a block size exactly + /// 128 bits. + /// The default value is 'AES'. + /// + [ApplyPolicy] + public string EncryptionAlgorithm { get; set; } = Constants.BCRYPT_AES_ALGORITHM; - public CngGcmAuthenticatedEncryptorConfiguration(CngGcmAuthenticatedEncryptionSettings settings) - : this(settings, services: null) - { - } + /// + /// The name of the provider which contains the implementation of the symmetric encryption algorithm. + /// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider. + /// This property is optional. + /// + /// + /// The default value is null. + /// + [ApplyPolicy] + public string EncryptionAlgorithmProvider { get; set; } = null; - public CngGcmAuthenticatedEncryptorConfiguration(CngGcmAuthenticatedEncryptionSettings settings, IServiceProvider services) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } + /// + /// The length (in bits) of the key that will be used for symmetric encryption. + /// This property is required to have a value. + /// + /// + /// The key length must be 128 bits or greater. + /// The default value is 256. + /// + [ApplyPolicy] + public int EncryptionAlgorithmKeySize { get; set; } = 256; - Settings = settings; - _services = services; + public override IAuthenticatedEncryptorDescriptor CreateNewDescriptor() + { + var internalConfiguration = (IInternalAlgorithmConfiguration)this; + return internalConfiguration.CreateDescriptorFromSecret(Secret.Random(KDK_SIZE_IN_BYTES)); } - public CngGcmAuthenticatedEncryptionSettings Settings { get; } - - public IAuthenticatedEncryptorDescriptor CreateNewDescriptor() + IAuthenticatedEncryptorDescriptor IInternalAlgorithmConfiguration.CreateDescriptorFromSecret(ISecret secret) { - return this.CreateNewDescriptorCore(); + return new CngGcmAuthenticatedEncryptorDescriptor(this, secret); } - IAuthenticatedEncryptorDescriptor IInternalAuthenticatedEncryptorConfiguration.CreateDescriptorFromSecret(ISecret secret) + /// + /// Validates that this is well-formed, i.e., + /// that the specified algorithm actually exists and can be instantiated properly. + /// An exception will be thrown if validation fails. + /// + void IInternalAlgorithmConfiguration.Validate() { - return new CngGcmAuthenticatedEncryptorDescriptor(Settings, secret, _services); + var factory = new CngGcmAuthenticatedEncryptorFactory(DataProtectionProviderFactory.GetDefaultLoggerFactory()); + // Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly. + using (var encryptor = factory.CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8), this)) + { + encryptor.PerformSelfTest(); + } } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptor.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptor.cs index fe631d94..28c0103a 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptor.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptor.cs @@ -9,22 +9,15 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat { /// /// A descriptor which can create an authenticated encryption system based upon the - /// configuration provided by an object. + /// configuration provided by an object. /// public sealed class CngGcmAuthenticatedEncryptorDescriptor : IAuthenticatedEncryptorDescriptor { - private readonly ILogger _log; - - public CngGcmAuthenticatedEncryptorDescriptor(CngGcmAuthenticatedEncryptionSettings settings, ISecret masterKey) - : this(settings, masterKey, services: null) - { - } - - public CngGcmAuthenticatedEncryptorDescriptor(CngGcmAuthenticatedEncryptionSettings settings, ISecret masterKey, IServiceProvider services) + public CngGcmAuthenticatedEncryptorDescriptor(CngGcmAuthenticatedEncryptorConfiguration configuration, ISecret masterKey) { - if (settings == null) + if (configuration == null) { - throw new ArgumentNullException(nameof(settings)); + throw new ArgumentNullException(nameof(configuration)); } if (masterKey == null) @@ -32,19 +25,13 @@ public CngGcmAuthenticatedEncryptorDescriptor(CngGcmAuthenticatedEncryptionSetti throw new ArgumentNullException(nameof(masterKey)); } - Settings = settings; + Configuration = configuration; MasterKey = masterKey; - _log = services.GetLogger(); } internal ISecret MasterKey { get; } - internal CngGcmAuthenticatedEncryptionSettings Settings { get; } - - public IAuthenticatedEncryptor CreateEncryptorInstance() - { - return Settings.CreateAuthenticatedEncryptorInstance(MasterKey, _log); - } + internal CngGcmAuthenticatedEncryptorConfiguration Configuration { get; } public XmlSerializedDescriptorInfo ExportToXml() { @@ -55,11 +42,11 @@ public XmlSerializedDescriptorInfo ExportToXml() // var encryptionElement = new XElement("encryption", - new XAttribute("algorithm", Settings.EncryptionAlgorithm), - new XAttribute("keyLength", Settings.EncryptionAlgorithmKeySize)); - if (Settings.EncryptionAlgorithmProvider != null) + new XAttribute("algorithm", Configuration.EncryptionAlgorithm), + new XAttribute("keyLength", Configuration.EncryptionAlgorithmKeySize)); + if (Configuration.EncryptionAlgorithmProvider != null) { - encryptionElement.SetAttributeValue("provider", Settings.EncryptionAlgorithmProvider); + encryptionElement.SetAttributeValue("provider", Configuration.EncryptionAlgorithmProvider); } var rootElement = new XElement("descriptor", diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorDeserializer.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorDeserializer.cs index 1a980dfe..0981fb55 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorDeserializer.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorDeserializer.cs @@ -12,17 +12,6 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat /// public sealed class CngGcmAuthenticatedEncryptorDescriptorDeserializer : IAuthenticatedEncryptorDescriptorDeserializer { - private readonly IServiceProvider _services; - - public CngGcmAuthenticatedEncryptorDescriptorDeserializer() - : this(services: null) - { - } - - public CngGcmAuthenticatedEncryptorDescriptorDeserializer(IServiceProvider services) - { - _services = services; - } /// /// Imports the from serialized XML. @@ -40,16 +29,16 @@ public IAuthenticatedEncryptorDescriptor ImportFromXml(XElement element) // ... // - var settings = new CngGcmAuthenticatedEncryptionSettings(); + var configuration = new CngGcmAuthenticatedEncryptorConfiguration(); var encryptionElement = element.Element("encryption"); - settings.EncryptionAlgorithm = (string)encryptionElement.Attribute("algorithm"); - settings.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength"); - settings.EncryptionAlgorithmProvider = (string)encryptionElement.Attribute("provider"); // could be null + configuration.EncryptionAlgorithm = (string)encryptionElement.Attribute("algorithm"); + configuration.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength"); + configuration.EncryptionAlgorithmProvider = (string)encryptionElement.Attribute("provider"); // could be null Secret masterKey = ((string)element.Element("masterKey")).ToSecret(); - return new CngGcmAuthenticatedEncryptorDescriptor(settings, masterKey, _services); + return new CngGcmAuthenticatedEncryptorDescriptor(configuration, masterKey); } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ConfigurationCommon.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ConfigurationCommon.cs deleted file mode 100644 index 359e0a19..00000000 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ConfigurationCommon.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel -{ - internal static class ConfigurationCommon - { - /// - /// Creates an from this - /// using a random 512-bit master key generated from a secure PRNG. - /// - public static IAuthenticatedEncryptorDescriptor CreateNewDescriptorCore(this IInternalAuthenticatedEncryptorConfiguration configuration) - { - const int KDK_SIZE_IN_BYTES = 512 / 8; - return configuration.CreateDescriptorFromSecret(Secret.Random(KDK_SIZE_IN_BYTES)); - } - } -} diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IAuthenticatedEncryptorDescriptor.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IAuthenticatedEncryptorDescriptor.cs index f4c51284..61769295 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IAuthenticatedEncryptorDescriptor.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IAuthenticatedEncryptorDescriptor.cs @@ -12,17 +12,6 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat /// public interface IAuthenticatedEncryptorDescriptor { - /// - /// Creates an instance based on the current descriptor. - /// - /// An instance. - /// - /// For a given descriptor, any two instances returned by this method should - /// be considered equivalent, e.g., the payload returned by one's - /// method should be consumable by the other's method. - /// - IAuthenticatedEncryptor CreateEncryptorInstance(); - /// /// Exports the current descriptor to XML. /// diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IInternalAuthenticatedEncryptorConfiguration.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IInternalAlgorithmConfiguration.cs similarity index 50% rename from src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IInternalAuthenticatedEncryptorConfiguration.cs rename to src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IInternalAlgorithmConfiguration.cs index bd5ba204..ede736e9 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IInternalAuthenticatedEncryptorConfiguration.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/IInternalAlgorithmConfiguration.cs @@ -1,24 +1,27 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using Microsoft.Extensions.Logging; - namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel { - // This type is not public because we don't want to lock ourselves into a contract stating - // that a descriptor is simply a configuration plus a single serializable, reproducible secret. - /// /// A type that knows how to create instances of an /// given specific secret key material. /// - internal interface IInternalAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration + /// + /// This type is not public because we don't want to lock ourselves into a contract stating + /// that a descriptor is simply a configuration plus a single serializable, reproducible secret. + /// + internal interface IInternalAlgorithmConfiguration { /// - /// Creates a new instance from this - /// configuration given specific secret key material. + /// Creates a new instance from this configuration + /// given specific secret key material. /// IAuthenticatedEncryptorDescriptor CreateDescriptorFromSecret(ISecret secret); + + /// + /// Performs a self-test of the algorithm specified by the configuration object. + /// + void Validate(); } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorConfiguration.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorConfiguration.cs index 077b4f6e..b437d59b 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorConfiguration.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorConfiguration.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Security.Cryptography; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel { @@ -10,36 +11,97 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat /// managed and /// types. /// - public sealed class ManagedAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration, IInternalAuthenticatedEncryptorConfiguration + public sealed class ManagedAuthenticatedEncryptorConfiguration : AlgorithmConfiguration, IInternalAlgorithmConfiguration { - private readonly IServiceProvider _services; + /// + /// The type of the algorithm to use for symmetric encryption. + /// The type must subclass . + /// This property is required to have a value. + /// + /// + /// The algorithm must support CBC-style encryption and PKCS#7 padding and must have a block size of 64 bits or greater. + /// The default algorithm is AES. + /// + [ApplyPolicy] + public Type EncryptionAlgorithmType { get; set; } = typeof(Aes); - public ManagedAuthenticatedEncryptorConfiguration(ManagedAuthenticatedEncryptionSettings settings) - : this(settings, services: null) + /// + /// The length (in bits) of the key that will be used for symmetric encryption. + /// This property is required to have a value. + /// + /// + /// The key length must be 128 bits or greater. + /// The default value is 256. + /// + [ApplyPolicy] + public int EncryptionAlgorithmKeySize { get; set; } = 256; + + /// + /// The type of the algorithm to use for validation. + /// Type type must subclass . + /// This property is required to have a value. + /// + /// + /// The algorithm must have a digest length of 128 bits or greater. + /// The default algorithm is HMACSHA256. + /// + [ApplyPolicy] + public Type ValidationAlgorithmType { get; set; } = typeof(HMACSHA256); + + public override IAuthenticatedEncryptorDescriptor CreateNewDescriptor() { + var internalConfiguration = (IInternalAlgorithmConfiguration)this; + return internalConfiguration.CreateDescriptorFromSecret(Secret.Random(KDK_SIZE_IN_BYTES)); } - public ManagedAuthenticatedEncryptorConfiguration(ManagedAuthenticatedEncryptionSettings settings, IServiceProvider services) + IAuthenticatedEncryptorDescriptor IInternalAlgorithmConfiguration.CreateDescriptorFromSecret(ISecret secret) { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - - Settings = settings; - _services = services; + return new ManagedAuthenticatedEncryptorDescriptor(this, secret); } - public ManagedAuthenticatedEncryptionSettings Settings { get; } - - public IAuthenticatedEncryptorDescriptor CreateNewDescriptor() + /// + /// Validates that this is well-formed, i.e., + /// that the specified algorithms actually exist and can be instantiated properly. + /// An exception will be thrown if validation fails. + /// + void IInternalAlgorithmConfiguration.Validate() { - return this.CreateNewDescriptorCore(); + var factory = new ManagedAuthenticatedEncryptorFactory(DataProtectionProviderFactory.GetDefaultLoggerFactory()); + // Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly. + using (var encryptor = factory.CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8), this)) + { + encryptor.PerformSelfTest(); + } } - IAuthenticatedEncryptorDescriptor IInternalAuthenticatedEncryptorConfiguration.CreateDescriptorFromSecret(ISecret secret) + // Any changes to this method should also be be reflected + // in ManagedAuthenticatedEncryptorDescriptorDeserializer.FriendlyNameToType. + private static string TypeToFriendlyName(Type type) { - return new ManagedAuthenticatedEncryptorDescriptor(Settings, secret, _services); + if (type == typeof(Aes)) + { + return nameof(Aes); + } + else if (type == typeof(HMACSHA1)) + { + return nameof(HMACSHA1); + } + else if (type == typeof(HMACSHA256)) + { + return nameof(HMACSHA256); + } + else if (type == typeof(HMACSHA384)) + { + return nameof(HMACSHA384); + } + else if (type == typeof(HMACSHA512)) + { + return nameof(HMACSHA512); + } + else + { + return type.AssemblyQualifiedName; + } } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptor.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptor.cs index 62d2bae7..2061115b 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptor.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptor.cs @@ -4,28 +4,20 @@ using System; using System.Security.Cryptography; using System.Xml.Linq; -using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel { /// /// A descriptor which can create an authenticated encryption system based upon the - /// configuration provided by an object. + /// configuration provided by an object. /// public sealed class ManagedAuthenticatedEncryptorDescriptor : IAuthenticatedEncryptorDescriptor { - private readonly ILogger _log; - - public ManagedAuthenticatedEncryptorDescriptor(ManagedAuthenticatedEncryptionSettings settings, ISecret masterKey) - : this(settings, masterKey, services: null) - { - } - - public ManagedAuthenticatedEncryptorDescriptor(ManagedAuthenticatedEncryptionSettings settings, ISecret masterKey, IServiceProvider services) + public ManagedAuthenticatedEncryptorDescriptor(ManagedAuthenticatedEncryptorConfiguration configuration, ISecret masterKey) { - if (settings == null) + if (configuration == null) { - throw new ArgumentNullException(nameof(settings)); + throw new ArgumentNullException(nameof(configuration)); } if (masterKey == null) @@ -33,19 +25,13 @@ public ManagedAuthenticatedEncryptorDescriptor(ManagedAuthenticatedEncryptionSet throw new ArgumentNullException(nameof(masterKey)); } - Settings = settings; + Configuration = configuration; MasterKey = masterKey; - _log = services.GetLogger(); } internal ISecret MasterKey { get; } - internal ManagedAuthenticatedEncryptionSettings Settings { get; } - - public IAuthenticatedEncryptor CreateEncryptorInstance() - { - return Settings.CreateAuthenticatedEncryptorInstance(MasterKey, _log); - } + internal ManagedAuthenticatedEncryptorConfiguration Configuration { get; } public XmlSerializedDescriptorInfo ExportToXml() { @@ -57,11 +43,11 @@ public XmlSerializedDescriptorInfo ExportToXml() // var encryptionElement = new XElement("encryption", - new XAttribute("algorithm", TypeToFriendlyName(Settings.EncryptionAlgorithmType)), - new XAttribute("keyLength", Settings.EncryptionAlgorithmKeySize)); + new XAttribute("algorithm", TypeToFriendlyName(Configuration.EncryptionAlgorithmType)), + new XAttribute("keyLength", Configuration.EncryptionAlgorithmKeySize)); var validationElement = new XElement("validation", - new XAttribute("algorithm", TypeToFriendlyName(Settings.ValidationAlgorithmType))); + new XAttribute("algorithm", TypeToFriendlyName(Configuration.ValidationAlgorithmType))); var rootElement = new XElement("descriptor", new XComment(" Algorithms provided by specified SymmetricAlgorithm and KeyedHashAlgorithm "), diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorDeserializer.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorDeserializer.cs index 5766051b..0f09c3a5 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorDeserializer.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorDeserializer.cs @@ -13,18 +13,6 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat /// public sealed class ManagedAuthenticatedEncryptorDescriptorDeserializer : IAuthenticatedEncryptorDescriptorDeserializer { - private readonly IServiceProvider _services; - - public ManagedAuthenticatedEncryptorDescriptorDeserializer() - : this(services: null) - { - } - - public ManagedAuthenticatedEncryptorDescriptorDeserializer(IServiceProvider services) - { - _services = services; - } - /// /// Imports the from serialized XML. /// @@ -42,18 +30,18 @@ public IAuthenticatedEncryptorDescriptor ImportFromXml(XElement element) // ... // - var settings = new ManagedAuthenticatedEncryptionSettings(); + var configuration = new ManagedAuthenticatedEncryptorConfiguration(); var encryptionElement = element.Element("encryption"); - settings.EncryptionAlgorithmType = FriendlyNameToType((string)encryptionElement.Attribute("algorithm")); - settings.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength"); + configuration.EncryptionAlgorithmType = FriendlyNameToType((string)encryptionElement.Attribute("algorithm")); + configuration.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength"); var validationElement = element.Element("validation"); - settings.ValidationAlgorithmType = FriendlyNameToType((string)validationElement.Attribute("algorithm")); + configuration.ValidationAlgorithmType = FriendlyNameToType((string)validationElement.Attribute("algorithm")); Secret masterKey = ((string)element.Element("masterKey")).ToSecret(); - return new ManagedAuthenticatedEncryptorDescriptor(settings, masterKey, _services); + return new ManagedAuthenticatedEncryptorDescriptor(configuration, masterKey); } // Any changes to this method should also be be reflected diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/EncryptionAlgorithm.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/EncryptionAlgorithm.cs index 20eec3ec..d6fbf280 100644 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/EncryptionAlgorithm.cs +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/EncryptionAlgorithm.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; - namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption { /// diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/IAuthenticatedEncryptorFactory.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/IAuthenticatedEncryptorFactory.cs new file mode 100644 index 00000000..b66f1442 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/IAuthenticatedEncryptorFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.DataProtection.KeyManagement; + +namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption +{ + public interface IAuthenticatedEncryptorFactory + { + /// + /// Creates an instance based on the given . + /// + /// An instance. + /// + /// For a given , any two instances returned by this method should + /// be considered equivalent, e.g., the payload returned by one's + /// method should be consumable by the other's method. + /// + IAuthenticatedEncryptor CreateEncryptorInstance(IKey key); + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/IInternalAuthenticatedEncryptionSettings.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/IInternalAuthenticatedEncryptionSettings.cs deleted file mode 100644 index 30c2113c..00000000 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/IInternalAuthenticatedEncryptionSettings.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; - -namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption -{ - /// - /// Implemented by our settings classes to generalize creating configuration objects. - /// - internal interface IInternalAuthenticatedEncryptionSettings - { - /// - /// Creates a object - /// from the given settings. - /// - IInternalAuthenticatedEncryptorConfiguration ToConfiguration(IServiceProvider services); - - /// - /// Performs a self-test of the algorithm specified by the settings object. - /// - void Validate(); - } -} diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ManagedAuthenticatedEncryptionSettings.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ManagedAuthenticatedEncryptionSettings.cs deleted file mode 100644 index 70bc7aa9..00000000 --- a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ManagedAuthenticatedEncryptionSettings.cs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Security.Cryptography; -using Microsoft.AspNetCore.Cryptography.Cng; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; -using Microsoft.AspNetCore.DataProtection.Managed; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption -{ - /// - /// Settings for configuring an authenticated encryption mechanism which uses - /// managed SymmetricAlgorithm and KeyedHashAlgorithm implementations. - /// - public sealed class ManagedAuthenticatedEncryptionSettings : IInternalAuthenticatedEncryptionSettings - { - /// - /// The type of the algorithm to use for symmetric encryption. - /// The type must subclass . - /// This property is required to have a value. - /// - /// - /// The algorithm must support CBC-style encryption and PKCS#7 padding and must have a block size of 64 bits or greater. - /// The default algorithm is AES. - /// - [ApplyPolicy] - public Type EncryptionAlgorithmType { get; set; } = typeof(Aes); - - /// - /// The length (in bits) of the key that will be used for symmetric encryption. - /// This property is required to have a value. - /// - /// - /// The key length must be 128 bits or greater. - /// The default value is 256. - /// - [ApplyPolicy] - public int EncryptionAlgorithmKeySize { get; set; } = 256; - - /// - /// The type of the algorithm to use for validation. - /// Type type must subclass . - /// This property is required to have a value. - /// - /// - /// The algorithm must have a digest length of 128 bits or greater. - /// The default algorithm is HMACSHA256. - /// - [ApplyPolicy] - public Type ValidationAlgorithmType { get; set; } = typeof(HMACSHA256); - - /// - /// Validates that this is well-formed, i.e., - /// that the specified algorithms actually exist and can be instantiated properly. - /// An exception will be thrown if validation fails. - /// - public void Validate() - { - // Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly. - using (var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8))) - { - encryptor.PerformSelfTest(); - } - } - - /* - * HELPER ROUTINES - */ - - internal ManagedAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(ISecret secret, ILogger logger = null) - { - return new ManagedAuthenticatedEncryptor( - keyDerivationKey: new Secret(secret), - symmetricAlgorithmFactory: GetSymmetricBlockCipherAlgorithmFactory(logger), - symmetricAlgorithmKeySizeInBytes: EncryptionAlgorithmKeySize / 8, - validationAlgorithmFactory: GetKeyedHashAlgorithmFactory(logger)); - } - - private Func GetKeyedHashAlgorithmFactory(ILogger logger) - { - // basic argument checking - if (ValidationAlgorithmType == null) - { - throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(ValidationAlgorithmType)); - } - - logger?.UsingManagedKeyedHashAlgorithm(ValidationAlgorithmType.FullName); - if (ValidationAlgorithmType == typeof(HMACSHA256)) - { - return () => new HMACSHA256(); - } - else if (ValidationAlgorithmType == typeof(HMACSHA512)) - { - return () => new HMACSHA512(); - } - else - { - return AlgorithmActivator.CreateFactory(ValidationAlgorithmType); - } - } - - private Func GetSymmetricBlockCipherAlgorithmFactory(ILogger logger) - { - // basic argument checking - if (EncryptionAlgorithmType == null) - { - throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithmType)); - } - typeof(SymmetricAlgorithm).AssertIsAssignableFrom(EncryptionAlgorithmType); - if (EncryptionAlgorithmKeySize < 0) - { - throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize)); - } - - logger?.UsingManagedSymmetricAlgorithm(EncryptionAlgorithmType.FullName); - - if (EncryptionAlgorithmType == typeof(Aes)) - { - Func factory = null; -#if !NETSTANDARD1_3 - if (OSVersionUtil.IsWindows()) - { - // If we're on desktop CLR and running on Windows, use the FIPS-compliant implementation. - factory = () => new AesCryptoServiceProvider(); - } -#endif - return factory ?? Aes.Create; - } - else - { - return AlgorithmActivator.CreateFactory(EncryptionAlgorithmType); - } - } - - IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionSettings.ToConfiguration(IServiceProvider services) - { - return new ManagedAuthenticatedEncryptorConfiguration(this, services); - } - - /// - /// Contains helper methods for generating cryptographic algorithm factories. - /// - private static class AlgorithmActivator - { - /// - /// Creates a factory that wraps a call to . - /// - public static Func CreateFactory(Type implementation) - { - return ((IActivator)Activator.CreateInstance(typeof(AlgorithmActivatorCore<>).MakeGenericType(implementation))).Creator; - } - - private interface IActivator - { - Func Creator { get; } - } - - private class AlgorithmActivatorCore : IActivator where T : new() - { - public Func Creator { get; } = Activator.CreateInstance; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactory.cs b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactory.cs new file mode 100644 index 00000000..a0d7bc22 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactory.cs @@ -0,0 +1,130 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Cryptography.Cng; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.Managed; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption +{ + public sealed class ManagedAuthenticatedEncryptorFactory : IAuthenticatedEncryptorFactory + { + private readonly ILogger _logger; + + public ManagedAuthenticatedEncryptorFactory(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public IAuthenticatedEncryptor CreateEncryptorInstance(IKey key) + { + var descriptor = key.Descriptor as ManagedAuthenticatedEncryptorDescriptor; + if (descriptor == null) + { + return null; + } + + return CreateAuthenticatedEncryptorInstance(descriptor.MasterKey, descriptor.Configuration); + } + + internal ManagedAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance( + ISecret secret, + ManagedAuthenticatedEncryptorConfiguration configuration) + { + if (configuration == null) + { + return null; + } + + return new ManagedAuthenticatedEncryptor( + keyDerivationKey: new Secret(secret), + symmetricAlgorithmFactory: GetSymmetricBlockCipherAlgorithmFactory(configuration), + symmetricAlgorithmKeySizeInBytes: configuration.EncryptionAlgorithmKeySize / 8, + validationAlgorithmFactory: GetKeyedHashAlgorithmFactory(configuration)); + } + + private Func GetKeyedHashAlgorithmFactory(ManagedAuthenticatedEncryptorConfiguration configuration) + { + // basic argument checking + if (configuration.ValidationAlgorithmType == null) + { + throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(configuration.ValidationAlgorithmType)); + } + + _logger.UsingManagedKeyedHashAlgorithm(configuration.ValidationAlgorithmType.FullName); + if (configuration.ValidationAlgorithmType == typeof(HMACSHA256)) + { + return () => new HMACSHA256(); + } + else if (configuration.ValidationAlgorithmType == typeof(HMACSHA512)) + { + return () => new HMACSHA512(); + } + else + { + return AlgorithmActivator.CreateFactory(configuration.ValidationAlgorithmType); + } + } + + private Func GetSymmetricBlockCipherAlgorithmFactory(ManagedAuthenticatedEncryptorConfiguration configuration) + { + // basic argument checking + if (configuration.EncryptionAlgorithmType == null) + { + throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(configuration.EncryptionAlgorithmType)); + } + typeof(SymmetricAlgorithm).AssertIsAssignableFrom(configuration.EncryptionAlgorithmType); + if (configuration.EncryptionAlgorithmKeySize < 0) + { + throw Error.Common_PropertyMustBeNonNegative(nameof(configuration.EncryptionAlgorithmKeySize)); + } + + _logger.UsingManagedSymmetricAlgorithm(configuration.EncryptionAlgorithmType.FullName); + + if (configuration.EncryptionAlgorithmType == typeof(Aes)) + { + Func factory = null; +#if !NETSTANDARD1_3 + if (OSVersionUtil.IsWindows()) + { + // If we're on desktop CLR and running on Windows, use the FIPS-compliant implementation. + factory = () => new AesCryptoServiceProvider(); + } +#endif + return factory ?? Aes.Create; + } + else + { + return AlgorithmActivator.CreateFactory(configuration.EncryptionAlgorithmType); + } + } + + /// + /// Contains helper methods for generating cryptographic algorithm factories. + /// + private static class AlgorithmActivator + { + /// + /// Creates a factory that wraps a call to . + /// + public static Func CreateFactory(Type implementation) + { + return ((IActivator)Activator.CreateInstance(typeof(AlgorithmActivatorCore<>).MakeGenericType(implementation))).Creator; + } + + private interface IActivator + { + Func Creator { get; } + } + + private class AlgorithmActivatorCore : IActivator where T : new() + { + public Func Creator { get; } = Activator.CreateInstance; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs b/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs index 74084f9d..e7ca436f 100644 --- a/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs @@ -4,11 +4,16 @@ using System; using System.ComponentModel; using System.IO; +using Microsoft.AspNetCore.Cryptography; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.AspNetCore.DataProtection.XmlEncryption; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.Win32; #if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml @@ -68,7 +73,11 @@ public static IDataProtectionBuilder AddKeyEscrowSink(this IDataProtectionBuilde throw new ArgumentNullException(nameof(sink)); } - builder.Services.AddSingleton(sink); + builder.Services.Configure(options => + { + options.KeyEscrowSinks.Add(sink); + }); + return builder; } @@ -89,7 +98,15 @@ public static IDataProtectionBuilder AddKeyEscrowSink(this IDat throw new ArgumentNullException(nameof(builder)); } - builder.Services.AddSingleton(); + builder.Services.AddSingleton>(services => + { + var implementationInstance = services.GetRequiredService(); + return new ConfigureOptions(options => + { + options.KeyEscrowSinks.Add(implementationInstance); + }); + }); + return builder; } @@ -114,7 +131,15 @@ public static IDataProtectionBuilder AddKeyEscrowSink(this IDataProtectionBuilde throw new ArgumentNullException(nameof(factory)); } - builder.Services.AddSingleton(factory); + builder.Services.AddSingleton>(services => + { + var instance = factory(services); + return new ConfigureOptions(options => + { + options.KeyEscrowSinks.Add(instance); + }); + }); + return builder; } @@ -182,7 +207,15 @@ public static IDataProtectionBuilder PersistKeysToFileSystem(this IDataProtectio throw new ArgumentNullException(nameof(directory)); } - Use(builder.Services, DataProtectionServiceDescriptors.IXmlRepository_FileSystem(directory)); + builder.Services.AddSingleton>(services => + { + var loggerFactory = services.GetRequiredService(); + return new ConfigureOptions(options => + { + options.XmlRepository = new FileSystemXmlRepository(directory, loggerFactory); + }); + }); + return builder; } @@ -204,7 +237,15 @@ public static IDataProtectionBuilder PersistKeysToRegistry(this IDataProtectionB throw new ArgumentNullException(nameof(registryKey)); } - Use(builder.Services, DataProtectionServiceDescriptors.IXmlRepository_Registry(registryKey)); + builder.Services.AddSingleton>(services => + { + var loggerFactory = services.GetRequiredService(); + return new ConfigureOptions(options => + { + options.XmlRepository = new RegistryXmlRepository(registryKey, loggerFactory); + }); + }); + return builder; } @@ -228,7 +269,15 @@ public static IDataProtectionBuilder ProtectKeysWithCertificate(this IDataProtec throw new ArgumentNullException(nameof(certificate)); } - Use(builder.Services, DataProtectionServiceDescriptors.IXmlEncryptor_Certificate(certificate)); + builder.Services.AddSingleton>(services => + { + var loggerFactory = services.GetRequiredService(); + return new ConfigureOptions(options => + { + options.XmlEncryptor = new CertificateXmlEncryptor(certificate, loggerFactory); + }); + }); + return builder; } @@ -256,12 +305,20 @@ public static IDataProtectionBuilder ProtectKeysWithCertificate(this IDataProtec throw Error.CertificateXmlEncryptor_CertificateNotFound(thumbprint); } - var services = builder.Services; - // ICertificateResolver is necessary for this type to work correctly, so register it // if it doesn't already exist. - services.TryAdd(DataProtectionServiceDescriptors.ICertificateResolver_Default()); - Use(services, DataProtectionServiceDescriptors.IXmlEncryptor_Certificate(thumbprint)); + builder.Services.TryAddSingleton(); + + builder.Services.AddSingleton>(services => + { + var loggerFactory = services.GetRequiredService(); + var certificateResolver = services.GetRequiredService(); + return new ConfigureOptions(options => + { + options.XmlEncryptor = new CertificateXmlEncryptor(thumbprint, certificateResolver, loggerFactory); + }); + }); + return builder; } @@ -305,7 +362,16 @@ public static IDataProtectionBuilder ProtectKeysWithDpapi(this IDataProtectionBu throw new ArgumentNullException(nameof(builder)); } - Use(builder.Services, DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToLocalMachine)); + builder.Services.AddSingleton>(services => + { + var loggerFactory = services.GetRequiredService(); + return new ConfigureOptions(options => + { + CryptoUtil.AssertPlatformIsWindows(); + options.XmlEncryptor = new DpapiXmlEncryptor(protectToLocalMachine, loggerFactory); + }); + }); + return builder; } @@ -358,7 +424,16 @@ public static IDataProtectionBuilder ProtectKeysWithDpapiNG(this IDataProtection throw new ArgumentNullException(nameof(protectionDescriptorRule)); } - Use(builder.Services, DataProtectionServiceDescriptors.IXmlEncryptor_DpapiNG(protectionDescriptorRule, flags)); + builder.Services.AddSingleton>(services => + { + var loggerFactory = services.GetRequiredService(); + return new ConfigureOptions(options => + { + CryptoUtil.AssertPlatformIsWindows8OrLater(); + options.XmlEncryptor = new DpapiNGXmlEncryptor(protectionDescriptorRule, flags, loggerFactory); + }); + }); + return builder; } @@ -395,21 +470,21 @@ public static IDataProtectionBuilder SetDefaultKeyLifetime(this IDataProtectionB /// by default when generating protected payloads. /// /// The . - /// Information about what cryptographic algorithms should be used. + /// Information about what cryptographic algorithms should be used. /// A reference to the after this operation has completed. - public static IDataProtectionBuilder UseCryptographicAlgorithms(this IDataProtectionBuilder builder, AuthenticatedEncryptionSettings settings) + public static IDataProtectionBuilder UseCryptographicAlgorithms(this IDataProtectionBuilder builder, AuthenticatedEncryptorConfiguration configuration) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } - if (settings == null) + if (configuration == null) { - throw new ArgumentNullException(nameof(settings)); + throw new ArgumentNullException(nameof(configuration)); } - return UseCryptographicAlgorithmsCore(builder, settings); + return UseCryptographicAlgorithmsCore(builder, configuration); } /// @@ -419,25 +494,25 @@ public static IDataProtectionBuilder UseCryptographicAlgorithms(this IDataProtec /// enumerations. /// /// The . - /// Information about what cryptographic algorithms should be used. + /// Information about what cryptographic algorithms should be used. /// A reference to the after this operation has completed. /// /// This API is only available on Windows. /// [EditorBrowsable(EditorBrowsableState.Advanced)] - public static IDataProtectionBuilder UseCustomCryptographicAlgorithms(this IDataProtectionBuilder builder, CngCbcAuthenticatedEncryptionSettings settings) + public static IDataProtectionBuilder UseCustomCryptographicAlgorithms(this IDataProtectionBuilder builder, CngCbcAuthenticatedEncryptorConfiguration configuration) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } - if (settings == null) + if (configuration == null) { - throw new ArgumentNullException(nameof(settings)); + throw new ArgumentNullException(nameof(configuration)); } - return UseCryptographicAlgorithmsCore(builder, settings); + return UseCryptographicAlgorithmsCore(builder, configuration); } /// @@ -447,25 +522,25 @@ public static IDataProtectionBuilder UseCustomCryptographicAlgorithms(this IData /// enumerations. /// /// The . - /// Information about what cryptographic algorithms should be used. + /// Information about what cryptographic algorithms should be used. /// A reference to the after this operation has completed. /// /// This API is only available on Windows. /// [EditorBrowsable(EditorBrowsableState.Advanced)] - public static IDataProtectionBuilder UseCustomCryptographicAlgorithms(this IDataProtectionBuilder builder, CngGcmAuthenticatedEncryptionSettings settings) + public static IDataProtectionBuilder UseCustomCryptographicAlgorithms(this IDataProtectionBuilder builder, CngGcmAuthenticatedEncryptorConfiguration configuration) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } - if (settings == null) + if (configuration == null) { - throw new ArgumentNullException(nameof(settings)); + throw new ArgumentNullException(nameof(configuration)); } - return UseCryptographicAlgorithmsCore(builder, settings); + return UseCryptographicAlgorithmsCore(builder, configuration); } /// @@ -475,28 +550,33 @@ public static IDataProtectionBuilder UseCustomCryptographicAlgorithms(this IData /// enumerations. /// /// The . - /// Information about what cryptographic algorithms should be used. + /// Information about what cryptographic algorithms should be used. /// A reference to the after this operation has completed. [EditorBrowsable(EditorBrowsableState.Advanced)] - public static IDataProtectionBuilder UseCustomCryptographicAlgorithms(this IDataProtectionBuilder builder, ManagedAuthenticatedEncryptionSettings settings) + public static IDataProtectionBuilder UseCustomCryptographicAlgorithms(this IDataProtectionBuilder builder, ManagedAuthenticatedEncryptorConfiguration configuration) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } - if (settings == null) + if (configuration == null) { - throw new ArgumentNullException(nameof(settings)); + throw new ArgumentNullException(nameof(configuration)); } - return UseCryptographicAlgorithmsCore(builder, settings); + return UseCryptographicAlgorithmsCore(builder, configuration); } - private static IDataProtectionBuilder UseCryptographicAlgorithmsCore(IDataProtectionBuilder builder, IInternalAuthenticatedEncryptionSettings settings) + private static IDataProtectionBuilder UseCryptographicAlgorithmsCore(IDataProtectionBuilder builder, AlgorithmConfiguration configuration) { - settings.Validate(); // perform self-test - Use(builder.Services, DataProtectionServiceDescriptors.IAuthenticatedEncryptorConfiguration_FromSettings(settings)); + ((IInternalAlgorithmConfiguration)configuration).Validate(); // perform self-test + + builder.Services.Configure(options => + { + options.AuthenticatedEncryptorConfiguration = configuration; + }); + return builder; } @@ -517,30 +597,9 @@ public static IDataProtectionBuilder UseEphemeralDataProtectionProvider(this IDa throw new ArgumentNullException(nameof(builder)); } - Use(builder.Services, DataProtectionServiceDescriptors.IDataProtectionProvider_Ephemeral()); - return builder; - } + builder.Services.Replace(ServiceDescriptor.Singleton()); - /* - * UTILITY ISERVICECOLLECTION METHODS - */ - - private static void RemoveAllServicesOfType(IServiceCollection services, Type serviceType) - { - // We go backward since we're modifying the collection in-place. - for (var i = services.Count - 1; i >= 0; i--) - { - if (services[i]?.ServiceType == serviceType) - { - services.RemoveAt(i); - } - } - } - - private static void Use(IServiceCollection services, ServiceDescriptor descriptor) - { - RemoveAllServicesOfType(services, descriptor.ServiceType); - services.Add(descriptor); + return builder; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.DataProtection/DataProtectionProviderFactory.cs b/src/Microsoft.AspNetCore.DataProtection/DataProtectionProviderFactory.cs index d9ec04dd..4f05478c 100644 --- a/src/Microsoft.AspNetCore.DataProtection/DataProtectionProviderFactory.cs +++ b/src/Microsoft.AspNetCore.DataProtection/DataProtectionProviderFactory.cs @@ -1,79 +1,16 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using Microsoft.AspNetCore.DataProtection.KeyManagement; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.DataProtection { - /// - /// Contains static factory methods for creating instances. - /// internal static class DataProtectionProviderFactory { - /// - /// Creates an given an . - /// - /// The global options to use when creating the provider. - /// Provides mandatory services for use by the provider. - /// An . - public static IDataProtectionProvider GetProviderFromServices(DataProtectionOptions options, IServiceProvider services) + public static ILoggerFactory GetDefaultLoggerFactory() { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - return GetProviderFromServices(options, services, mustCreateImmediately: false); - } - - internal static IDataProtectionProvider GetProviderFromServices(DataProtectionOptions options, IServiceProvider services, bool mustCreateImmediately) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - IDataProtectionProvider dataProtectionProvider = null; - - // If we're being asked to create the provider immediately, then it means that - // we're already in a call to GetService, and we're responsible for supplying - // the default implementation ourselves. We can't call GetService again or - // else we risk stack diving. - if (!mustCreateImmediately) - { - dataProtectionProvider = services.GetService(); - } - - // If all else fails, create a keyring manually based on the other registered services. - if (dataProtectionProvider == null) - { - var keyRingProvider = new KeyRingProvider( - keyManager: services.GetRequiredService(), - keyManagementOptions: services.GetService>()?.Value, // might be null - services: services); - dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyRingProvider, services); - } - - // Finally, link the provider to the supplied discriminator - if (!String.IsNullOrEmpty(options.ApplicationDiscriminator)) - { - dataProtectionProvider = dataProtectionProvider.CreateProtector(options.ApplicationDiscriminator); - } - - return dataProtectionProvider; + return NullLoggerFactory.Instance; } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceCollectionExtensions.cs index 36b4eabe..95e8b2cc 100644 --- a/src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceCollectionExtensions.cs @@ -2,9 +2,15 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Cryptography.Cng; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection.Internal; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; +using Microsoft.AspNetCore.DataProtection.XmlEncryption; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { @@ -26,7 +32,7 @@ public static IDataProtectionBuilder AddDataProtection(this IServiceCollection s services.AddSingleton(); services.AddOptions(); - services.TryAdd(DataProtectionServices.GetDefaultServices()); + AddDataProtectionServices(services); return new DataProtectionBuilder(services); } @@ -53,5 +59,48 @@ public static IDataProtectionBuilder AddDataProtection(this IServiceCollection s services.Configure(setupAction); return builder; } + + private static void AddDataProtectionServices(IServiceCollection services) + { + services.TryAddSingleton(DataProtectionProviderFactory.GetDefaultLoggerFactory()); + + if (OSVersionUtil.IsWindows()) + { + services.AddSingleton(); + } + + services.TryAddEnumerable( + ServiceDescriptor.Singleton, KeyManagementOptionsSetup>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, DataProtectionOptionsSetup>()); + + services.AddSingleton(); + + // Internal services + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(s => + { + var dpOptions = s.GetRequiredService>(); + var keyRingProvider = s.GetRequiredService(); + var loggerFactory = s.GetRequiredService(); + + IDataProtectionProvider dataProtectionProvider = null; + dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyRingProvider, loggerFactory); + + // Link the provider to the supplied discriminator + if (!string.IsNullOrEmpty(dpOptions.Value.ApplicationDiscriminator)) + { + dataProtectionProvider = dataProtectionProvider.CreateProtector(dpOptions.Value.ApplicationDiscriminator); + } + + return dataProtectionProvider; + }); + +#if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml + services.AddSingleton(); +#endif + } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceDescriptors.cs b/src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceDescriptors.cs deleted file mode 100644 index 388454fc..00000000 --- a/src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceDescriptors.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using Microsoft.AspNetCore.Cryptography; -using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; -using Microsoft.AspNetCore.DataProtection.KeyManagement; -using Microsoft.AspNetCore.DataProtection.Repositories; -using Microsoft.AspNetCore.DataProtection.XmlEncryption; -using Microsoft.Extensions.Options; -using Microsoft.Win32; - -#if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml -using System.Security.Cryptography.X509Certificates; -#endif - -namespace Microsoft.Extensions.DependencyInjection -{ - /// - /// Default instances for the Data Protection system. - /// - internal static class DataProtectionServiceDescriptors - { - /// - /// An where the key lifetime is specified explicitly. - /// - - public static ServiceDescriptor ConfigureOptions_DefaultKeyLifetime(int numDays) - { - return ServiceDescriptor.Transient>(services => - { - return new ConfigureOptions(options => - { - options.NewKeyLifetime = TimeSpan.FromDays(numDays); - }); - }); - } - - /// - /// An backed by an . - /// - public static ServiceDescriptor IAuthenticatedEncryptorConfiguration_FromSettings(IInternalAuthenticatedEncryptionSettings options) - { - return ServiceDescriptor.Singleton(options.ToConfiguration); - } - -#if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml - /// - /// An backed by the default implementation. - /// - public static ServiceDescriptor ICertificateResolver_Default() - { - return ServiceDescriptor.Singleton(); - } -#endif - - /// - /// An ephemeral . - /// - public static ServiceDescriptor IDataProtectionProvider_Ephemeral() - { - return ServiceDescriptor.Singleton(services => new EphemeralDataProtectionProvider(services)); - } - - /// - /// An backed by a given implementation type. - /// - /// - /// The implementation type name is provided as a string so that we can provide activation services. - /// - public static ServiceDescriptor IKeyEscrowSink_FromTypeName(string implementationTypeName) - { - return ServiceDescriptor.Singleton(services => services.GetActivator().CreateInstance(implementationTypeName)); - } - -#if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml - - /// - /// An backed by an X.509 certificate. - /// - public static ServiceDescriptor IXmlEncryptor_Certificate(X509Certificate2 certificate) - { - return ServiceDescriptor.Singleton(services => new CertificateXmlEncryptor(certificate, services)); - } - - /// - /// An backed by an X.509 certificate. - /// - public static ServiceDescriptor IXmlEncryptor_Certificate(string thumbprint) - { - return ServiceDescriptor.Singleton(services => new CertificateXmlEncryptor( - thumbprint: thumbprint, - certificateResolver: services.GetRequiredService(), - services: services)); - } - -#endif - - /// - /// An backed by DPAPI. - /// - public static ServiceDescriptor IXmlEncryptor_Dpapi(bool protectToMachine) - { - CryptoUtil.AssertPlatformIsWindows(); - return ServiceDescriptor.Singleton(services => new DpapiXmlEncryptor(protectToMachine, services)); - } - - /// - /// An backed by DPAPI-NG. - /// - public static ServiceDescriptor IXmlEncryptor_DpapiNG(string protectionDescriptorRule, DpapiNGProtectionDescriptorFlags flags) - { - CryptoUtil.AssertPlatformIsWindows8OrLater(); - return ServiceDescriptor.Singleton(services => new DpapiNGXmlEncryptor(protectionDescriptorRule, flags, services)); - } - - /// - /// An backed by a file system. - /// - public static ServiceDescriptor IXmlRepository_FileSystem(DirectoryInfo directory) - { - return ServiceDescriptor.Singleton(services => new FileSystemXmlRepository(directory, services)); - } - - /// - /// An backed by the Windows registry. - /// - public static ServiceDescriptor IXmlRepository_Registry(RegistryKey registryKey) - { - return ServiceDescriptor.Singleton(services => new RegistryXmlRepository(registryKey, services)); - } - } -} diff --git a/src/Microsoft.AspNetCore.DataProtection/DataProtectionServices.cs b/src/Microsoft.AspNetCore.DataProtection/DataProtectionServices.cs deleted file mode 100644 index 424f4bad..00000000 --- a/src/Microsoft.AspNetCore.DataProtection/DataProtectionServices.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Cryptography.Cng; -using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; -using Microsoft.AspNetCore.DataProtection.Cng; -using Microsoft.AspNetCore.DataProtection.KeyManagement; -using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; -using Microsoft.AspNetCore.DataProtection.Repositories; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Win32; - -namespace Microsoft.Extensions.DependencyInjection -{ - /// - /// Provides access to default Data Protection instances. - /// - public static class DataProtectionServices - { - /// - /// Returns a collection of default instances that can be - /// used to bootstrap the Data Protection system. - /// - public static IEnumerable GetDefaultServices() - { - // The default key services are a strange beast. We don't want to return - // IXmlEncryptor and IXmlRepository as-is because they almost always have to be - // set as a matched pair. Instead, our built-in key manager will use a meta-service - // which represents the default pairing (logic based on hosting environment as - // demonstrated below), and if the developer explicitly specifies one or the other - // we'll not use the fallback at all. - yield return ServiceDescriptor.Singleton(services => - { - var log = services.GetLogger(typeof(DataProtectionServices)); - - ServiceDescriptor keyEncryptorDescriptor = null; - ServiceDescriptor keyRepositoryDescriptor = null; - - // If we're running in Azure Web Sites, the key repository goes in the %HOME% directory. - var azureWebSitesKeysFolder = FileSystemXmlRepository.GetKeyStorageDirectoryForAzureWebSites(); - if (azureWebSitesKeysFolder != null) - { - log?.UsingAzureAsKeyRepository(azureWebSitesKeysFolder.FullName); - - // Cloud DPAPI isn't yet available, so we don't encrypt keys at rest. - // This isn't all that different than what Azure Web Sites does today, and we can always add this later. - keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_FileSystem(azureWebSitesKeysFolder); - } - else - { - // If the user profile is available, store keys in the user profile directory. - var localAppDataKeysFolder = FileSystemXmlRepository.DefaultKeyStorageDirectory; - if (localAppDataKeysFolder != null) - { - if (OSVersionUtil.IsWindows()) - { - // If the user profile is available, we can protect using DPAPI. - // Probe to see if protecting to local user is available, and use it as the default if so. - keyEncryptorDescriptor = DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToMachine: !DpapiSecretSerializerHelper.CanProtectToCurrentUserAccount()); - } - keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_FileSystem(localAppDataKeysFolder); - - if (keyEncryptorDescriptor != null) - { - log?.UsingProfileAsKeyRepositoryWithDPAPI(localAppDataKeysFolder.FullName); - } - else - { - log?.UsingProfileAsKeyRepository(localAppDataKeysFolder.FullName); - } - } - else - { - // Use profile isn't available - can we use the HKLM registry? - RegistryKey regKeyStorageKey = null; - if (OSVersionUtil.IsWindows()) - { - regKeyStorageKey = RegistryXmlRepository.DefaultRegistryKey; - } - if (regKeyStorageKey != null) - { - // If the user profile isn't available, we can protect using DPAPI (to machine). - keyEncryptorDescriptor = DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToMachine: true); - keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_Registry(regKeyStorageKey); - - log?.UsingRegistryAsKeyRepositoryWithDPAPI(regKeyStorageKey.Name); - } - else - { - // Final fallback - use an ephemeral repository since we don't know where else to go. - // This can only be used for development scenarios. - keyRepositoryDescriptor = ServiceDescriptor.Singleton( - s => new EphemeralXmlRepository(s)); - - log?.UsingEphemeralKeyRepository(); - } - } - } - - return new DefaultKeyServices( - services: services, - keyEncryptorDescriptor: keyEncryptorDescriptor, - keyRepositoryDescriptor: keyRepositoryDescriptor); - }); - - // Provide root key management and data protection services - yield return ServiceDescriptor.Singleton(services => new XmlKeyManager(services)); - - yield return ServiceDescriptor.Singleton( - services => DataProtectionProviderFactory.GetProviderFromServices( - options: services.GetRequiredService>().Value, - services: services, - mustCreateImmediately: true /* this is the ultimate fallback */)); - - // Provide services required for XML encryption -#if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml - yield return DataProtectionServiceDescriptors.ICertificateResolver_Default(); -#endif - - // Hook up the logic which allows populating default options - yield return ServiceDescriptor.Transient>(services => - { - return new ConfigureOptions(options => - { - options.ApplicationDiscriminator = services.GetApplicationUniqueIdentifier(); - }); - }); - - // Read and apply policy from the registry, overriding any other defaults. - var encryptorConfigurationReadFromRegistry = false; - if (OSVersionUtil.IsWindows()) - { - foreach (var descriptor in RegistryPolicyResolver.ResolveDefaultPolicy()) - { - yield return descriptor; - if (descriptor.ServiceType == typeof(IAuthenticatedEncryptorConfiguration)) - { - encryptorConfigurationReadFromRegistry = true; - } - } - } - - // Finally, provide a fallback encryptor configuration if one wasn't already specified. - if (!encryptorConfigurationReadFromRegistry) - { - yield return DataProtectionServiceDescriptors.IAuthenticatedEncryptorConfiguration_FromSettings( - new AuthenticatedEncryptionSettings());; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.DataProtection/EphemeralDataProtectionProvider.cs b/src/Microsoft.AspNetCore.DataProtection/EphemeralDataProtectionProvider.cs index 6b30fd13..93cb0215 100644 --- a/src/Microsoft.AspNetCore.DataProtection/EphemeralDataProtectionProvider.cs +++ b/src/Microsoft.AspNetCore.DataProtection/EphemeralDataProtectionProvider.cs @@ -4,6 +4,7 @@ using System; using Microsoft.AspNetCore.Cryptography.Cng; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.Extensions.Logging; @@ -22,36 +23,28 @@ public sealed class EphemeralDataProtectionProvider : IDataProtectionProvider { private readonly KeyRingBasedDataProtectionProvider _dataProtectionProvider; - /// - /// Creates an ephemeral . - /// - public EphemeralDataProtectionProvider() - : this(services: null) - { - } - /// /// Creates an ephemeral , optionally providing /// services (such as logging) for consumption by the provider. /// - public EphemeralDataProtectionProvider(IServiceProvider services) + public EphemeralDataProtectionProvider(ILoggerFactory loggerFactory) { IKeyRingProvider keyringProvider; if (OSVersionUtil.IsWindows()) { // Fastest implementation: AES-256-GCM [CNG] - keyringProvider = new EphemeralKeyRing(); + keyringProvider = new EphemeralKeyRing(loggerFactory); } else { // Slowest implementation: AES-256-CBC + HMACSHA256 [Managed] - keyringProvider = new EphemeralKeyRing(); + keyringProvider = new EphemeralKeyRing(loggerFactory); } - var logger = services.GetLogger(); - logger?.UsingEphemeralDataProtectionProvider(); + var logger = loggerFactory.CreateLogger(); + logger.UsingEphemeralDataProtectionProvider(); - _dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyringProvider, services); + _dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyringProvider, loggerFactory); } public IDataProtector CreateProtector(string purpose) @@ -66,12 +59,17 @@ public IDataProtector CreateProtector(string purpose) } private sealed class EphemeralKeyRing : IKeyRing, IKeyRingProvider - where T : IInternalAuthenticatedEncryptionSettings, new() + where T : AlgorithmConfiguration, new() { + public EphemeralKeyRing(ILoggerFactory loggerFactory) + { + DefaultAuthenticatedEncryptor = GetDefaultEncryptor(loggerFactory); + } + // Currently hardcoded to a 512-bit KDK. private const int NUM_BYTES_IN_KDK = 512 / 8; - public IAuthenticatedEncryptor DefaultAuthenticatedEncryptor { get; } = new T().ToConfiguration(services: null).CreateNewDescriptor().CreateEncryptorInstance(); + public IAuthenticatedEncryptor DefaultAuthenticatedEncryptor { get; } public Guid DefaultKeyId { get; } = default(Guid); @@ -85,6 +83,29 @@ public IKeyRing GetCurrentKeyRing() { return this; } + + private static IAuthenticatedEncryptor GetDefaultEncryptor(ILoggerFactory loggerFactory) + { + var configuration = new T(); + if (configuration is CngGcmAuthenticatedEncryptorConfiguration) + { + var descriptor = (CngGcmAuthenticatedEncryptorDescriptor)new T().CreateNewDescriptor(); + return new CngGcmAuthenticatedEncryptorFactory(loggerFactory) + .CreateAuthenticatedEncryptorInstance( + descriptor.MasterKey, + configuration as CngGcmAuthenticatedEncryptorConfiguration); + } + else if (configuration is ManagedAuthenticatedEncryptorConfiguration) + { + var descriptor = (ManagedAuthenticatedEncryptorDescriptor)new T().CreateNewDescriptor(); + return new ManagedAuthenticatedEncryptorFactory(loggerFactory) + .CreateAuthenticatedEncryptorInstance( + descriptor.MasterKey, + configuration as ManagedAuthenticatedEncryptorConfiguration); + } + + return null; + } } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/Error.cs b/src/Microsoft.AspNetCore.DataProtection/Error.cs index 8bd8d21c..304f08e5 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Error.cs +++ b/src/Microsoft.AspNetCore.DataProtection/Error.cs @@ -39,13 +39,13 @@ public static CryptographicException CryptCommon_PayloadInvalid() public static InvalidOperationException Common_PropertyCannotBeNullOrEmpty(string propertyName) { - var message = String.Format(CultureInfo.CurrentCulture, Resources.Common_PropertyCannotBeNullOrEmpty, propertyName); + var message = string.Format(CultureInfo.CurrentCulture, Resources.Common_PropertyCannotBeNullOrEmpty, propertyName); return new InvalidOperationException(message); } public static InvalidOperationException Common_PropertyMustBeNonNegative(string propertyName) { - var message = String.Format(CultureInfo.CurrentCulture, Resources.Common_PropertyMustBeNonNegative, propertyName); + var message = string.Format(CultureInfo.CurrentCulture, Resources.Common_PropertyMustBeNonNegative, propertyName); return new InvalidOperationException(message); } @@ -56,13 +56,13 @@ public static CryptographicException Common_EncryptionFailed(Exception inner = n public static CryptographicException Common_KeyNotFound(Guid id) { - var message = String.Format(CultureInfo.CurrentCulture, Resources.Common_KeyNotFound, id); + var message = string.Format(CultureInfo.CurrentCulture, Resources.Common_KeyNotFound, id); return new CryptographicException(message); } public static CryptographicException Common_KeyRevoked(Guid id) { - var message = String.Format(CultureInfo.CurrentCulture, Resources.Common_KeyRevoked, id); + var message = string.Format(CultureInfo.CurrentCulture, Resources.Common_KeyRevoked, id); return new CryptographicException(message); } @@ -88,7 +88,7 @@ public static CryptographicException ProtectionProvider_BadVersion() public static InvalidOperationException XmlKeyManager_DuplicateKey(Guid keyId) { - var message = String.Format(CultureInfo.CurrentCulture, Resources.XmlKeyManager_DuplicateKey, keyId); + var message = string.Format(CultureInfo.CurrentCulture, Resources.XmlKeyManager_DuplicateKey, keyId); return new InvalidOperationException(message); } } diff --git a/src/Microsoft.AspNetCore.DataProtection/IDataProtectionBuilder.cs b/src/Microsoft.AspNetCore.DataProtection/IDataProtectionBuilder.cs index 55348b75..54539f7b 100644 --- a/src/Microsoft.AspNetCore.DataProtection/IDataProtectionBuilder.cs +++ b/src/Microsoft.AspNetCore.DataProtection/IDataProtectionBuilder.cs @@ -1,9 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.Extensions.DependencyInjection; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using System.IO; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.DataProtection { @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.DataProtection /// /// Similarly, when a developer modifies the default protected payload cryptographic /// algorithms, it is intended that he also select an explitiy key storage location. - /// A call to + /// A call to /// should therefore generally be paired with a call to , /// for example. /// @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.DataProtection /// /// Similarly, when a developer modifies the default protected payload cryptographic /// algorithms, it is intended that he also select an explitiy key storage location. - /// A call to + /// A call to /// should therefore generally be paired with a call to , /// for example. /// diff --git a/src/Microsoft.AspNetCore.DataProtection/Internal/DataProtectionBuilder.cs b/src/Microsoft.AspNetCore.DataProtection/Internal/DataProtectionBuilder.cs index 3ab488b8..bc8908c9 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Internal/DataProtectionBuilder.cs +++ b/src/Microsoft.AspNetCore.DataProtection/Internal/DataProtectionBuilder.cs @@ -2,18 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.ComponentModel; -using System.IO; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; -using Microsoft.AspNetCore.DataProtection.KeyManagement; -using Microsoft.AspNetCore.DataProtection.XmlEncryption; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Win32; - -#if !DOTNET5_4 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml -using System.Security.Cryptography.X509Certificates; -#endif namespace Microsoft.AspNetCore.DataProtection.Internal { diff --git a/src/Microsoft.AspNetCore.DataProtection/Internal/DataProtectionOptionsSetup.cs b/src/Microsoft.AspNetCore.DataProtection/Internal/DataProtectionOptionsSetup.cs new file mode 100644 index 00000000..d5e25b75 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection/Internal/DataProtectionOptionsSetup.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.DataProtection.Internal +{ + internal class DataProtectionOptionsSetup : IConfigureOptions + { + private readonly IServiceProvider _services; + + public DataProtectionOptionsSetup(IServiceProvider provider) + { + _services = provider; + } + + public void Configure(DataProtectionOptions options) + { + options.ApplicationDiscriminator = _services.GetApplicationUniqueIdentifier(); + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection/Internal/KeyManagementOptionsSetup.cs b/src/Microsoft.AspNetCore.DataProtection/Internal/KeyManagementOptionsSetup.cs new file mode 100644 index 00000000..1f72510e --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection/Internal/KeyManagementOptionsSetup.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.DataProtection.Internal +{ + internal class KeyManagementOptionsSetup : IConfigureOptions + { + private readonly RegistryPolicyResolver _registryPolicyResolver; + private readonly ILoggerFactory _loggerFactory; + + public KeyManagementOptionsSetup(ILoggerFactory loggerFactory) : this(loggerFactory, registryPolicyResolver: null) + { + } + + public KeyManagementOptionsSetup(ILoggerFactory loggerFactory, RegistryPolicyResolver registryPolicyResolver) + { + _loggerFactory = loggerFactory; + _registryPolicyResolver = registryPolicyResolver; + } + + public void Configure(KeyManagementOptions options) + { + RegistryPolicy context = null; + if (_registryPolicyResolver != null) + { + context = _registryPolicyResolver.ResolvePolicy(); + } + + if (context != null) + { + if (context.DefaultKeyLifetime.HasValue) + { + options.NewKeyLifetime = TimeSpan.FromDays(context.DefaultKeyLifetime.Value); + } + + options.AuthenticatedEncryptorConfiguration = context.EncryptorConfiguration; + + var escrowSinks = context.KeyEscrowSinks; + if (escrowSinks != null) + { + foreach (var escrowSink in escrowSinks) + { + options.KeyEscrowSinks.Add(escrowSink); + } + } + } + + if (options.AuthenticatedEncryptorConfiguration == null) + { + options.AuthenticatedEncryptorConfiguration = new AuthenticatedEncryptorConfiguration(); + } + + options.AuthenticatedEncryptorFactories.Add(new CngGcmAuthenticatedEncryptorFactory(_loggerFactory)); + options.AuthenticatedEncryptorFactories.Add(new CngCbcAuthenticatedEncryptorFactory(_loggerFactory)); + options.AuthenticatedEncryptorFactories.Add(new ManagedAuthenticatedEncryptorFactory(_loggerFactory)); + options.AuthenticatedEncryptorFactories.Add(new AuthenticatedEncryptorFactory(_loggerFactory)); + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/DefaultKeyResolver.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/DefaultKeyResolver.cs index 6f2af240..c2efbf14 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/DefaultKeyResolver.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/DefaultKeyResolver.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.DataProtection.KeyManagement { @@ -28,6 +29,8 @@ internal sealed class DefaultKeyResolver : IDefaultKeyResolver private readonly ILogger _logger; + private readonly IEnumerable _encryptorFactories; + /// /// The maximum skew that is allowed between servers. /// This is used to allow newly-created keys to be used across servers even though @@ -39,23 +42,38 @@ internal sealed class DefaultKeyResolver : IDefaultKeyResolver /// private readonly TimeSpan _maxServerToServerClockSkew; - public DefaultKeyResolver(TimeSpan keyPropagationWindow, TimeSpan maxServerToServerClockSkew, IServiceProvider services) + public DefaultKeyResolver(IOptions keyManagementOptions, ILoggerFactory loggerFactory) { - _keyPropagationWindow = keyPropagationWindow; - _maxServerToServerClockSkew = maxServerToServerClockSkew; - _logger = services.GetLogger(); + _keyPropagationWindow = keyManagementOptions.Value.KeyPropagationWindow; + _maxServerToServerClockSkew = keyManagementOptions.Value.MaxServerClockSkew; + _encryptorFactories = keyManagementOptions.Value.AuthenticatedEncryptorFactories; + _logger = loggerFactory.CreateLogger(); } private bool CanCreateAuthenticatedEncryptor(IKey key) { try { - var encryptorInstance = key.CreateEncryptorInstance() ?? CryptoUtil.Fail("CreateEncryptorInstance returned null."); + IAuthenticatedEncryptor encryptorInstance = null; + foreach (var factory in _encryptorFactories) + { + encryptorInstance = factory.CreateEncryptorInstance(key); + if (encryptorInstance != null) + { + break; + } + } + + if (encryptorInstance == null) + { + CryptoUtil.Fail("CreateEncryptorInstance returned null."); + } + return true; } catch (Exception ex) { - _logger?.KeyIsIneligibleToBeTheDefaultKeyBecauseItsMethodFailed(key.KeyId, nameof(IKey.CreateEncryptorInstance), ex); + _logger.KeyIsIneligibleToBeTheDefaultKeyBecauseItsMethodFailed(key.KeyId, nameof(IAuthenticatedEncryptorFactory.CreateEncryptorInstance), ex); return false; } } @@ -70,12 +88,12 @@ private IKey FindDefaultKey(DateTimeOffset now, IEnumerable allKeys, out I if (preferredDefaultKey != null) { - _logger?.ConsideringKeyWithExpirationDateAsDefaultKey(preferredDefaultKey.KeyId, preferredDefaultKey.ExpirationDate); + _logger.ConsideringKeyWithExpirationDateAsDefaultKey(preferredDefaultKey.KeyId, preferredDefaultKey.ExpirationDate); // if the key has been revoked or is expired, it is no longer a candidate if (preferredDefaultKey.IsRevoked || preferredDefaultKey.IsExpired(now) || !CanCreateAuthenticatedEncryptor(preferredDefaultKey)) { - _logger?.KeyIsNoLongerUnderConsiderationAsDefault(preferredDefaultKey.KeyId); + _logger.KeyIsNoLongerUnderConsiderationAsDefault(preferredDefaultKey.KeyId); preferredDefaultKey = null; } } @@ -98,7 +116,7 @@ private IKey FindDefaultKey(DateTimeOffset now, IEnumerable allKeys, out I if (callerShouldGenerateNewKey) { - _logger?.DefaultKeyExpirationImminentAndRepository(); + _logger.DefaultKeyExpirationImminentAndRepository(); } fallbackKey = null; @@ -119,7 +137,7 @@ orderby key.CreationDate ascending where !key.IsRevoked && CanCreateAuthenticatedEncryptor(key) select key).FirstOrDefault(); - _logger?.RepositoryContainsNoViableDefaultKey(); + _logger.RepositoryContainsNoViableDefaultKey(); callerShouldGenerateNewKey = true; return null; diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/DeferredKey.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/DeferredKey.cs index 36b7bb0d..9afea8a9 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/DeferredKey.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/DeferredKey.cs @@ -3,7 +3,6 @@ using System; using System.Xml.Linq; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.DataProtection.XmlEncryption; @@ -23,11 +22,11 @@ public DeferredKey( DateTimeOffset expirationDate, IInternalXmlKeyManager keyManager, XElement keyElement) - : base(keyId, creationDate, activationDate, expirationDate, new Lazy(GetLazyEncryptorDelegate(keyManager, keyElement))) + : base(keyId, creationDate, activationDate, expirationDate, new Lazy(GetLazyDescriptorDelegate(keyManager, keyElement))) { } - private static Func GetLazyEncryptorDelegate(IInternalXmlKeyManager keyManager, XElement keyElement) + private static Func GetLazyDescriptorDelegate(IInternalXmlKeyManager keyManager, XElement keyElement) { // The element will be held around in memory for a potentially lengthy period // of time. Since it might contain sensitive information, we should protect it. @@ -35,7 +34,7 @@ private static Func GetLazyEncryptorDelegate(IInternalX try { - return () => keyManager.DeserializeDescriptorFromKeyElement(encryptedKeyElement.ToXElement()).CreateEncryptorInstance(); + return () => keyManager.DeserializeDescriptorFromKeyElement(encryptedKeyElement.ToXElement()); } finally { diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/IKey.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/IKey.cs index f9ef009f..0ac31444 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/IKey.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/IKey.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; namespace Microsoft.AspNetCore.DataProtection.KeyManagement { @@ -45,10 +46,8 @@ public interface IKey Guid KeyId { get; } /// - /// Creates an IAuthenticatedEncryptor instance that can be used to encrypt data - /// to and decrypt data from this key. + /// Gets the instance associated with this key. /// - /// An IAuthenticatedEncryptor. - IAuthenticatedEncryptor CreateEncryptorInstance(); + IAuthenticatedEncryptorDescriptor Descriptor { get; } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Internal/CacheableKeyRing.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Internal/CacheableKeyRing.cs index cd522e74..36c9b00a 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Internal/CacheableKeyRing.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Internal/CacheableKeyRing.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; namespace Microsoft.AspNetCore.DataProtection.KeyManagement.Internal { @@ -14,8 +15,8 @@ public sealed class CacheableKeyRing { private readonly CancellationToken _expirationToken; - internal CacheableKeyRing(CancellationToken expirationToken, DateTimeOffset expirationTime, IKey defaultKey, IEnumerable allKeys) - : this(expirationToken, expirationTime, keyRing: new KeyRing(defaultKey, allKeys)) + internal CacheableKeyRing(CancellationToken expirationToken, DateTimeOffset expirationTime, IKey defaultKey, IEnumerable allKeys, IEnumerable encryptorFactories) + : this(expirationToken, expirationTime, keyRing: new KeyRing(defaultKey, allKeys, encryptorFactories)) { } diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Internal/DefaultKeyResolution.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Internal/DefaultKeyResolution.cs index f458092b..2d5b06d8 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Internal/DefaultKeyResolution.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Internal/DefaultKeyResolution.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; namespace Microsoft.AspNetCore.DataProtection.KeyManagement.Internal { @@ -11,7 +11,7 @@ public struct DefaultKeyResolution /// The default key, may be null if no key is a good default candidate. /// /// - /// If this property is non-null, its method will succeed + /// If this property is non-null, its method will succeed /// so is appropriate for use with deferred keys. /// public IKey DefaultKey; @@ -22,7 +22,7 @@ public struct DefaultKeyResolution /// be null if there is no viable fallback key. /// /// - /// If this property is non-null, its method will succeed + /// If this property is non-null, its method will succeed /// so is appropriate for use with deferred keys. /// public IKey FallbackKey; diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Key.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Key.cs index 5e5b9766..fd049a66 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Key.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/Key.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement internal sealed class Key : KeyBase { public Key(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate, IAuthenticatedEncryptorDescriptor descriptor) - : base(keyId, creationDate, activationDate, expirationDate, new Lazy(descriptor.CreateEncryptorInstance)) + : base(keyId, creationDate, activationDate, expirationDate, new Lazy(() => descriptor)) { } } diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyBase.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyBase.cs index 1afc6237..cd14b5e2 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyBase.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyBase.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; namespace Microsoft.AspNetCore.DataProtection.KeyManagement { @@ -11,15 +12,15 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement /// internal abstract class KeyBase : IKey { - private readonly Lazy _lazyEncryptor; + private readonly Lazy _lazyDescriptor; - public KeyBase(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate, Lazy lazyEncryptor) + public KeyBase(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate, Lazy lazyDescriptor) { KeyId = keyId; CreationDate = creationDate; ActivationDate = activationDate; ExpirationDate = expirationDate; - _lazyEncryptor = lazyEncryptor; + _lazyDescriptor = lazyDescriptor; } public DateTimeOffset ActivationDate { get; } @@ -32,9 +33,12 @@ public KeyBase(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activatio public Guid KeyId { get; } - public IAuthenticatedEncryptor CreateEncryptorInstance() + public IAuthenticatedEncryptorDescriptor Descriptor { - return _lazyEncryptor.Value; + get + { + return _lazyDescriptor.Value; + } } internal void SetRevoked() diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyManagementOptions.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyManagementOptions.cs index 65652ea0..0680239f 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyManagementOptions.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyManagementOptions.cs @@ -2,6 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Repositories; +using Microsoft.AspNetCore.DataProtection.XmlEncryption; namespace Microsoft.AspNetCore.DataProtection.KeyManagement { @@ -24,8 +29,21 @@ internal KeyManagementOptions(KeyManagementOptions other) { if (other != null) { - this.AutoGenerateKeys = other.AutoGenerateKeys; - this._newKeyLifetime = other._newKeyLifetime; + AutoGenerateKeys = other.AutoGenerateKeys; + _newKeyLifetime = other._newKeyLifetime; + XmlEncryptor = other.XmlEncryptor; + XmlRepository = other.XmlRepository; + AuthenticatedEncryptorConfiguration = other.AuthenticatedEncryptorConfiguration; + + foreach (var keyEscrowSink in other.KeyEscrowSinks) + { + KeyEscrowSinks.Add(keyEscrowSink); + } + + foreach (var encryptorFactory in other.AuthenticatedEncryptorFactories) + { + AuthenticatedEncryptorFactories.Add(encryptorFactory); + } } } @@ -119,5 +137,32 @@ public TimeSpan NewKeyLifetime _newKeyLifetime = value; } } + + /// + /// The instance that can be used to create + /// the instance. + /// + public AlgorithmConfiguration AuthenticatedEncryptorConfiguration { get; set; } + + /// + /// The list of to store the key material in. + /// + public IList KeyEscrowSinks { get; } = new List(); + + /// + /// The to use for storing and retrieving XML elements. + /// + public IXmlRepository XmlRepository { get; set; } + + /// + /// The to use for encrypting XML elements. + /// + public IXmlEncryptor XmlEncryptor { get; set; } + + /// + /// The list of that will be used for creating + /// s. + /// + public IList AuthenticatedEncryptorFactories { get; } = new List(); } } diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRing.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRing.cs index 2a180afd..b8392d54 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRing.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRing.cs @@ -17,12 +17,12 @@ internal sealed class KeyRing : IKeyRing private readonly KeyHolder _defaultKeyHolder; private readonly Dictionary _keyIdToKeyHolderMap; - public KeyRing(IKey defaultKey, IEnumerable allKeys) + public KeyRing(IKey defaultKey, IEnumerable allKeys, IEnumerable encryptorFactories) { _keyIdToKeyHolderMap = new Dictionary(); foreach (IKey key in allKeys) { - _keyIdToKeyHolderMap.Add(key.KeyId, new KeyHolder(key)); + _keyIdToKeyHolderMap.Add(key.KeyId, new KeyHolder(key, encryptorFactories)); } // It's possible under some circumstances that the default key won't be part of 'allKeys', @@ -30,7 +30,7 @@ public KeyRing(IKey defaultKey, IEnumerable allKeys) // wasn't in the underlying repository. In this case, we just add it now. if (!_keyIdToKeyHolderMap.ContainsKey(defaultKey.KeyId)) { - _keyIdToKeyHolderMap.Add(defaultKey.KeyId, new KeyHolder(defaultKey)); + _keyIdToKeyHolderMap.Add(defaultKey.KeyId, new KeyHolder(defaultKey, encryptorFactories)); } DefaultKeyId = defaultKey.KeyId; @@ -61,17 +61,19 @@ private sealed class KeyHolder { private readonly IKey _key; private IAuthenticatedEncryptor _encryptor; + private readonly IEnumerable _encryptorFactories; - internal KeyHolder(IKey key) + internal KeyHolder(IKey key, IEnumerable encryptorFactories) { _key = key; + _encryptorFactories = encryptorFactories; } internal IAuthenticatedEncryptor GetEncryptorInstance(out bool isRevoked) { // simple double-check lock pattern // we can't use LazyInitializer because we don't have a simple value factory - var encryptor = Volatile.Read(ref _encryptor); + IAuthenticatedEncryptor encryptor = Volatile.Read(ref _encryptor); if (encryptor == null) { lock (this) @@ -79,7 +81,14 @@ internal IAuthenticatedEncryptor GetEncryptorInstance(out bool isRevoked) encryptor = Volatile.Read(ref _encryptor); if (encryptor == null) { - encryptor = _key.CreateEncryptorInstance(); + foreach (var factory in _encryptorFactories) + { + encryptor = factory.CreateEncryptorInstance(_key); + if (encryptor != null) + { + break; + } + } Volatile.Write(ref _encryptor, encryptor); } } diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingBasedDataProtectionProvider.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingBasedDataProtectionProvider.cs index 7ed4124f..f7f785cc 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingBasedDataProtectionProvider.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingBasedDataProtectionProvider.cs @@ -12,10 +12,10 @@ internal unsafe sealed class KeyRingBasedDataProtectionProvider : IDataProtectio private readonly IKeyRingProvider _keyRingProvider; private readonly ILogger _logger; - public KeyRingBasedDataProtectionProvider(IKeyRingProvider keyRingProvider, IServiceProvider services) + public KeyRingBasedDataProtectionProvider(IKeyRingProvider keyRingProvider, ILoggerFactory loggerFactory) { _keyRingProvider = keyRingProvider; - _logger = services.GetLogger(); // note: for protector (not provider!) type, could be null + _logger = loggerFactory.CreateLogger(); // note: for protector (not provider!) type } public IDataProtector CreateProtector(string purpose) diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingBasedDataProtector.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingBasedDataProtector.cs index 12888f8b..d866ed6e 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingBasedDataProtector.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingBasedDataProtector.cs @@ -246,7 +246,7 @@ private byte[] UnprotectCore(byte[] protectedData, bool allowOperationsOnRevoked var requestedEncryptor = currentKeyRing.GetAuthenticatedEncryptorByKeyId(keyIdFromPayload, out keyWasRevoked); if (requestedEncryptor == null) { - _logger?.KeyWasNotFoundInTheKeyRingUnprotectOperationCannotProceed(keyIdFromPayload); + _logger.KeyWasNotFoundInTheKeyRingUnprotectOperationCannotProceed(keyIdFromPayload); throw Error.Common_KeyNotFound(keyIdFromPayload); } @@ -262,12 +262,12 @@ private byte[] UnprotectCore(byte[] protectedData, bool allowOperationsOnRevoked { if (allowOperationsOnRevokedKeys) { - _logger?.KeyWasRevokedCallerRequestedUnprotectOperationProceedRegardless(keyIdFromPayload); + _logger.KeyWasRevokedCallerRequestedUnprotectOperationProceedRegardless(keyIdFromPayload); status = UnprotectStatus.DecryptionKeyWasRevoked; } else { - _logger?.KeyWasRevokedUnprotectOperationCannotProceed(keyIdFromPayload); + _logger.KeyWasRevokedUnprotectOperationCannotProceed(keyIdFromPayload); throw Error.Common_KeyRevoked(keyIdFromPayload); } } diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingProvider.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingProvider.cs index 6dbca4d9..7e953cbd 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingProvider.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/KeyRingProvider.cs @@ -6,9 +6,10 @@ using System.Diagnostics; using System.Threading; using Microsoft.AspNetCore.Cryptography; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.DataProtection.KeyManagement { @@ -22,14 +23,32 @@ internal sealed class KeyRingProvider : ICacheableKeyRingProvider, IKeyRingProvi private readonly IKeyManager _keyManager; private readonly ILogger _logger; - public KeyRingProvider(IKeyManager keyManager, KeyManagementOptions keyManagementOptions, IServiceProvider services) + public KeyRingProvider( + IKeyManager keyManager, + IOptions keyManagementOptions, + IDefaultKeyResolver defaultKeyResolver, + ILoggerFactory loggerFactory) + : this( + keyManager, + keyManagementOptions, + cacheableKeyRingProvider: null, + defaultKeyResolver: defaultKeyResolver, + loggerFactory: loggerFactory) { - _keyManagementOptions = new KeyManagementOptions(keyManagementOptions); // clone so new instance is immutable + } + + public KeyRingProvider( + IKeyManager keyManager, + IOptions keyManagementOptions, + ICacheableKeyRingProvider cacheableKeyRingProvider, + IDefaultKeyResolver defaultKeyResolver, + ILoggerFactory loggerFactory) + { + _keyManagementOptions = new KeyManagementOptions(keyManagementOptions.Value); // clone so new instance is immutable _keyManager = keyManager; - _cacheableKeyRingProvider = services?.GetService() ?? this; - _logger = services?.GetLogger(); - _defaultKeyResolver = services?.GetService() - ?? new DefaultKeyResolver(_keyManagementOptions.KeyPropagationWindow, _keyManagementOptions.MaxServerClockSkew, services); + _cacheableKeyRingProvider = cacheableKeyRingProvider ?? this; + _defaultKeyResolver = defaultKeyResolver; + _logger = loggerFactory.CreateLogger(); } private CacheableKeyRing CreateCacheableKeyRingCore(DateTimeOffset now, IKey keyJustAdded) @@ -46,7 +65,7 @@ private CacheableKeyRing CreateCacheableKeyRingCore(DateTimeOffset now, IKey key return CreateCacheableKeyRingCoreStep2(now, cacheExpirationToken, defaultKeyPolicy.DefaultKey, allKeys); } - _logger?.PolicyResolutionStatesThatANewKeyShouldBeAddedToTheKeyRing(); + _logger.PolicyResolutionStatesThatANewKeyShouldBeAddedToTheKeyRing(); // We shouldn't call CreateKey more than once, else we risk stack diving. This code path shouldn't // get hit unless there was an ineligible key with an activation date slightly later than the one we @@ -67,12 +86,12 @@ private CacheableKeyRing CreateCacheableKeyRingCore(DateTimeOffset now, IKey key var keyToUse = defaultKeyPolicy.DefaultKey ?? defaultKeyPolicy.FallbackKey; if (keyToUse == null) { - _logger?.KeyRingDoesNotContainValidDefaultKey(); + _logger.KeyRingDoesNotContainValidDefaultKey(); throw new InvalidOperationException(Resources.KeyRingProvider_NoDefaultKey_AutoGenerateDisabled); } else { - _logger?.UsingFallbackKeyWithExpirationAsDefaultKey(keyToUse.KeyId, keyToUse.ExpirationDate); + _logger.UsingFallbackKeyWithExpirationAsDefaultKey(keyToUse.KeyId, keyToUse.ExpirationDate); return CreateCacheableKeyRingCoreStep2(now, cacheExpirationToken, keyToUse, allKeys); } } @@ -99,9 +118,9 @@ private CacheableKeyRing CreateCacheableKeyRingCoreStep2(DateTimeOffset now, Can Debug.Assert(defaultKey != null); // Invariant: our caller ensures that CreateEncryptorInstance succeeded at least once - Debug.Assert(defaultKey.CreateEncryptorInstance() != null); + Debug.Assert(CreateEncryptorForKey(defaultKey) != null); - _logger?.UsingKeyAsDefaultKey(defaultKey.KeyId); + _logger.UsingKeyAsDefaultKey(defaultKey.KeyId); var nextAutoRefreshTime = now + GetRefreshPeriodWithJitter(_keyManagementOptions.KeyRingRefreshPeriod); @@ -116,7 +135,8 @@ private CacheableKeyRing CreateCacheableKeyRingCoreStep2(DateTimeOffset now, Can expirationToken: cacheExpirationToken, expirationTime: (defaultKey.ExpirationDate <= now) ? nextAutoRefreshTime : Min(defaultKey.ExpirationDate, nextAutoRefreshTime), defaultKey: defaultKey, - allKeys: allKeys); + allKeys: allKeys, + encryptorFactories: _keyManagementOptions.AuthenticatedEncryptorFactories); } public IKeyRing GetCurrentKeyRing() @@ -156,7 +176,7 @@ internal IKeyRing GetCurrentKeyRingCore(DateTime utcNow) if (existingCacheableKeyRing != null) { - _logger?.ExistingCachedKeyRingIsExpired(); + _logger.ExistingCachedKeyRingIsExpired(); } // It's up to us to refresh the cached keyring. @@ -171,11 +191,11 @@ internal IKeyRing GetCurrentKeyRingCore(DateTime utcNow) { if (existingCacheableKeyRing != null) { - _logger?.ErrorOccurredWhileRefreshingKeyRing(ex); + _logger.ErrorOccurredWhileRefreshingKeyRing(ex); } else { - _logger?.ErrorOccurredWhileReadingKeyRing(ex); + _logger.ErrorOccurredWhileReadingKeyRing(ex); } // Failures that occur while refreshing the keyring are most likely transient, perhaps due to a @@ -216,6 +236,20 @@ internal IKeyRing GetCurrentKeyRingCore(DateTime utcNow) } } + private IAuthenticatedEncryptor CreateEncryptorForKey(IKey key) + { + foreach (var factory in _keyManagementOptions.AuthenticatedEncryptorFactories) + { + var encryptor = factory.CreateEncryptorInstance(key); + if (encryptor != null) + { + return encryptor; + } + } + + return null; + } + private static TimeSpan GetRefreshPeriodWithJitter(TimeSpan refreshPeriod) { // We'll fudge the refresh period up to -20% so that multiple applications don't try to diff --git a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/XmlKeyManager.cs b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/XmlKeyManager.cs index 64a84a51..b7b29114 100644 --- a/src/Microsoft.AspNetCore.DataProtection/KeyManagement/XmlKeyManager.cs +++ b/src/Microsoft.AspNetCore.DataProtection/KeyManagement/XmlKeyManager.cs @@ -4,21 +4,23 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Xml; using System.Xml.Linq; using Microsoft.AspNetCore.Cryptography; +using Microsoft.AspNetCore.Cryptography.Cng; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Cng; using Microsoft.AspNetCore.DataProtection.Internal; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.AspNetCore.DataProtection.XmlEncryption; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; - -using static System.FormattableString; +using Microsoft.Extensions.Options; +using Microsoft.Win32; namespace Microsoft.AspNetCore.DataProtection.KeyManagement { @@ -43,9 +45,10 @@ public sealed class XmlKeyManager : IKeyManager, IInternalXmlKeyManager private const string RevokeAllKeysValue = "*"; private readonly IActivator _activator; - private readonly IAuthenticatedEncryptorConfiguration _authenticatedEncryptorConfiguration; - private readonly IInternalXmlKeyManager _internalKeyManager; + private readonly AlgorithmConfiguration _authenticatedEncryptorConfiguration; private readonly IKeyEscrowSink _keyEscrowSink; + private readonly IInternalXmlKeyManager _internalKeyManager; + private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; private CancellationTokenSource _cacheExpirationTokenSource; @@ -53,59 +56,49 @@ public sealed class XmlKeyManager : IKeyManager, IInternalXmlKeyManager /// /// Creates an . /// - /// The repository where keys are stored. - /// Configuration for newly-created keys. - /// A provider of optional services. - public XmlKeyManager( - IXmlRepository repository, - IAuthenticatedEncryptorConfiguration configuration, - IServiceProvider services) + /// The instance that provides the configuration. + /// The . + /// The . + public XmlKeyManager(IOptions keyManagementOptions, IActivator activator, ILoggerFactory loggerFactory) { - if (repository == null) - { - throw new ArgumentNullException(nameof(repository)); - } + _loggerFactory = loggerFactory; + _logger = _loggerFactory.CreateLogger(); - if (configuration == null) + KeyRepository = keyManagementOptions.Value.XmlRepository; + KeyEncryptor = keyManagementOptions.Value.XmlEncryptor; + if (KeyRepository == null) { - throw new ArgumentNullException(nameof(configuration)); + if (KeyEncryptor != null) + { + throw new InvalidOperationException( + Resources.FormatXmlKeyManager_IXmlRepositoryNotFound(nameof(IXmlRepository), nameof(IXmlEncryptor))); + } + else + { + var keyRepositoryEncryptorPair = GetFallbackKeyRepositoryEncryptorPair(); + KeyRepository = keyRepositoryEncryptorPair.Key; + KeyEncryptor = keyRepositoryEncryptorPair.Value; + } } - KeyEncryptor = services.GetService(); // optional - KeyRepository = repository; + _authenticatedEncryptorConfiguration = keyManagementOptions.Value.AuthenticatedEncryptorConfiguration; - _activator = services.GetActivator(); // returns non-null - _authenticatedEncryptorConfiguration = configuration; - _internalKeyManager = services.GetService() ?? this; - _keyEscrowSink = services.GetKeyEscrowSink(); // not required - _logger = services.GetLogger(); // not required + var escrowSinks = keyManagementOptions.Value.KeyEscrowSinks; + _keyEscrowSink = escrowSinks.Count > 0 ? new AggregateKeyEscrowSink(escrowSinks) : null; + _activator = activator; TriggerAndResetCacheExpirationToken(suppressLogging: true); + _internalKeyManager = _internalKeyManager ?? this; } - internal XmlKeyManager(IServiceProvider services) + // Internal for testing. + internal XmlKeyManager( + IOptions keyManagementOptions, + IActivator activator, + ILoggerFactory loggerFactory, + IInternalXmlKeyManager internalXmlKeyManager) + : this(keyManagementOptions, activator, loggerFactory) { - // First, see if an explicit encryptor or repository was specified. - // If either was specified, then we won't use the fallback. - KeyEncryptor = services.GetService(); // optional - KeyRepository = (KeyEncryptor != null) - ? services.GetRequiredService() // required if encryptor is specified - : services.GetService(); // optional if encryptor not specified - - // If the repository is missing, then we get both the encryptor and the repository from the fallback. - // If the fallback is missing, the final call to GetRequiredService below will throw. - if (KeyRepository == null) - { - var defaultKeyServices = services.GetService(); - KeyEncryptor = defaultKeyServices?.GetKeyEncryptor(); // optional - KeyRepository = defaultKeyServices?.GetKeyRepository() ?? services.GetRequiredService(); - } - - _activator = services.GetActivator(); // returns non-null - _authenticatedEncryptorConfiguration = services.GetRequiredService(); - _internalKeyManager = services.GetService() ?? this; - _keyEscrowSink = services.GetKeyEscrowSink(); // not required - _logger = services.GetLogger(); // not required - TriggerAndResetCacheExpirationToken(suppressLogging: true); + _internalKeyManager = internalXmlKeyManager; } internal IXmlEncryptor KeyEncryptor { get; } @@ -177,7 +170,7 @@ public IReadOnlyCollection GetAllKeys() else { // Skip unknown elements. - _logger?.UnknownElementWithNameFoundInKeyringSkipping(element.Name); + _logger.UnknownElementWithNameFoundInKeyringSkipping(element.Name); } } @@ -191,11 +184,11 @@ public IReadOnlyCollection GetAllKeys() if (key != null) { key.SetRevoked(); - _logger?.MarkedKeyAsRevokedInTheKeyring(revokedKeyId); + _logger.MarkedKeyAsRevokedInTheKeyring(revokedKeyId); } else { - _logger?.TriedToProcessRevocationOfKeyButNoSuchKeyWasFound(revokedKeyId); + _logger.TriedToProcessRevocationOfKeyButNoSuchKeyWasFound(revokedKeyId); } } } @@ -213,7 +206,7 @@ public IReadOnlyCollection GetAllKeys() if (key.CreationDate < mostRecentMassRevocationDate) { key.SetRevoked(); - _logger?.MarkedKeyAsRevokedInTheKeyring(key.KeyId); + _logger.MarkedKeyAsRevokedInTheKeyring(key.KeyId); } } } @@ -239,14 +232,14 @@ private KeyBase ProcessKeyElement(XElement keyElement) DateTimeOffset activationDate = (DateTimeOffset)keyElement.Element(ActivationDateElementName); DateTimeOffset expirationDate = (DateTimeOffset)keyElement.Element(ExpirationDateElementName); - _logger?.FoundKey(keyId); + _logger.FoundKey(keyId); return new DeferredKey( keyId: keyId, creationDate: creationDate, activationDate: activationDate, expirationDate: expirationDate, - keyManager: _internalKeyManager, + keyManager: this, keyElement: keyElement); } catch (Exception ex) @@ -270,14 +263,14 @@ private object ProcessRevocationElement(XElement revocationElement) { // this is a mass revocation of all keys as of the specified revocation date DateTimeOffset massRevocationDate = (DateTimeOffset)revocationElement.Element(RevocationDateElementName); - _logger?.FoundRevocationOfAllKeysCreatedPriorTo(massRevocationDate); + _logger.FoundRevocationOfAllKeysCreatedPriorTo(massRevocationDate); return massRevocationDate; } else { // only one key is being revoked var keyId = XmlConvert.ToGuid(keyIdAsString); - _logger?.FoundRevocationOfKey(keyId); + _logger.FoundRevocationOfKey(keyId); return keyId; } } @@ -285,7 +278,7 @@ private object ProcessRevocationElement(XElement revocationElement) { // Any exceptions that occur are fatal - we don't want to continue if we cannot process // revocation information. - _logger?.ExceptionWhileProcessingRevocationElement(revocationElement, ex); + _logger.ExceptionWhileProcessingRevocationElement(revocationElement, ex); throw; } } @@ -299,7 +292,7 @@ public void RevokeAllKeys(DateTimeOffset revocationDate, string reason = null) // ... // - _logger?.RevokingAllKeysAsOfForReason(revocationDate, reason); + _logger.RevokingAllKeysAsOfForReason(revocationDate, reason); var revocationElement = new XElement(RevocationElementName, new XAttribute(VersionAttributeName, 1), @@ -327,7 +320,7 @@ private void TriggerAndResetCacheExpirationToken([CallerMemberName] string opNam { if (!suppressLogging) { - _logger?.KeyCacheExpirationTokenTriggeredByOperation(opName); + _logger.KeyCacheExpirationTokenTriggeredByOperation(opName); } Interlocked.Exchange(ref _cacheExpirationTokenSource, new CancellationTokenSource())?.Cancel(); @@ -341,10 +334,10 @@ private void WriteKeyDeserializationErrorToLog(Exception error, XElement keyElem // include sensitive information in the exception message. // write sanitized element - _logger?.ExceptionWhileProcessingKeyElement(keyElement.WithoutChildNodes(), error); + _logger.ExceptionWhileProcessingKeyElement(keyElement.WithoutChildNodes(), error); // write full element - _logger?.AnExceptionOccurredWhileProcessingElementDebug(keyElement, error); + _logger.AnExceptionOccurredWhileProcessingElementDebug(keyElement, error); } @@ -359,13 +352,13 @@ IKey IInternalXmlKeyManager.CreateNewKey(Guid keyId, DateTimeOffset creationDate // // - _logger?.CreatingKey(keyId, creationDate, activationDate, expirationDate); + _logger.CreatingKey(keyId, creationDate, activationDate, expirationDate); var newDescriptor = _authenticatedEncryptorConfiguration.CreateNewDescriptor() ?? CryptoUtil.Fail("CreateNewDescriptor returned null."); var descriptorXmlInfo = newDescriptor.ExportToXml(); - _logger?.DescriptorDeserializerTypeForKeyIs(keyId, descriptorXmlInfo.DeserializerType.AssemblyQualifiedName); + _logger.DescriptorDeserializerTypeForKeyIs(keyId, descriptorXmlInfo.DeserializerType.AssemblyQualifiedName); // build the element var keyElement = new XElement(KeyElementName, @@ -381,23 +374,23 @@ IKey IInternalXmlKeyManager.CreateNewKey(Guid keyId, DateTimeOffset creationDate // If key escrow policy is in effect, write the *unencrypted* key now. if (_keyEscrowSink != null) { - _logger?.KeyEscrowSinkFoundWritingKeyToEscrow(keyId); + _logger.KeyEscrowSinkFoundWritingKeyToEscrow(keyId); } else { - _logger?.NoKeyEscrowSinkFoundNotWritingKeyToEscrow(keyId); + _logger.NoKeyEscrowSinkFoundNotWritingKeyToEscrow(keyId); } _keyEscrowSink?.Store(keyId, keyElement); // If an XML encryptor has been configured, protect secret key material now. if (KeyEncryptor == null) { - _logger?.NoXMLEncryptorConfiguredKeyMayBePersistedToStorageInUnencryptedForm(keyId); + _logger.NoXMLEncryptorConfiguredKeyMayBePersistedToStorageInUnencryptedForm(keyId); } var possiblyEncryptedKeyElement = KeyEncryptor?.EncryptIfNecessary(keyElement) ?? keyElement; // Persist it to the underlying repository and trigger the cancellation token. - var friendlyName = Invariant($"key-{keyId:D}"); + var friendlyName = string.Format(CultureInfo.InvariantCulture, "key-{0:D}", keyId); KeyRepository.StoreElement(possiblyEncryptedKeyElement, friendlyName); TriggerAndResetCacheExpirationToken(); @@ -440,7 +433,7 @@ void IInternalXmlKeyManager.RevokeSingleKey(Guid keyId, DateTimeOffset revocatio // ... // - _logger?.RevokingKeyForReason(keyId, revocationDate, reason); + _logger.RevokingKeyForReason(keyId, revocationDate, reason); var revocationElement = new XElement(RevocationElementName, new XAttribute(VersionAttributeName, 1), @@ -450,9 +443,97 @@ void IInternalXmlKeyManager.RevokeSingleKey(Guid keyId, DateTimeOffset revocatio new XElement(ReasonElementName, reason)); // Persist it to the underlying repository and trigger the cancellation token - var friendlyName = Invariant($"revocation-{keyId:D}"); + var friendlyName = string.Format(CultureInfo.InvariantCulture, "revocation-{0:D}", keyId); KeyRepository.StoreElement(revocationElement, friendlyName); TriggerAndResetCacheExpirationToken(); } + + internal KeyValuePair GetFallbackKeyRepositoryEncryptorPair() + { + IXmlRepository repository = null; + IXmlEncryptor encryptor = null; + + // If we're running in Azure Web Sites, the key repository goes in the %HOME% directory. + var azureWebSitesKeysFolder = FileSystemXmlRepository.GetKeyStorageDirectoryForAzureWebSites(); + if (azureWebSitesKeysFolder != null) + { + _logger.UsingAzureAsKeyRepository(azureWebSitesKeysFolder.FullName); + + // Cloud DPAPI isn't yet available, so we don't encrypt keys at rest. + // This isn't all that different than what Azure Web Sites does today, and we can always add this later. + repository = new FileSystemXmlRepository(azureWebSitesKeysFolder, _loggerFactory); + } + else + { + // If the user profile is available, store keys in the user profile directory. + var localAppDataKeysFolder = FileSystemXmlRepository.DefaultKeyStorageDirectory; + if (localAppDataKeysFolder != null) + { + if (OSVersionUtil.IsWindows()) + { + // If the user profile is available, we can protect using DPAPI. + // Probe to see if protecting to local user is available, and use it as the default if so. + encryptor = new DpapiXmlEncryptor( + protectToLocalMachine: !DpapiSecretSerializerHelper.CanProtectToCurrentUserAccount(), + loggerFactory: _loggerFactory); + } + repository = new FileSystemXmlRepository(localAppDataKeysFolder, _loggerFactory); + + if (encryptor != null) + { + _logger.UsingProfileAsKeyRepositoryWithDPAPI(localAppDataKeysFolder.FullName); + } + else + { + _logger.UsingProfileAsKeyRepository(localAppDataKeysFolder.FullName); + } + } + else + { + // Use profile isn't available - can we use the HKLM registry? + RegistryKey regKeyStorageKey = null; + if (OSVersionUtil.IsWindows()) + { + regKeyStorageKey = RegistryXmlRepository.DefaultRegistryKey; + } + if (regKeyStorageKey != null) + { + // If the user profile isn't available, we can protect using DPAPI (to machine). + encryptor = new DpapiXmlEncryptor(protectToLocalMachine: true, loggerFactory: _loggerFactory); + repository = new RegistryXmlRepository(regKeyStorageKey, _loggerFactory); + + _logger.UsingRegistryAsKeyRepositoryWithDPAPI(regKeyStorageKey.Name); + } + else + { + // Final fallback - use an ephemeral repository since we don't know where else to go. + // This can only be used for development scenarios. + repository = new EphemeralXmlRepository(_loggerFactory); + + _logger.UsingEphemeralKeyRepository(); + } + } + } + + return new KeyValuePair(repository, encryptor); + } + + private sealed class AggregateKeyEscrowSink : IKeyEscrowSink + { + private readonly IList _sinks; + + public AggregateKeyEscrowSink(IList sinks) + { + _sinks = sinks; + } + + public void Store(Guid keyId, XElement element) + { + foreach (var sink in _sinks) + { + sink.Store(keyId, element); + } + } + } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.DataProtection/Properties/Resources.Designer.cs index 287746ea..c2db503d 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.DataProtection/Properties/Resources.Designer.cs @@ -394,6 +394,22 @@ internal static string FormatLifetimeMustNotBeNegative(object p0) return string.Format(CultureInfo.CurrentCulture, GetString("LifetimeMustNotBeNegative"), p0); } + /// + /// The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set. + /// + internal static string XmlKeyManager_IXmlRepositoryNotFound + { + get { return GetString("XmlKeyManager_IXmlRepositoryNotFound"); } + } + + /// + /// The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set. + /// + internal static string FormatXmlKeyManager_IXmlRepositoryNotFound(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("XmlKeyManager_IXmlRepositoryNotFound"), p0, p1); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.DataProtection/RC1ForwardingActivator.cs b/src/Microsoft.AspNetCore.DataProtection/RC1ForwardingActivator.cs index 10c936b6..9d76aaac 100644 --- a/src/Microsoft.AspNetCore.DataProtection/RC1ForwardingActivator.cs +++ b/src/Microsoft.AspNetCore.DataProtection/RC1ForwardingActivator.cs @@ -12,13 +12,13 @@ internal class RC1ForwardingActivator: SimpleActivator private const string To = "Microsoft.AspNetCore.DataProtection"; private readonly ILogger _logger; - public RC1ForwardingActivator(IServiceProvider services) : this(services, null) + public RC1ForwardingActivator(IServiceProvider services) : this(services, DataProtectionProviderFactory.GetDefaultLoggerFactory()) { } public RC1ForwardingActivator(IServiceProvider services, ILoggerFactory loggerFactory) : base(services) { - _logger = loggerFactory?.CreateLogger(typeof(RC1ForwardingActivator)); + _logger = loggerFactory.CreateLogger(typeof(RC1ForwardingActivator)); } public override object CreateInstance(Type expectedBaseType, string implementationTypeName) @@ -29,7 +29,7 @@ public override object CreateInstance(Type expectedBaseType, string implementati var type = Type.GetType(forwardedImplementationTypeName, false); if (type != null) { - _logger?.LogDebug("Forwarded activator type request from {FromType} to {ToType}", + _logger.LogDebug("Forwarded activator type request from {FromType} to {ToType}", implementationTypeName, forwardedImplementationTypeName); diff --git a/src/Microsoft.AspNetCore.DataProtection/RegistryPolicy.cs b/src/Microsoft.AspNetCore.DataProtection/RegistryPolicy.cs new file mode 100644 index 00000000..5617ce78 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection/RegistryPolicy.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.KeyManagement; + +namespace Microsoft.AspNetCore.DataProtection +{ + internal class RegistryPolicy + { + public RegistryPolicy( + AlgorithmConfiguration configuration, + IEnumerable keyEscrowSinks, + int? defaultKeyLifetime) + { + EncryptorConfiguration = configuration; + KeyEscrowSinks = keyEscrowSinks; + DefaultKeyLifetime = defaultKeyLifetime; + } + + public AlgorithmConfiguration EncryptorConfiguration { get; } + + public IEnumerable KeyEscrowSinks { get; } + + public int? DefaultKeyLifetime { get; } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection/RegistryPolicyResolver.cs b/src/Microsoft.AspNetCore.DataProtection/RegistryPolicyResolver.cs index 31c44f66..da5b3357 100644 --- a/src/Microsoft.AspNetCore.DataProtection/RegistryPolicyResolver.cs +++ b/src/Microsoft.AspNetCore.DataProtection/RegistryPolicyResolver.cs @@ -7,9 +7,10 @@ using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Cryptography; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Internal; using Microsoft.AspNetCore.DataProtection.KeyManagement; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Win32; namespace Microsoft.AspNetCore.DataProtection @@ -19,11 +20,22 @@ namespace Microsoft.AspNetCore.DataProtection /// internal sealed class RegistryPolicyResolver { - private readonly RegistryKey _policyRegKey; + private readonly Func _getPolicyRegKey; + private readonly IActivator _activator; + private readonly ILoggerFactory _loggerFactory; - internal RegistryPolicyResolver(RegistryKey policyRegKey) + public RegistryPolicyResolver(IActivator activator, ILoggerFactory loggerFactory) { - _policyRegKey = policyRegKey; + _getPolicyRegKey = () => Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\DotNetPackages\Microsoft.AspNetCore.DataProtection"); + _activator = activator; + _loggerFactory = loggerFactory; + } + + internal RegistryPolicyResolver(RegistryKey policyRegKey, IActivator activator, ILoggerFactory loggerFactory) + { + _getPolicyRegKey = () => policyRegKey; + _activator = activator; + _loggerFactory = loggerFactory; } // populates an options object from values stored in the registry @@ -59,11 +71,11 @@ private static void PopulateOptions(object options, RegistryKey key) private static List ReadKeyEscrowSinks(RegistryKey key) { - List sinks = new List(); + var sinks = new List(); // The format of this key is "type1; type2; ...". // We call Type.GetType to perform an eager check that the type exists. - string sinksFromRegistry = (string)key.GetValue("KeyEscrowSinks"); + var sinksFromRegistry = (string)key.GetValue("KeyEscrowSinks"); if (sinksFromRegistry != null) { foreach (string sinkFromRegistry in sinksFromRegistry.Split(';')) @@ -81,69 +93,60 @@ private static List ReadKeyEscrowSinks(RegistryKey key) } /// - /// Returns an array of s from the default registry location. + /// Returns a from the default registry location. /// - public static ServiceDescriptor[] ResolveDefaultPolicy() + public static RegistryPolicy ResolveDefaultPolicy(IActivator activator, ILoggerFactory loggerFactory) { - var subKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\DotNetPackages\Microsoft.AspNetCore.DataProtection"); - if (subKey != null) - { - using (subKey) - { - return new RegistryPolicyResolver(subKey).ResolvePolicy(); - } - } - else - { - return new ServiceDescriptor[0]; - } + return new RegistryPolicyResolver(activator, loggerFactory).ResolvePolicy(); } - internal ServiceDescriptor[] ResolvePolicy() + internal RegistryPolicy ResolvePolicy() { - return ResolvePolicyCore().ToArray(); // fully evaluate enumeration while the reg key is open + using (var registryKey = _getPolicyRegKey()) + { + return ResolvePolicyCore(registryKey); // fully evaluate enumeration while the reg key is open + } } - private IEnumerable ResolvePolicyCore() + private RegistryPolicy ResolvePolicyCore(RegistryKey policyRegKey) { + if (policyRegKey == null) + { + return null; + } + // Read the encryption options type: CNG-CBC, CNG-GCM, Managed - IInternalAuthenticatedEncryptionSettings options = null; - string encryptionType = (string)_policyRegKey.GetValue("EncryptionType"); + AlgorithmConfiguration configuration = null; + + var encryptionType = (string)policyRegKey.GetValue("EncryptionType"); if (String.Equals(encryptionType, "CNG-CBC", StringComparison.OrdinalIgnoreCase)) { - options = new CngCbcAuthenticatedEncryptionSettings(); + configuration = new CngCbcAuthenticatedEncryptorConfiguration(); } else if (String.Equals(encryptionType, "CNG-GCM", StringComparison.OrdinalIgnoreCase)) { - options = new CngGcmAuthenticatedEncryptionSettings(); + configuration = new CngGcmAuthenticatedEncryptorConfiguration(); } else if (String.Equals(encryptionType, "Managed", StringComparison.OrdinalIgnoreCase)) { - options = new ManagedAuthenticatedEncryptionSettings(); + configuration = new ManagedAuthenticatedEncryptorConfiguration(); } else if (!String.IsNullOrEmpty(encryptionType)) { throw CryptoUtil.Fail("Unrecognized EncryptionType: " + encryptionType); } - if (options != null) + if (configuration != null) { - PopulateOptions(options, _policyRegKey); - yield return DataProtectionServiceDescriptors.IAuthenticatedEncryptorConfiguration_FromSettings(options); + PopulateOptions(configuration, policyRegKey); } // Read ancillary data - int? defaultKeyLifetime = (int?)_policyRegKey.GetValue("DefaultKeyLifetime"); - if (defaultKeyLifetime.HasValue) - { - yield return DataProtectionServiceDescriptors.ConfigureOptions_DefaultKeyLifetime(defaultKeyLifetime.Value); - } + var defaultKeyLifetime = (int?)policyRegKey.GetValue("DefaultKeyLifetime"); - var keyEscrowSinks = ReadKeyEscrowSinks(_policyRegKey); - foreach (var keyEscrowSink in keyEscrowSinks) - { - yield return DataProtectionServiceDescriptors.IKeyEscrowSink_FromTypeName(keyEscrowSink); - } + var keyEscrowSinks = ReadKeyEscrowSinks(policyRegKey).Select(item => _activator.CreateInstance(item)); + + return new RegistryPolicy(configuration, keyEscrowSinks, defaultKeyLifetime); } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/Repositories/EphemeralXmlRepository.cs b/src/Microsoft.AspNetCore.DataProtection/Repositories/EphemeralXmlRepository.cs index e5f0f937..17c7156b 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Repositories/EphemeralXmlRepository.cs +++ b/src/Microsoft.AspNetCore.DataProtection/Repositories/EphemeralXmlRepository.cs @@ -17,10 +17,10 @@ internal class EphemeralXmlRepository : IXmlRepository { private readonly List _storedElements = new List(); - public EphemeralXmlRepository(IServiceProvider services) + public EphemeralXmlRepository(ILoggerFactory loggerFactory) { - var logger = services?.GetLogger(); - logger?.UsingInmemoryRepository(); + var logger = loggerFactory.CreateLogger(); + logger.UsingInmemoryRepository(); } public virtual IReadOnlyCollection GetAllElements() diff --git a/src/Microsoft.AspNetCore.DataProtection/Repositories/FileSystemXmlRepository.cs b/src/Microsoft.AspNetCore.DataProtection/Repositories/FileSystemXmlRepository.cs index fc47f437..696e54bc 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Repositories/FileSystemXmlRepository.cs +++ b/src/Microsoft.AspNetCore.DataProtection/Repositories/FileSystemXmlRepository.cs @@ -24,21 +24,8 @@ public class FileSystemXmlRepository : IXmlRepository /// Creates a with keys stored at the given directory. /// /// The directory in which to persist key material. - public FileSystemXmlRepository(DirectoryInfo directory) - : this(directory, services: null) - { - if (directory == null) - { - throw new ArgumentNullException(nameof(directory)); - } - } - - /// - /// Creates a with keys stored at the given directory. - /// - /// The directory in which to persist key material. - /// An optional to provide ancillary services. - public FileSystemXmlRepository(DirectoryInfo directory, IServiceProvider services) + /// The . + public FileSystemXmlRepository(DirectoryInfo directory, ILoggerFactory loggerFactory) { if (directory == null) { @@ -46,8 +33,7 @@ public FileSystemXmlRepository(DirectoryInfo directory, IServiceProvider service } Directory = directory; - Services = services; - _logger = services?.GetLogger(); + _logger = loggerFactory.CreateLogger(); } /// @@ -65,11 +51,6 @@ public FileSystemXmlRepository(DirectoryInfo directory, IServiceProvider service /// public DirectoryInfo Directory { get; } - /// - /// The provided to the constructor. - /// - protected IServiceProvider Services { get; } - private const string DataProtectionKeysFolderName = "DataProtection-Keys"; private static DirectoryInfo GetKeyStorageDirectoryFromBaseAppDataPath(string basePath) @@ -185,7 +166,7 @@ private static bool IsSafeFilename(string filename) private XElement ReadElementFromFile(string fullPath) { - _logger?.ReadingDataFromFile(fullPath); + _logger.ReadingDataFromFile(fullPath); using (var fileStream = File.OpenRead(fullPath)) { @@ -203,7 +184,7 @@ public virtual void StoreElement(XElement element, string friendlyName) if (!IsSafeFilename(friendlyName)) { var newFriendlyName = Guid.NewGuid().ToString(); - _logger?.NameIsNotSafeFileName(friendlyName, newFriendlyName); + _logger.NameIsNotSafeFileName(friendlyName, newFriendlyName); friendlyName = newFriendlyName; } @@ -229,7 +210,7 @@ private void StoreElementCore(XElement element, string filename) // Once the file has been fully written, perform the rename. // Renames are atomic operations on the file systems we support. - _logger?.WritingDataToFile(finalFilename); + _logger.WritingDataToFile(finalFilename); File.Move(tempFilename, finalFilename); } finally diff --git a/src/Microsoft.AspNetCore.DataProtection/Repositories/RegistryXmlRepository.cs b/src/Microsoft.AspNetCore.DataProtection/Repositories/RegistryXmlRepository.cs index fa237c6f..7692d1cc 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Repositories/RegistryXmlRepository.cs +++ b/src/Microsoft.AspNetCore.DataProtection/Repositories/RegistryXmlRepository.cs @@ -3,14 +3,13 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Security.Principal; using System.Xml.Linq; using Microsoft.Extensions.Logging; using Microsoft.Win32; -using static System.FormattableString; - namespace Microsoft.AspNetCore.DataProtection.Repositories { /// @@ -26,21 +25,8 @@ public class RegistryXmlRepository : IXmlRepository /// Creates a with keys stored in the given registry key. /// /// The registry key in which to persist key material. - public RegistryXmlRepository(RegistryKey registryKey) - : this(registryKey, services: null) - { - if (registryKey == null) - { - throw new ArgumentNullException(nameof(registryKey)); - } - } - - /// - /// Creates a with keys stored in the given registry key. - /// - /// The registry key in which to persist key material. - /// The used to resolve services. - public RegistryXmlRepository(RegistryKey registryKey, IServiceProvider services) + /// The . + public RegistryXmlRepository(RegistryKey registryKey, ILoggerFactory loggerFactory) { if (registryKey == null) { @@ -48,8 +34,7 @@ public RegistryXmlRepository(RegistryKey registryKey, IServiceProvider services) } RegistryKey = registryKey; - Services = services; - _logger = services?.GetLogger(); + _logger = loggerFactory.CreateLogger(); } /// @@ -67,11 +52,6 @@ public RegistryXmlRepository(RegistryKey registryKey, IServiceProvider services) /// public RegistryKey RegistryKey { get; } - /// - /// The provided to the constructor. - /// - protected IServiceProvider Services { get; } - public virtual IReadOnlyCollection GetAllElements() { // forces complete enumeration @@ -107,7 +87,11 @@ private static RegistryKey GetDefaultHklmStorageKey() // Even though this is in HKLM, WAS ensures that applications hosted in IIS are properly isolated. // See APP_POOL::EnsureSharedMachineKeyStorage in WAS source for more info. // The version number will need to change if IIS hosts Core CLR directly. - var aspnetAutoGenKeysBaseKeyName = Invariant($@"SOFTWARE\Microsoft\ASP.NET\4.0.30319.0\AutoGenKeys\{WindowsIdentity.GetCurrent().User.Value}"); + var aspnetAutoGenKeysBaseKeyName = string.Format( + CultureInfo.InvariantCulture, + @"SOFTWARE\Microsoft\ASP.NET\4.0.30319.0\AutoGenKeys\{0}", + WindowsIdentity.GetCurrent().User.Value); + var aspnetBaseKey = hklmBaseKey.OpenSubKey(aspnetAutoGenKeysBaseKeyName, writable: true); if (aspnetBaseKey != null) { @@ -141,7 +125,7 @@ private static bool IsSafeRegistryValueName(string filename) private XElement ReadElementFromRegKey(RegistryKey regKey, string valueName) { - _logger?.ReadingDataFromRegistryKeyValue(regKey, valueName); + _logger.ReadingDataFromRegistryKeyValue(regKey, valueName); var data = regKey.GetValue(valueName) as string; return (!String.IsNullOrEmpty(data)) ? XElement.Parse(data) : null; @@ -157,7 +141,7 @@ public virtual void StoreElement(XElement element, string friendlyName) if (!IsSafeRegistryValueName(friendlyName)) { var newFriendlyName = Guid.NewGuid().ToString(); - _logger?.NameIsNotSafeRegistryValueName(friendlyName, newFriendlyName); + _logger.NameIsNotSafeRegistryValueName(friendlyName, newFriendlyName); friendlyName = newFriendlyName; } diff --git a/src/Microsoft.AspNetCore.DataProtection/Resources.resx b/src/Microsoft.AspNetCore.DataProtection/Resources.resx index e45b2274..292ec056 100644 --- a/src/Microsoft.AspNetCore.DataProtection/Resources.resx +++ b/src/Microsoft.AspNetCore.DataProtection/Resources.resx @@ -189,4 +189,7 @@ {0} must not be negative + + The '{0}' instance could not be found. When an '{1}' instance is set, a corresponding '{0}' instance must also be set. + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.DataProtection/StringInterpolation.cs b/src/Microsoft.AspNetCore.DataProtection/StringInterpolation.cs deleted file mode 100644 index cb6120c6..00000000 --- a/src/Microsoft.AspNetCore.DataProtection/StringInterpolation.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if !NETSTANDARD1_3 -// These classes allow using the C# string interpolation feature from .NET 4.5.1. -// They're slimmed-down versions of the classes that exist in .NET 4.6. - -using System.Globalization; - -namespace System -{ - internal struct FormattableString - { - private readonly object[] _arguments; - public readonly string Format; - - internal FormattableString(string format, params object[] arguments) - { - Format = format; - _arguments = arguments; - } - - public object[] GetArguments() => _arguments; - - public static string Invariant(FormattableString formattable) - { - return String.Format(CultureInfo.InvariantCulture, formattable.Format, formattable.GetArguments()); - } - } -} - -namespace System.Runtime.CompilerServices -{ - internal static class FormattableStringFactory - { - public static FormattableString Create(string format, params object[] arguments) - { - return new FormattableString(format, arguments); - } - } -} - -#endif diff --git a/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/CertificateXmlEncryptor.cs b/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/CertificateXmlEncryptor.cs index 4e7a538e..3bf68dcc 100644 --- a/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/CertificateXmlEncryptor.cs +++ b/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/CertificateXmlEncryptor.cs @@ -9,7 +9,6 @@ using System.Xml; using System.Xml.Linq; using Microsoft.AspNetCore.Cryptography; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.DataProtection.XmlEncryption @@ -23,29 +22,13 @@ public sealed class CertificateXmlEncryptor : IInternalCertificateXmlEncryptor, private readonly IInternalCertificateXmlEncryptor _encryptor; private readonly ILogger _logger; - /// - /// Creates a given a certificate's thumbprint and an - /// that can be used to resolve the certificate. - /// - /// The thumbprint (as a hex string) of the certificate with which to - /// encrypt the key material. The certificate must be locatable by . - /// A resolver which can locate objects. - public CertificateXmlEncryptor(string thumbprint, ICertificateResolver certificateResolver) - : this(thumbprint, certificateResolver, services: null) - { - } - /// /// Creates a given a certificate's thumbprint, an /// that can be used to resolve the certificate, and /// an . /// - /// The thumbprint (as a hex string) of the certificate with which to - /// encrypt the key material. The certificate must be locatable by . - /// A resolver which can locate objects. - /// An optional to provide ancillary services. - public CertificateXmlEncryptor(string thumbprint, ICertificateResolver certificateResolver, IServiceProvider services) - : this(services) + public CertificateXmlEncryptor(string thumbprint, ICertificateResolver certificateResolver, ILoggerFactory loggerFactory) + : this(loggerFactory, encryptor: null) { if (thumbprint == null) { @@ -60,23 +43,12 @@ public CertificateXmlEncryptor(string thumbprint, ICertificateResolver certifica _certFactory = CreateCertFactory(thumbprint, certificateResolver); } - /// - /// Creates a given an instance. - /// - /// The with which to encrypt the key material. - public CertificateXmlEncryptor(X509Certificate2 certificate) - : this(certificate, services: null) - { - } - /// /// Creates a given an instance /// and an . /// - /// The with which to encrypt the key material. - /// An optional to provide ancillary services. - public CertificateXmlEncryptor(X509Certificate2 certificate, IServiceProvider services) - : this(services) + public CertificateXmlEncryptor(X509Certificate2 certificate, ILoggerFactory loggerFactory) + : this(loggerFactory, encryptor: null) { if (certificate == null) { @@ -86,10 +58,10 @@ public CertificateXmlEncryptor(X509Certificate2 certificate, IServiceProvider se _certFactory = () => certificate; } - internal CertificateXmlEncryptor(IServiceProvider services) + internal CertificateXmlEncryptor(ILoggerFactory loggerFactory, IInternalCertificateXmlEncryptor encryptor) { - _encryptor = services?.GetService() ?? this; - _logger = services.GetLogger(); + _encryptor = encryptor ?? this; + _logger = loggerFactory.CreateLogger(); } /// @@ -149,7 +121,7 @@ private Func CreateCertFactory(string thumbprint, ICertificate } catch (Exception ex) { - _logger?.ExceptionWhileTryingToResolveCertificateWithThumbprint(thumbprint, ex); + _logger.ExceptionWhileTryingToResolveCertificateWithThumbprint(thumbprint, ex); throw; } @@ -161,7 +133,7 @@ EncryptedData IInternalCertificateXmlEncryptor.PerformEncryption(EncryptedXml en var cert = _certFactory() ?? CryptoUtil.Fail("Cert factory returned null."); - _logger?.EncryptingToX509CertificateWithThumbprint(cert.Thumbprint); + _logger.EncryptingToX509CertificateWithThumbprint(cert.Thumbprint); try { @@ -169,7 +141,7 @@ EncryptedData IInternalCertificateXmlEncryptor.PerformEncryption(EncryptedXml en } catch (Exception ex) { - _logger?.AnErrorOccurredWhileEncryptingToX509CertificateWithThumbprint(cert.Thumbprint, ex); + _logger.AnErrorOccurredWhileEncryptingToX509CertificateWithThumbprint(cert.Thumbprint, ex); throw; } } diff --git a/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/DpapiNGXmlEncryptor.cs b/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/DpapiNGXmlEncryptor.cs index 3ec4325e..f5162496 100644 --- a/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/DpapiNGXmlEncryptor.cs +++ b/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/DpapiNGXmlEncryptor.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Globalization; using System.Security.Principal; using System.Xml.Linq; using Microsoft.AspNetCore.Cryptography; @@ -9,8 +10,6 @@ using Microsoft.AspNetCore.DataProtection.Cng; using Microsoft.Extensions.Logging; -using static System.FormattableString; - namespace Microsoft.AspNetCore.DataProtection.XmlEncryption { /// @@ -29,18 +28,8 @@ public sealed class DpapiNGXmlEncryptor : IXmlEncryptor /// /// The rule string from which to create the protection descriptor. /// Flags controlling the creation of the protection descriptor. - public DpapiNGXmlEncryptor(string protectionDescriptorRule, DpapiNGProtectionDescriptorFlags flags) - : this(protectionDescriptorRule, flags, services: null) - { - } - - /// - /// Creates a new instance of a . - /// - /// The rule string from which to create the protection descriptor. - /// Flags controlling the creation of the protection descriptor. - /// An optional to provide ancillary services. - public DpapiNGXmlEncryptor(string protectionDescriptorRule, DpapiNGProtectionDescriptorFlags flags, IServiceProvider services) + /// The . + public DpapiNGXmlEncryptor(string protectionDescriptorRule, DpapiNGProtectionDescriptorFlags flags, ILoggerFactory loggerFactory) { if (protectionDescriptorRule == null) { @@ -53,7 +42,7 @@ public DpapiNGXmlEncryptor(string protectionDescriptorRule, DpapiNGProtectionDes UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus); CryptoUtil.AssertSafeHandleIsValid(_protectionDescriptorHandle); - _logger = services.GetLogger(); + _logger = loggerFactory.CreateLogger(); } /// @@ -73,7 +62,7 @@ public EncryptedXmlInfo Encrypt(XElement plaintextElement) } var protectionDescriptorRuleString = _protectionDescriptorHandle.GetProtectionDescriptorRuleString(); - _logger?.EncryptingToWindowsDPAPINGUsingProtectionDescriptorRule(protectionDescriptorRuleString); + _logger.EncryptingToWindowsDPAPINGUsingProtectionDescriptorRule(protectionDescriptorRuleString); // Convert the XML element to a binary secret so that it can be run through DPAPI byte[] cngDpapiEncryptedData; @@ -86,7 +75,7 @@ public EncryptedXmlInfo Encrypt(XElement plaintextElement) } catch (Exception ex) { - _logger?.ErrorOccurredWhileEncryptingToWindowsDPAPING(ex); + _logger.ErrorOccurredWhileEncryptingToWindowsDPAPING(ex); throw; } @@ -118,7 +107,7 @@ internal static string GetDefaultProtectionDescriptorString() using (var currentIdentity = WindowsIdentity.GetCurrent()) { // use the SID to create an SDDL string - return Invariant($"SID={currentIdentity.User.Value}"); + return string.Format(CultureInfo.InvariantCulture, "SID={0}", currentIdentity.User.Value); } } } diff --git a/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/DpapiXmlEncryptor.cs b/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/DpapiXmlEncryptor.cs index 5b216ec5..d7fa2d7b 100644 --- a/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/DpapiXmlEncryptor.cs +++ b/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/DpapiXmlEncryptor.cs @@ -21,28 +21,18 @@ public sealed class DpapiXmlEncryptor : IXmlEncryptor private readonly ILogger _logger; private readonly bool _protectToLocalMachine; - /// - /// Creates a given a protection scope. - /// - /// 'true' if the data should be decipherable by anybody on the local machine, - /// 'false' if the data should only be decipherable by the current Windows user account. - public DpapiXmlEncryptor(bool protectToLocalMachine) - : this(protectToLocalMachine, services: null) - { - } - /// /// Creates a given a protection scope and an . /// /// 'true' if the data should be decipherable by anybody on the local machine, /// 'false' if the data should only be decipherable by the current Windows user account. - /// An optional to provide ancillary services. - public DpapiXmlEncryptor(bool protectToLocalMachine, IServiceProvider services) + /// The . + public DpapiXmlEncryptor(bool protectToLocalMachine, ILoggerFactory loggerFactory) { CryptoUtil.AssertPlatformIsWindows(); _protectToLocalMachine = protectToLocalMachine; - _logger = services.GetLogger(); + _logger = loggerFactory.CreateLogger(); } /// @@ -62,11 +52,11 @@ public EncryptedXmlInfo Encrypt(XElement plaintextElement) } if (_protectToLocalMachine) { - _logger?.EncryptingToWindowsDPAPIForLocalMachineAccount(); + _logger.EncryptingToWindowsDPAPIForLocalMachineAccount(); } else { - _logger?.EncryptingToWindowsDPAPIForCurrentUserAccount(WindowsIdentity.GetCurrent().Name); + _logger.EncryptingToWindowsDPAPIForCurrentUserAccount(WindowsIdentity.GetCurrent().Name); } // Convert the XML element to a binary secret so that it can be run through DPAPI @@ -80,7 +70,7 @@ public EncryptedXmlInfo Encrypt(XElement plaintextElement) } catch (Exception ex) { - _logger?.ErrorOccurredWhileEncryptingToWindowsDPAPI(ex); + _logger.ErrorOccurredWhileEncryptingToWindowsDPAPI(ex); throw; } diff --git a/test/Microsoft.AspNetCore.Cryptography.Internal.Test/Microsoft.AspNetCore.Cryptography.Internal.Test.csproj b/test/Microsoft.AspNetCore.Cryptography.Internal.Test/Microsoft.AspNetCore.Cryptography.Internal.Test.csproj index 832db332..ec9c8b64 100644 --- a/test/Microsoft.AspNetCore.Cryptography.Internal.Test/Microsoft.AspNetCore.Cryptography.Internal.Test.csproj +++ b/test/Microsoft.AspNetCore.Cryptography.Internal.Test/Microsoft.AspNetCore.Cryptography.Internal.Test.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Test.csproj b/test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Test.csproj index 041fc58a..d2be0cff 100644 --- a/test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Test.csproj +++ b/test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Test/Microsoft.AspNetCore.Cryptography.KeyDerivation.Test.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/test/Microsoft.AspNetCore.DataProtection.Abstractions.Test/Microsoft.AspNetCore.DataProtection.Abstractions.Test.csproj b/test/Microsoft.AspNetCore.DataProtection.Abstractions.Test/Microsoft.AspNetCore.DataProtection.Abstractions.Test.csproj index 6ecaf250..263dddec 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Abstractions.Test/Microsoft.AspNetCore.DataProtection.Abstractions.Test.csproj +++ b/test/Microsoft.AspNetCore.DataProtection.Abstractions.Test/Microsoft.AspNetCore.DataProtection.Abstractions.Test.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/test/Microsoft.AspNetCore.DataProtection.AzureStorage.Test/Microsoft.AspNetCore.DataProtection.AzureStorage.Test.csproj b/test/Microsoft.AspNetCore.DataProtection.AzureStorage.Test/Microsoft.AspNetCore.DataProtection.AzureStorage.Test.csproj index ddb87f8e..1aeefb1e 100644 --- a/test/Microsoft.AspNetCore.DataProtection.AzureStorage.Test/Microsoft.AspNetCore.DataProtection.AzureStorage.Test.csproj +++ b/test/Microsoft.AspNetCore.DataProtection.AzureStorage.Test/Microsoft.AspNetCore.DataProtection.AzureStorage.Test.csproj @@ -20,4 +20,8 @@ + + + + diff --git a/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/Microsoft.AspNetCore.DataProtection.Extensions.Test.csproj b/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/Microsoft.AspNetCore.DataProtection.Extensions.Test.csproj index 5c62910c..36750335 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/Microsoft.AspNetCore.DataProtection.Extensions.Test.csproj +++ b/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/Microsoft.AspNetCore.DataProtection.Extensions.Test.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/TimeLimitedDataProtectorTests.cs b/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/TimeLimitedDataProtectorTests.cs index be9b19c2..45f81756 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/TimeLimitedDataProtectorTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/TimeLimitedDataProtectorTests.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Security.Cryptography; using Microsoft.AspNetCore.DataProtection.Extensions; +using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; @@ -152,7 +153,7 @@ public void Unprotect_UnprotectOperationFails_HomogenizesExceptionToCryptographi public void RoundTrip_ProtectedData() { // Arrange - var ephemeralProtector = new EphemeralDataProtectionProvider().CreateProtector("my purpose"); + var ephemeralProtector = new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("my purpose"); var timeLimitedProtector = new TimeLimitedDataProtector(ephemeralProtector); var expectedExpiration = StringToDateTime("2020-01-01 00:00:00Z"); diff --git a/test/Microsoft.AspNetCore.DataProtection.Redis.Test/Microsoft.AspNetCore.DataProtection.Redis.Test.csproj b/test/Microsoft.AspNetCore.DataProtection.Redis.Test/Microsoft.AspNetCore.DataProtection.Redis.Test.csproj index e1c3d0cb..39ba6b87 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Redis.Test/Microsoft.AspNetCore.DataProtection.Redis.Test.csproj +++ b/test/Microsoft.AspNetCore.DataProtection.Redis.Test/Microsoft.AspNetCore.DataProtection.Redis.Test.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/CngCbcAuthenticatedEncryptorFactoryTest.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/CngCbcAuthenticatedEncryptorFactoryTest.cs new file mode 100644 index 00000000..2b13d799 --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/CngCbcAuthenticatedEncryptorFactoryTest.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Cng; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.Test.Shared; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption +{ + public class CngCbcAuthenticatedEncryptorFactoryTest + { + [Fact] + public void CreateEncrptorInstance_UnknownDescriptorType_ReturnsNull() + { + // Arrange + var key = new Mock(); + key.Setup(k => k.Descriptor).Returns(new Mock().Object); + + var factory = new CngCbcAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + // Act + var encryptor = factory.CreateEncryptorInstance(key.Object); + + // Assert + Assert.Null(encryptor); + } + + [ConditionalFact] + [ConditionalRunTestOnlyOnWindows] + public void CreateEncrptorInstance_ExpectedDescriptorType_ReturnsEncryptor() + { + // Arrange + var descriptor = new CngCbcAuthenticatedEncryptorConfiguration().CreateNewDescriptor(); + var key = new Mock(); + key.Setup(k => k.Descriptor).Returns(descriptor); + + var factory = new CngCbcAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + // Act + var encryptor = factory.CreateEncryptorInstance(key.Object); + + // Assert + Assert.NotNull(encryptor); + Assert.IsType(encryptor); + } + } +} diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/CngGcmAuthenticatedEncryptorFactoryTest.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/CngGcmAuthenticatedEncryptorFactoryTest.cs new file mode 100644 index 00000000..e641705f --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/CngGcmAuthenticatedEncryptorFactoryTest.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Cng; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.Test.Shared; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption +{ + public class CngGcmAuthenticatedEncryptorFactoryTest + { + [Fact] + public void CreateEncrptorInstance_UnknownDescriptorType_ReturnsNull() + { + // Arrange + var key = new Mock(); + key.Setup(k => k.Descriptor).Returns(new Mock().Object); + + var factory = new CngGcmAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + // Act + var encryptor = factory.CreateEncryptorInstance(key.Object); + + // Assert + Assert.Null(encryptor); + } + + [ConditionalFact] + [ConditionalRunTestOnlyOnWindows] + public void CreateEncrptorInstance_ExpectedDescriptorType_ReturnsEncryptor() + { + // Arrange + var descriptor = new CngGcmAuthenticatedEncryptorConfiguration().CreateNewDescriptor(); + var key = new Mock(); + key.Setup(k => k.Descriptor).Returns(descriptor); + + var factory = new CngGcmAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + // Act + var encryptor = factory.CreateEncryptorInstance(key.Object); + + // Assert + Assert.NotNull(encryptor); + Assert.IsType(encryptor); + } + } +} diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorDeserializerTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorDeserializerTests.cs index ad79a1e2..92645667 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorDeserializerTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorDeserializerTests.cs @@ -3,6 +3,8 @@ using System; using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel @@ -13,13 +15,14 @@ public class AuthenticatedEncryptorDescriptorDeserializerTests public void ImportFromXml_Cbc_CreatesAppropriateDescriptor() { // Arrange - var control = new AuthenticatedEncryptorDescriptor( - new AuthenticatedEncryptionSettings() + var descriptor = new AuthenticatedEncryptorDescriptor( + new AuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = EncryptionAlgorithm.AES_192_CBC, ValidationAlgorithm = ValidationAlgorithm.HMACSHA512 }, - "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()).CreateEncryptorInstance(); + "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()); + var control = CreateEncryptorInstanceFromDescriptor(descriptor); const string xml = @" @@ -27,7 +30,8 @@ public void ImportFromXml_Cbc_CreatesAppropriateDescriptor() k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA== "; - var test = new AuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)).CreateEncryptorInstance(); + var deserializedDescriptor = new AuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)); + var test = CreateEncryptorInstanceFromDescriptor(deserializedDescriptor as AuthenticatedEncryptorDescriptor); // Act & assert byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; @@ -36,5 +40,19 @@ public void ImportFromXml_Cbc_CreatesAppropriateDescriptor() byte[] roundTripPlaintext = test.Decrypt(new ArraySegment(ciphertext), new ArraySegment(aad)); Assert.Equal(plaintext, roundTripPlaintext); } + + private static IAuthenticatedEncryptor CreateEncryptorInstanceFromDescriptor(AuthenticatedEncryptorDescriptor descriptor) + { + var key = new Key( + Guid.NewGuid(), + DateTimeOffset.Now, + DateTimeOffset.Now + TimeSpan.FromHours(1), + DateTimeOffset.Now + TimeSpan.FromDays(30), + descriptor); + + var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + return encryptorFactory.CreateEncryptorInstance(key); + } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorTests.cs index 96a16dcc..54b977c8 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorTests.cs @@ -8,9 +8,11 @@ using Microsoft.AspNetCore.Cryptography.Cng; using Microsoft.AspNetCore.Cryptography.SafeHandles; using Microsoft.AspNetCore.DataProtection.Cng; +using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.DataProtection.Managed; using Microsoft.AspNetCore.DataProtection.Test.Shared; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel @@ -38,7 +40,7 @@ public void CreateAuthenticatedEncryptor_RoundTripsData_CngCbcImplementation(Enc symmetricAlgorithmHandle: CachedAlgorithmHandles.AES_CBC, symmetricAlgorithmKeySizeInBytes: (uint)(keyLengthInBits / 8), hmacAlgorithmHandle: BCryptAlgorithmHandle.OpenAlgorithmHandle(hashAlgorithm, hmac: true)); - var test = CreateDescriptor(encryptionAlgorithm, validationAlgorithm, masterKey).CreateEncryptorInstance(); + var test = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, validationAlgorithm, masterKey)); // Act & assert - data round trips properly from control to test byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; @@ -64,7 +66,7 @@ public void CreateAuthenticatedEncryptor_RoundTripsData_CngGcmImplementation(Enc keyDerivationKey: masterKey, symmetricAlgorithmHandle: CachedAlgorithmHandles.AES_GCM, symmetricAlgorithmKeySizeInBytes: (uint)(keyLengthInBits / 8)); - var test = CreateDescriptor(encryptionAlgorithm, ValidationAlgorithm.HMACSHA256 /* unused */, masterKey).CreateEncryptorInstance(); + var test = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, ValidationAlgorithm.HMACSHA256 /* unused */, masterKey)); // Act & assert - data round trips properly from control to test byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; @@ -102,7 +104,7 @@ public void CreateAuthenticatedEncryptor_RoundTripsData_ManagedImplementation( symmetricAlgorithmFactory: () => Aes.Create(), symmetricAlgorithmKeySizeInBytes: keyLengthInBits / 8, validationAlgorithmFactory: validationAlgorithmFactory); - var test = CreateDescriptor(encryptionAlgorithm, validationAlgorithm, masterKey).CreateEncryptorInstance(); + var test = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, validationAlgorithm, masterKey)); // Act & assert - data round trips properly from control to test byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; @@ -160,11 +162,26 @@ public void ExportToXml_ProducesCorrectPayload_Gcm() private static AuthenticatedEncryptorDescriptor CreateDescriptor(EncryptionAlgorithm encryptionAlgorithm, ValidationAlgorithm validationAlgorithm, ISecret masterKey) { - return new AuthenticatedEncryptorDescriptor(new AuthenticatedEncryptionSettings() + return new AuthenticatedEncryptorDescriptor(new AuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = encryptionAlgorithm, ValidationAlgorithm = validationAlgorithm }, masterKey); } + + private static IAuthenticatedEncryptor CreateEncryptorInstanceFromDescriptor(AuthenticatedEncryptorDescriptor descriptor) + { + // Dummy key with the specified descriptor. + var key = new Key( + Guid.NewGuid(), + DateTimeOffset.Now, + DateTimeOffset.Now + TimeSpan.FromHours(1), + DateTimeOffset.Now + TimeSpan.FromDays(30), + descriptor); + + var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + return encryptorFactory.CreateEncryptorInstance(key); + } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorConfigurationTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorConfigurationTests.cs index d3e12501..9be30149 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorConfigurationTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorConfigurationTests.cs @@ -12,7 +12,7 @@ public class CngCbcAuthenticatedEncryptorConfigurationTests public void CreateNewDescriptor_CreatesUniqueCorrectlySizedMasterKey() { // Arrange - var configuration = new CngCbcAuthenticatedEncryptorConfiguration(new CngCbcAuthenticatedEncryptionSettings()); + var configuration = new CngCbcAuthenticatedEncryptorConfiguration(); // Act var masterKey1 = ((CngCbcAuthenticatedEncryptorDescriptor)configuration.CreateNewDescriptor()).MasterKey; @@ -28,13 +28,13 @@ public void CreateNewDescriptor_CreatesUniqueCorrectlySizedMasterKey() public void CreateNewDescriptor_PropagatesOptions() { // Arrange - var configuration = new CngCbcAuthenticatedEncryptorConfiguration(new CngCbcAuthenticatedEncryptionSettings()); + var configuration = new CngCbcAuthenticatedEncryptorConfiguration(); // Act var descriptor = (CngCbcAuthenticatedEncryptorDescriptor)configuration.CreateNewDescriptor(); // Assert - Assert.Equal(configuration.Settings, descriptor.Settings); + Assert.Equal(configuration, descriptor.Configuration); } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorDeserializerTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorDeserializerTests.cs index cffbb279..51897e64 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorDeserializerTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorDeserializerTests.cs @@ -4,8 +4,10 @@ using System; using System.Xml.Linq; using Microsoft.AspNetCore.Cryptography; +using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.DataProtection.Test.Shared; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel @@ -17,8 +19,8 @@ public class CngCbcAuthenticatedEncryptorDescriptorDeserializerTests public void ImportFromXml_CreatesAppropriateDescriptor() { // Arrange - var control = new CngCbcAuthenticatedEncryptorDescriptor( - new CngCbcAuthenticatedEncryptionSettings() + var descriptor = new CngCbcAuthenticatedEncryptorDescriptor( + new CngCbcAuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = Constants.BCRYPT_AES_ALGORITHM, EncryptionAlgorithmKeySize = 192, @@ -26,7 +28,8 @@ public void ImportFromXml_CreatesAppropriateDescriptor() HashAlgorithm = Constants.BCRYPT_SHA512_ALGORITHM, HashAlgorithmProvider = null }, - "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()).CreateEncryptorInstance(); + "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()); + var control = CreateEncryptorInstanceFromDescriptor(descriptor); const string xml = @" @@ -34,7 +37,8 @@ public void ImportFromXml_CreatesAppropriateDescriptor() k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA== "; - var test = new CngCbcAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)).CreateEncryptorInstance(); + var deserializedDescriptor = new CngCbcAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)); + var test = CreateEncryptorInstanceFromDescriptor(deserializedDescriptor as CngCbcAuthenticatedEncryptorDescriptor); // Act & assert byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; @@ -43,5 +47,19 @@ public void ImportFromXml_CreatesAppropriateDescriptor() byte[] roundTripPlaintext = test.Decrypt(new ArraySegment(ciphertext), new ArraySegment(aad)); Assert.Equal(plaintext, roundTripPlaintext); } + + private static IAuthenticatedEncryptor CreateEncryptorInstanceFromDescriptor(CngCbcAuthenticatedEncryptorDescriptor descriptor) + { + var key = new Key( + Guid.NewGuid(), + DateTimeOffset.Now, + DateTimeOffset.Now + TimeSpan.FromHours(1), + DateTimeOffset.Now + TimeSpan.FromDays(30), + descriptor); + + var encryptorFactory = new CngCbcAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + return encryptorFactory.CreateEncryptorInstance(key); + } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorTests.cs index beb176d5..090465fb 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngCbcAuthenticatedEncryptorDescriptorTests.cs @@ -13,7 +13,7 @@ public void ExportToXml_WithProviders_ProducesCorrectPayload() { // Arrange var masterKey = "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret(); - var descriptor = new CngCbcAuthenticatedEncryptorDescriptor(new CngCbcAuthenticatedEncryptionSettings() + var descriptor = new CngCbcAuthenticatedEncryptorDescriptor(new CngCbcAuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = "enc-alg", EncryptionAlgorithmKeySize = 2048, @@ -43,7 +43,7 @@ public void ExportToXml_WithoutProviders_ProducesCorrectPayload() { // Arrange var masterKey = "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret(); - var descriptor = new CngCbcAuthenticatedEncryptorDescriptor(new CngCbcAuthenticatedEncryptionSettings() + var descriptor = new CngCbcAuthenticatedEncryptorDescriptor(new CngCbcAuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = "enc-alg", EncryptionAlgorithmKeySize = 2048, diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorConfigurationTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorConfigurationTests.cs index a5b84d7d..e70460cf 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorConfigurationTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorConfigurationTests.cs @@ -12,7 +12,7 @@ public class CngGcmAuthenticatedEncryptorConfigurationTests public void CreateNewDescriptor_CreatesUniqueCorrectlySizedMasterKey() { // Arrange - var configuration = new CngGcmAuthenticatedEncryptorConfiguration(new CngGcmAuthenticatedEncryptionSettings()); + var configuration = new CngGcmAuthenticatedEncryptorConfiguration(); // Act var masterKey1 = ((CngGcmAuthenticatedEncryptorDescriptor)configuration.CreateNewDescriptor()).MasterKey; @@ -28,13 +28,13 @@ public void CreateNewDescriptor_CreatesUniqueCorrectlySizedMasterKey() public void CreateNewDescriptor_PropagatesOptions() { // Arrange - var configuration = new CngGcmAuthenticatedEncryptorConfiguration(new CngGcmAuthenticatedEncryptionSettings()); + var configuration = new CngGcmAuthenticatedEncryptorConfiguration(); // Act var descriptor = (CngGcmAuthenticatedEncryptorDescriptor)configuration.CreateNewDescriptor(); // Assert - Assert.Equal(configuration.Settings, descriptor.Settings); + Assert.Equal(configuration, descriptor.Configuration); } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorDeserializerTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorDeserializerTests.cs index cbb10767..6adbdcc1 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorDeserializerTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorDeserializerTests.cs @@ -4,8 +4,10 @@ using System; using System.Xml.Linq; using Microsoft.AspNetCore.Cryptography; +using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.DataProtection.Test.Shared; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel @@ -17,21 +19,23 @@ public class CngGcmAuthenticatedEncryptorDescriptorDeserializerTests public void ImportFromXml_CreatesAppropriateDescriptor() { // Arrange - var control = new CngGcmAuthenticatedEncryptorDescriptor( - new CngGcmAuthenticatedEncryptionSettings() + var descriptor = new CngGcmAuthenticatedEncryptorDescriptor( + new CngGcmAuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = Constants.BCRYPT_AES_ALGORITHM, EncryptionAlgorithmKeySize = 192, EncryptionAlgorithmProvider = null }, - "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()).CreateEncryptorInstance(); + "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()); + var control = CreateEncryptorInstanceFromDescriptor(descriptor); const string xml = @" k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA== "; - var test = new CngGcmAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)).CreateEncryptorInstance(); + var deserializedDescriptor = new CngGcmAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)); + var test = CreateEncryptorInstanceFromDescriptor(deserializedDescriptor as CngGcmAuthenticatedEncryptorDescriptor); // Act & assert byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; @@ -40,5 +44,19 @@ public void ImportFromXml_CreatesAppropriateDescriptor() byte[] roundTripPlaintext = test.Decrypt(new ArraySegment(ciphertext), new ArraySegment(aad)); Assert.Equal(plaintext, roundTripPlaintext); } + + private static IAuthenticatedEncryptor CreateEncryptorInstanceFromDescriptor(CngGcmAuthenticatedEncryptorDescriptor descriptor) + { + var key = new Key( + keyId: Guid.NewGuid(), + creationDate: DateTimeOffset.Now, + activationDate: DateTimeOffset.Now + TimeSpan.FromHours(1), + expirationDate: DateTimeOffset.Now + TimeSpan.FromDays(30), + descriptor: descriptor); + + var encryptorFactory = new CngGcmAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + return encryptorFactory.CreateEncryptorInstance(key); + } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorTests.cs index 57334b35..933f7e7d 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/CngGcmAuthenticatedEncryptorDescriptorTests.cs @@ -13,7 +13,7 @@ public void ExportToXml_WithProviders_ProducesCorrectPayload() { // Arrange var masterKey = "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret(); - var descriptor = new CngGcmAuthenticatedEncryptorDescriptor(new CngGcmAuthenticatedEncryptionSettings() + var descriptor = new CngGcmAuthenticatedEncryptorDescriptor(new CngGcmAuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = "enc-alg", EncryptionAlgorithmKeySize = 2048, @@ -40,7 +40,7 @@ public void ExportToXml_WithoutProviders_ProducesCorrectPayload() { // Arrange var masterKey = "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret(); - var descriptor = new CngGcmAuthenticatedEncryptorDescriptor(new CngGcmAuthenticatedEncryptionSettings() + var descriptor = new CngGcmAuthenticatedEncryptorDescriptor(new CngGcmAuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = "enc-alg", EncryptionAlgorithmKeySize = 2048 diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorConfigurationTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorConfigurationTests.cs index d851234b..6dbc4b7f 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorConfigurationTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorConfigurationTests.cs @@ -12,7 +12,7 @@ public class ManagedAuthenticatedEncryptorConfigurationTests public void CreateNewDescriptor_CreatesUniqueCorrectlySizedMasterKey() { // Arrange - var configuration = new ManagedAuthenticatedEncryptorConfiguration(new ManagedAuthenticatedEncryptionSettings()); + var configuration = new ManagedAuthenticatedEncryptorConfiguration(); // Act var masterKey1 = ((ManagedAuthenticatedEncryptorDescriptor)configuration.CreateNewDescriptor()).MasterKey; @@ -28,13 +28,13 @@ public void CreateNewDescriptor_CreatesUniqueCorrectlySizedMasterKey() public void CreateNewDescriptor_PropagatesOptions() { // Arrange - var configuration = new ManagedAuthenticatedEncryptorConfiguration(new ManagedAuthenticatedEncryptionSettings()); + var configuration = new ManagedAuthenticatedEncryptorConfiguration(); // Act var descriptor = (ManagedAuthenticatedEncryptorDescriptor)configuration.CreateNewDescriptor(); // Assert - Assert.Equal(configuration.Settings, descriptor.Settings); + Assert.Equal(configuration, descriptor.Configuration); } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorDeserializerTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorDeserializerTests.cs index a79fc1c6..9a5162cc 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorDeserializerTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorDeserializerTests.cs @@ -4,6 +4,8 @@ using System; using System.Security.Cryptography; using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel @@ -18,16 +20,17 @@ public class ManagedAuthenticatedEncryptorDescriptorDeserializerTests public void ImportFromXml_BuiltInTypes_CreatesAppropriateDescriptor(Type encryptionAlgorithmType, Type validationAlgorithmType) { // Arrange - var control = new ManagedAuthenticatedEncryptorDescriptor( - new ManagedAuthenticatedEncryptionSettings() + var descriptor = new ManagedAuthenticatedEncryptorDescriptor( + new ManagedAuthenticatedEncryptorConfiguration() { EncryptionAlgorithmType = encryptionAlgorithmType, EncryptionAlgorithmKeySize = 192, ValidationAlgorithmType = validationAlgorithmType }, - "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()).CreateEncryptorInstance(); + "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()); + var control = CreateEncryptorInstanceFromDescriptor(descriptor); - string xml = String.Format(@" + string xml = string.Format(@" @@ -36,7 +39,8 @@ public void ImportFromXml_BuiltInTypes_CreatesAppropriateDescriptor(Type encrypt ", encryptionAlgorithmType.Name, validationAlgorithmType.Name); - var test = new ManagedAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)).CreateEncryptorInstance(); + var deserializedDescriptor = new ManagedAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)); + var test = CreateEncryptorInstanceFromDescriptor(deserializedDescriptor as ManagedAuthenticatedEncryptorDescriptor); // Act & assert byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; @@ -50,16 +54,17 @@ public void ImportFromXml_BuiltInTypes_CreatesAppropriateDescriptor(Type encrypt public void ImportFromXml_CustomType_CreatesAppropriateDescriptor() { // Arrange - var control = new ManagedAuthenticatedEncryptorDescriptor( - new ManagedAuthenticatedEncryptionSettings() + var descriptor = new ManagedAuthenticatedEncryptorDescriptor( + new ManagedAuthenticatedEncryptorConfiguration() { EncryptionAlgorithmType = typeof(Aes), EncryptionAlgorithmKeySize = 192, ValidationAlgorithmType = typeof(HMACSHA384) }, - "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()).CreateEncryptorInstance(); + "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret()); + var control = CreateEncryptorInstanceFromDescriptor(descriptor); - string xml = String.Format(@" + string xml = string.Format(@" @@ -68,7 +73,8 @@ public void ImportFromXml_CustomType_CreatesAppropriateDescriptor() ", typeof(Aes).AssemblyQualifiedName, typeof(HMACSHA384).AssemblyQualifiedName); - var test = new ManagedAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)).CreateEncryptorInstance(); + var deserializedDescriptor = new ManagedAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)); + var test = CreateEncryptorInstanceFromDescriptor(deserializedDescriptor as ManagedAuthenticatedEncryptorDescriptor); // Act & assert byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 }; @@ -77,5 +83,19 @@ public void ImportFromXml_CustomType_CreatesAppropriateDescriptor() byte[] roundTripPlaintext = test.Decrypt(new ArraySegment(ciphertext), new ArraySegment(aad)); Assert.Equal(plaintext, roundTripPlaintext); } + + private static IAuthenticatedEncryptor CreateEncryptorInstanceFromDescriptor(ManagedAuthenticatedEncryptorDescriptor descriptor) + { + var key = new Key( + Guid.NewGuid(), + DateTimeOffset.Now, + DateTimeOffset.Now + TimeSpan.FromHours(1), + DateTimeOffset.Now + TimeSpan.FromDays(30), + descriptor); + + var encryptorFactory = new ManagedAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + return encryptorFactory.CreateEncryptorInstance(key); + } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorTests.cs index 6383b7e7..4e4f4534 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ConfigurationModel/ManagedAuthenticatedEncryptorDescriptorTests.cs @@ -14,7 +14,7 @@ public void ExportToXml_CustomTypes_ProducesCorrectPayload() { // Arrange var masterKey = "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret(); - var descriptor = new ManagedAuthenticatedEncryptorDescriptor(new ManagedAuthenticatedEncryptionSettings() + var descriptor = new ManagedAuthenticatedEncryptorDescriptor(new ManagedAuthenticatedEncryptorConfiguration() { EncryptionAlgorithmType = typeof(MySymmetricAlgorithm), EncryptionAlgorithmKeySize = 2048, @@ -26,7 +26,7 @@ public void ExportToXml_CustomTypes_ProducesCorrectPayload() // Assert Assert.Equal(typeof(ManagedAuthenticatedEncryptorDescriptorDeserializer), retVal.DeserializerType); - string expectedXml = String.Format(@" + string expectedXml = string.Format(@" @@ -47,7 +47,7 @@ public void ExportToXml_BuiltInTypes_ProducesCorrectPayload(Type encryptionAlgor { // Arrange var masterKey = "k88VrwGLINfVAqzlAp7U4EAjdlmUG17c756McQGdjHU8Ajkfc/A3YOKdqlMcF6dXaIxATED+g2f62wkRRRRRzA==".ToSecret(); - var descriptor = new ManagedAuthenticatedEncryptorDescriptor(new ManagedAuthenticatedEncryptionSettings() + var descriptor = new ManagedAuthenticatedEncryptorDescriptor(new ManagedAuthenticatedEncryptorConfiguration() { EncryptionAlgorithmType = encryptionAlgorithmType, EncryptionAlgorithmKeySize = 2048, @@ -59,7 +59,7 @@ public void ExportToXml_BuiltInTypes_ProducesCorrectPayload(Type encryptionAlgor // Assert Assert.Equal(typeof(ManagedAuthenticatedEncryptorDescriptorDeserializer), retVal.DeserializerType); - string expectedXml = String.Format(@" + string expectedXml = string.Format(@" diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactoryTest.cs b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactoryTest.cs new file mode 100644 index 00000000..ef5eae5d --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.Test/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactoryTest.cs @@ -0,0 +1,50 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Cng; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.Managed; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption +{ + public class ManagedAuthenticatedEncryptorFactoryTest + { + [Fact] + public void CreateEncrptorInstance_UnknownDescriptorType_ReturnsNull() + { + // Arrange + var key = new Mock(); + key.Setup(k => k.Descriptor).Returns(new Mock().Object); + + var factory = new ManagedAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + // Act + var encryptor = factory.CreateEncryptorInstance(key.Object); + + // Assert + Assert.Null(encryptor); + } + + [Fact] + public void CreateEncrptorInstance_ExpectedDescriptorType_ReturnsEncryptor() + { + // Arrange + var descriptor = new ManagedAuthenticatedEncryptorConfiguration().CreateNewDescriptor(); + var key = new Mock(); + key.Setup(k => k.Descriptor).Returns(descriptor); + + var factory = new ManagedAuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + + // Act + var encryptor = factory.CreateEncryptorInstance(key.Object); + + // Assert + Assert.NotNull(encryptor); + Assert.IsType(encryptor); + } + } +} diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/EphemeralDataProtectionProviderTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/EphemeralDataProtectionProviderTests.cs index 45a51e22..dc7dc642 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/EphemeralDataProtectionProviderTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/EphemeralDataProtectionProviderTests.cs @@ -4,6 +4,7 @@ using System; using System.Security.Cryptography; using System.Text; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection @@ -14,8 +15,8 @@ public class EphemeralDataProtectionProviderTests public void DifferentProvider_SamePurpose_DoesNotRoundTripData() { // Arrange - var dataProtector1 = new EphemeralDataProtectionProvider().CreateProtector("purpose"); - var dataProtector2 = new EphemeralDataProtectionProvider().CreateProtector("purpose"); + var dataProtector1 = new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("purpose"); + var dataProtector2 = new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("purpose"); byte[] bytes = Encoding.UTF8.GetBytes("Hello there!"); // Act & assert @@ -31,7 +32,7 @@ public void DifferentProvider_SamePurpose_DoesNotRoundTripData() public void SingleProvider_DifferentPurpose_DoesNotRoundTripData() { // Arrange - var dataProtectionProvider = new EphemeralDataProtectionProvider(); + var dataProtectionProvider = new EphemeralDataProtectionProvider(NullLoggerFactory.Instance); var dataProtector1 = dataProtectionProvider.CreateProtector("purpose"); var dataProtector2 = dataProtectionProvider.CreateProtector("different purpose"); byte[] bytes = Encoding.UTF8.GetBytes("Hello there!"); @@ -48,7 +49,7 @@ public void SingleProvider_DifferentPurpose_DoesNotRoundTripData() public void SingleProvider_SamePurpose_RoundTripsData() { // Arrange - var dataProtectionProvider = new EphemeralDataProtectionProvider(); + var dataProtectionProvider = new EphemeralDataProtectionProvider(NullLoggerFactory.Instance); var dataProtector1 = dataProtectionProvider.CreateProtector("purpose"); var dataProtector2 = dataProtectionProvider.CreateProtector("purpose"); // should be equivalent to the previous instance byte[] bytes = Encoding.UTF8.GetBytes("Hello there!"); diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/Internal/KeyManagementOptionsSetupTest.cs b/test/Microsoft.AspNetCore.DataProtection.Test/Internal/KeyManagementOptionsSetupTest.cs new file mode 100644 index 00000000..6de0c195 --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.Test/Internal/KeyManagementOptionsSetupTest.cs @@ -0,0 +1,155 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Win32; +using Xunit; + +namespace Microsoft.AspNetCore.DataProtection.Internal +{ + public class KeyManagementOptionsSetupTest + { + [Fact] + public void Configure_SetsExpectedValues() + { + // Arrange + var setup = new KeyManagementOptionsSetup(NullLoggerFactory.Instance); + var options = new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = null + }; + + // Act + setup.Configure(options); + + // Assert + Assert.Empty(options.KeyEscrowSinks); + Assert.NotNull(options.AuthenticatedEncryptorConfiguration); + Assert.IsType(options.AuthenticatedEncryptorConfiguration); + Assert.Collection( + options.AuthenticatedEncryptorFactories, + f => Assert.IsType(f), + f => Assert.IsType(f), + f => Assert.IsType(f), + f => Assert.IsType(f)); + } + + [ConditionalFact] + [ConditionalRunTestOnlyIfHkcuRegistryAvailable] + public void Configure_WithRegistryPolicyResolver_SetsValuesFromResolver() + { + // Arrange + var registryEntries = new Dictionary() + { + ["KeyEscrowSinks"] = String.Join(" ;; ; ", new Type[] { typeof(MyKeyEscrowSink1), typeof(MyKeyEscrowSink2) }.Select(t => t.AssemblyQualifiedName)), + ["EncryptionType"] = "managed", + ["DefaultKeyLifetime"] = 1024 // days + }; + var options = new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = null + }; + + // Act + RunTest(registryEntries, options); + + // Assert + Assert.Collection( + options.KeyEscrowSinks, + k => Assert.IsType(k), + k => Assert.IsType(k)); + Assert.Equal(TimeSpan.FromDays(1024), options.NewKeyLifetime); + Assert.NotNull(options.AuthenticatedEncryptorConfiguration); + Assert.IsType(options.AuthenticatedEncryptorConfiguration); + Assert.Collection( + options.AuthenticatedEncryptorFactories, + f => Assert.IsType(f), + f => Assert.IsType(f), + f => Assert.IsType(f), + f => Assert.IsType(f)); + } + + private static void RunTest(Dictionary regValues, KeyManagementOptions options) + { + WithUniqueTempRegKey(registryKey => + { + foreach (var entry in regValues) + { + registryKey.SetValue(entry.Key, entry.Value); + } + + var policyResolver = new RegistryPolicyResolver( + registryKey, + activator: SimpleActivator.DefaultWithoutServices, + loggerFactory: NullLoggerFactory.Instance); + + var setup = new KeyManagementOptionsSetup(NullLoggerFactory.Instance, policyResolver); + + setup.Configure(options); + }); + } + + /// + /// Runs a test and cleans up the registry key afterward. + /// + private static void WithUniqueTempRegKey(Action testCode) + { + string uniqueName = Guid.NewGuid().ToString(); + var uniqueSubkey = LazyHkcuTempKey.Value.CreateSubKey(uniqueName); + try + { + testCode(uniqueSubkey); + } + finally + { + // clean up when test is done + LazyHkcuTempKey.Value.DeleteSubKeyTree(uniqueName, throwOnMissingSubKey: false); + } + } + + private static readonly Lazy LazyHkcuTempKey = new Lazy(() => + { + try + { + return Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\ASP.NET\temp"); + } + catch + { + // swallow all failures + return null; + } + }); + + private class ConditionalRunTestOnlyIfHkcuRegistryAvailable : Attribute, ITestCondition + { + public bool IsMet => (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && LazyHkcuTempKey.Value != null); + + public string SkipReason { get; } = "HKCU registry couldn't be opened."; + } + + private class MyKeyEscrowSink1 : IKeyEscrowSink + { + public void Store(Guid keyId, XElement element) + { + throw new NotImplementedException(); + } + } + + private class MyKeyEscrowSink2 : IKeyEscrowSink + { + public void Store(Guid keyId, XElement element) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/DefaultKeyResolverTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/DefaultKeyResolverTests.cs index dec696bf..9aeb5ea2 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/DefaultKeyResolverTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/DefaultKeyResolverTests.cs @@ -4,8 +4,11 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -17,7 +20,7 @@ public class DefaultKeyResolverTests public void ResolveDefaultKeyPolicy_EmptyKeyRing_ReturnsNullDefaultKey() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); // Act var resolution = resolver.ResolveDefaultKeyPolicy(DateTimeOffset.Now, new IKey[0]); @@ -31,7 +34,7 @@ public void ResolveDefaultKeyPolicy_EmptyKeyRing_ReturnsNullDefaultKey() public void ResolveDefaultKeyPolicy_ValidExistingKey_ReturnsExistingKey() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z"); var key2 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z"); @@ -47,7 +50,7 @@ public void ResolveDefaultKeyPolicy_ValidExistingKey_ReturnsExistingKey() public void ResolveDefaultKeyPolicy_ValidExistingKey_AllowsForClockSkew_KeysStraddleSkewLine_ReturnsExistingKey() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z"); var key2 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z"); @@ -63,7 +66,7 @@ public void ResolveDefaultKeyPolicy_ValidExistingKey_AllowsForClockSkew_KeysStra public void ResolveDefaultKeyPolicy_ValidExistingKey_AllowsForClockSkew_AllKeysInFuture_ReturnsExistingKey() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z"); // Act @@ -78,7 +81,7 @@ public void ResolveDefaultKeyPolicy_ValidExistingKey_AllowsForClockSkew_AllKeysI public void ResolveDefaultKeyPolicy_ValidExistingKey_NoSuccessor_ReturnsExistingKey_SignalsGenerateNewKey() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z"); // Act @@ -93,7 +96,7 @@ public void ResolveDefaultKeyPolicy_ValidExistingKey_NoSuccessor_ReturnsExisting public void ResolveDefaultKeyPolicy_ValidExistingKey_NoLegitimateSuccessor_ReturnsExistingKey_SignalsGenerateNewKey() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z"); var key2 = CreateKey("2016-03-01 00:00:00Z", "2017-03-01 00:00:00Z", isRevoked: true); var key3 = CreateKey("2016-03-01 00:00:00Z", "2016-03-02 00:00:00Z"); // key expires too soon @@ -110,7 +113,7 @@ public void ResolveDefaultKeyPolicy_ValidExistingKey_NoLegitimateSuccessor_Retur public void ResolveDefaultKeyPolicy_MostRecentKeyIsInvalid_BecauseOfRevocation_ReturnsNull() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z"); var key2 = CreateKey("2015-03-02 00:00:00Z", "2016-03-01 00:00:00Z", isRevoked: true); @@ -126,9 +129,9 @@ public void ResolveDefaultKeyPolicy_MostRecentKeyIsInvalid_BecauseOfRevocation_R public void ResolveDefaultKeyPolicy_MostRecentKeyIsInvalid_BecauseOfFailureToDecipher_ReturnsNull() { // Arrange - var resolver = CreateDefaultKeyResolver(); var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z"); - var key2 = CreateKey("2015-03-02 00:00:00Z", "2016-03-01 00:00:00Z", createEncryptorInstanceThrows: true); + var key2 = CreateKey("2015-03-02 00:00:00Z", "2016-03-01 00:00:00Z"); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory(throwForKeys: key2)); // Act var resolution = resolver.ResolveDefaultKeyPolicy("2015-04-01 00:00:00Z", key1, key2); @@ -142,11 +145,11 @@ public void ResolveDefaultKeyPolicy_MostRecentKeyIsInvalid_BecauseOfFailureToDec public void ResolveDefaultKeyPolicy_FutureKeyIsValidAndWithinClockSkew_ReturnsFutureKey() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z"); // Act - var resolution = resolver.ResolveDefaultKeyPolicy("2015-02-28 23:53:00Z", key1); + var resolution = resolver.ResolveDefaultKeyPolicy("2015-02-28 23:55:00Z", key1); // Assert Assert.Same(key1, resolution.DefaultKey); @@ -157,7 +160,7 @@ public void ResolveDefaultKeyPolicy_FutureKeyIsValidAndWithinClockSkew_ReturnsFu public void ResolveDefaultKeyPolicy_FutureKeyIsValidButNotWithinClockSkew_ReturnsNull() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2015-03-01 00:00:00Z", "2016-03-01 00:00:00Z"); // Act @@ -172,7 +175,7 @@ public void ResolveDefaultKeyPolicy_FutureKeyIsValidButNotWithinClockSkew_Return public void ResolveDefaultKeyPolicy_IgnoresExpiredOrRevokedFutureKeys() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2015-03-01 00:00:00Z", "2014-03-01 00:00:00Z"); // expiration before activation should never occur var key2 = CreateKey("2015-03-01 00:01:00Z", "2015-04-01 00:00:00Z", isRevoked: true); var key3 = CreateKey("2015-03-01 00:02:00Z", "2015-04-01 00:00:00Z"); @@ -189,7 +192,7 @@ public void ResolveDefaultKeyPolicy_IgnoresExpiredOrRevokedFutureKeys() public void ResolveDefaultKeyPolicy_FallbackKey_SelectsLatestBeforePriorPropagationWindow_IgnoresRevokedKeys() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-01 00:00:00Z"); var key2 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-02 00:00:00Z"); var key3 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-03 00:00:00Z", isRevoked: true); @@ -207,11 +210,11 @@ public void ResolveDefaultKeyPolicy_FallbackKey_SelectsLatestBeforePriorPropagat public void ResolveDefaultKeyPolicy_FallbackKey_SelectsLatestBeforePriorPropagationWindow_IgnoresFailures() { // Arrange - var resolver = CreateDefaultKeyResolver(); var key1 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-01 00:00:00Z"); var key2 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-02 00:00:00Z"); - var key3 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-03 00:00:00Z", createEncryptorInstanceThrows: true); + var key3 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-03 00:00:00Z"); var key4 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-04 00:00:00Z"); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory(throwForKeys: key3)); // Act var resolution = resolver.ResolveDefaultKeyPolicy("2000-01-05 00:00:00Z", key1, key2, key3, key4); @@ -225,7 +228,7 @@ public void ResolveDefaultKeyPolicy_FallbackKey_SelectsLatestBeforePriorPropagat public void ResolveDefaultKeyPolicy_FallbackKey_NoNonRevokedKeysBeforePriorPropagationWindow_SelectsEarliestNonRevokedKey() { // Arrange - var resolver = CreateDefaultKeyResolver(); + var resolver = CreateDefaultKeyResolver(new MyEncryptorFactory()); var key1 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-03 00:00:00Z", isRevoked: true); var key2 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-04 00:00:00Z"); var key3 = CreateKey("2010-01-01 00:00:00Z", "2010-01-01 00:00:00Z", creationDate: "2000-01-05 00:00:00Z"); @@ -238,15 +241,14 @@ public void ResolveDefaultKeyPolicy_FallbackKey_NoNonRevokedKeysBeforePriorPropa Assert.True(resolution.ShouldGenerateNewKey); } - private static IDefaultKeyResolver CreateDefaultKeyResolver() + private static IDefaultKeyResolver CreateDefaultKeyResolver(IAuthenticatedEncryptorFactory encryptorFactory) { - return new DefaultKeyResolver( - keyPropagationWindow: TimeSpan.FromDays(2), - maxServerToServerClockSkew: TimeSpan.FromMinutes(7), - services: null); + var options = Options.Create(new KeyManagementOptions()); + options.Value.AuthenticatedEncryptorFactories.Add(encryptorFactory); + return new DefaultKeyResolver(options, NullLoggerFactory.Instance); } - private static IKey CreateKey(string activationDate, string expirationDate, string creationDate = null, bool isRevoked = false, bool createEncryptorInstanceThrows = false) + private static IKey CreateKey(string activationDate, string expirationDate, string creationDate = null, bool isRevoked = false) { var mockKey = new Mock(); mockKey.Setup(o => o.KeyId).Returns(Guid.NewGuid()); @@ -254,15 +256,30 @@ private static IKey CreateKey(string activationDate, string expirationDate, stri mockKey.Setup(o => o.ActivationDate).Returns(DateTimeOffset.ParseExact(activationDate, "u", CultureInfo.InvariantCulture)); mockKey.Setup(o => o.ExpirationDate).Returns(DateTimeOffset.ParseExact(expirationDate, "u", CultureInfo.InvariantCulture)); mockKey.Setup(o => o.IsRevoked).Returns(isRevoked); - if (createEncryptorInstanceThrows) + + return mockKey.Object; + } + + private class MyEncryptorFactory : IAuthenticatedEncryptorFactory + { + private IReadOnlyList _throwForKeys; + + public MyEncryptorFactory(params IKey[] throwForKeys) { - mockKey.Setup(o => o.CreateEncryptorInstance()).Throws(new Exception("This method fails.")); + _throwForKeys = throwForKeys; } - else + + public IAuthenticatedEncryptor CreateEncryptorInstance(IKey key) { - mockKey.Setup(o => o.CreateEncryptorInstance()).Returns(new Mock().Object); + if (_throwForKeys.Contains(key)) + { + throw new Exception("This method fails."); + } + else + { + return new Mock().Object; + } } - return mockKey.Object; } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/DeferredKeyTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/DeferredKeyTests.cs index 53ec5940..90cf63c0 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/DeferredKeyTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/DeferredKeyTests.cs @@ -3,10 +3,10 @@ using System; using System.Xml.Linq; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -22,15 +22,25 @@ public void Ctor_Properties() var creationDate = DateTimeOffset.Now; var activationDate = creationDate.AddDays(2); var expirationDate = creationDate.AddDays(90); + var mockDescriptor = Mock.Of(); + var mockInternalKeyManager = new Mock(); + mockInternalKeyManager.Setup(o => o.DeserializeDescriptorFromKeyElement(It.IsAny())) + .Returns(element => + { + XmlAssert.Equal(@"", element); + return mockDescriptor; + }); + var options = Options.Create(new KeyManagementOptions()); // Act - var key = new DeferredKey(keyId, creationDate, activationDate, expirationDate, new Mock().Object, XElement.Parse(@"")); + var key = new DeferredKey(keyId, creationDate, activationDate, expirationDate, mockInternalKeyManager.Object, XElement.Parse(@"")); // Assert Assert.Equal(keyId, key.KeyId); Assert.Equal(creationDate, key.CreationDate); Assert.Equal(activationDate, key.ActivationDate); Assert.Equal(expirationDate, key.ExpirationDate); + Assert.Same(mockDescriptor, key.Descriptor); } [Fact] @@ -38,6 +48,7 @@ public void SetRevoked_Respected() { // Arrange var now = DateTimeOffset.UtcNow; + var options = Options.Create(new KeyManagementOptions()); var key = new DeferredKey(Guid.Empty, now, now, now, new Mock().Object, XElement.Parse(@"")); // Act & assert @@ -47,32 +58,7 @@ public void SetRevoked_Respected() } [Fact] - public void CreateEncryptorInstance_Success() - { - // Arrange - var expectedEncryptor = new Mock().Object; - var mockDescriptor = new Mock(); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(expectedEncryptor); - var mockKeyManager = new Mock(); - mockKeyManager.Setup(o => o.DeserializeDescriptorFromKeyElement(It.IsAny())) - .Returns(element => - { - XmlAssert.Equal(@"", element); - return mockDescriptor.Object; - }); - - var now = DateTimeOffset.UtcNow; - var key = new DeferredKey(Guid.Empty, now, now, now, mockKeyManager.Object, XElement.Parse(@"")); - - // Act - var actual = key.CreateEncryptorInstance(); - - // Assert - Assert.Same(expectedEncryptor, actual); - } - - [Fact] - public void CreateEncryptorInstance_CachesFailures() + public void Get_Descriptor_CachesFailures() { // Arrange int numTimesCalled = 0; @@ -88,8 +74,8 @@ public void CreateEncryptorInstance_CachesFailures() var key = new DeferredKey(Guid.Empty, now, now, now, mockKeyManager.Object, XElement.Parse(@"")); // Act & assert - ExceptionAssert.Throws(() => key.CreateEncryptorInstance(), "How exceptional."); - ExceptionAssert.Throws(() => key.CreateEncryptorInstance(), "How exceptional."); + ExceptionAssert.Throws(() => key.Descriptor, "How exceptional."); + ExceptionAssert.Throws(() => key.Descriptor, "How exceptional."); Assert.Equal(1, numTimesCalled); } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyEscrowServiceProviderExtensionsTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyEscrowServiceProviderExtensionsTests.cs index bd90f374..8db64657 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyEscrowServiceProviderExtensionsTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyEscrowServiceProviderExtensionsTests.cs @@ -39,7 +39,7 @@ public void GetKeyEscrowSink_SingleKeyEscrowRegistration_ReturnsAggregateOverSin mockKeyEscrowSink.Setup(o => o.Store(It.IsAny(), It.IsAny())) .Callback((keyId, element) => { - output.Add(String.Format(CultureInfo.InvariantCulture, "{0:D}: {1}", keyId, element.Name.LocalName)); + output.Add(string.Format(CultureInfo.InvariantCulture, "{0:D}: {1}", keyId, element.Name.LocalName)); }); var serviceCollection = new ServiceCollection(); @@ -64,14 +64,14 @@ public void GetKeyEscrowSink_MultipleKeyEscrowRegistration_ReturnsAggregate() mockKeyEscrowSink1.Setup(o => o.Store(It.IsAny(), It.IsAny())) .Callback((keyId, element) => { - output.Add(String.Format(CultureInfo.InvariantCulture, "[sink1] {0:D}: {1}", keyId, element.Name.LocalName)); + output.Add(string.Format(CultureInfo.InvariantCulture, "[sink1] {0:D}: {1}", keyId, element.Name.LocalName)); }); var mockKeyEscrowSink2 = new Mock(); mockKeyEscrowSink2.Setup(o => o.Store(It.IsAny(), It.IsAny())) .Callback((keyId, element) => { - output.Add(String.Format(CultureInfo.InvariantCulture, "[sink2] {0:D}: {1}", keyId, element.Name.LocalName)); + output.Add(string.Format(CultureInfo.InvariantCulture, "[sink2] {0:D}: {1}", keyId, element.Name.LocalName)); }); var serviceCollection = new ServiceCollection(); diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingBasedDataProtectorTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingBasedDataProtectorTests.cs index 2e510d5c..42b5153c 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingBasedDataProtectorTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingBasedDataProtectorTests.cs @@ -11,6 +11,8 @@ using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; @@ -24,7 +26,7 @@ public void Protect_NullPlaintext_Throws() // Arrange IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: new Mock().Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -59,7 +61,7 @@ public void Protect_EncryptsToDefaultProtector_MultiplePurposes() IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: new[] { "purpose1", "purpose2" }, newPurpose: "yet another purpose"); @@ -97,7 +99,7 @@ public void Protect_EncryptsToDefaultProtector_SinglePurpose() IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: new string[0], newPurpose: "single purpose"); @@ -114,7 +116,7 @@ public void Protect_HomogenizesExceptionsToCryptographicException() // Arrange IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: new Mock(MockBehavior.Strict).Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -129,7 +131,7 @@ public void Unprotect_NullProtectedData_Throws() // Arrange IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: new Mock().Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -143,7 +145,7 @@ public void Unprotect_PayloadTooShort_ThrowsBadMagicHeader() // Arrange IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: new Mock().Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -161,7 +163,7 @@ public void Unprotect_PayloadHasBadMagicHeader_ThrowsBadMagicHeader() // Arrange IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: new Mock().Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -179,7 +181,7 @@ public void Unprotect_PayloadHasIncorrectVersionMarker_ThrowsNewerVersion() // Arrange IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: new Mock().Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -201,17 +203,19 @@ public void Unprotect_KeyNotFound_ThrowsKeyNotFound() ciphertext: new byte[0]); var mockDescriptor = new Mock(); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(new Mock().Object); + var mockEncryptorFactory = new Mock(); + mockEncryptorFactory.Setup(o => o.CreateEncryptorInstance(It.IsAny())).Returns(new Mock().Object); + var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance); // the keyring has only one key Key key = new Key(Guid.Empty, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object); - var keyRing = new KeyRing(key, new[] { key }); + var keyRing = new KeyRing(key, new[] { key }, new[] { mockEncryptorFactory.Object }); var mockKeyRingProvider = new Mock(); mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing); IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -230,18 +234,19 @@ public void Unprotect_KeyRevoked_RevocationDisallowed_ThrowsKeyRevoked() ciphertext: new byte[0]); var mockDescriptor = new Mock(); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(new Mock().Object); + var mockEncryptorFactory = new Mock(); + mockEncryptorFactory.Setup(o => o.CreateEncryptorInstance(It.IsAny())).Returns(new Mock().Object); // the keyring has only one key Key key = new Key(keyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object); key.SetRevoked(); - var keyRing = new KeyRing(key, new[] { key }); + var keyRing = new KeyRing(key, new[] { key }, new[] { mockEncryptorFactory.Object }); var mockKeyRingProvider = new Mock(); mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing); IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -270,17 +275,18 @@ public void Unprotect_KeyRevoked_RevocationAllowed_ReturnsOriginalData_SetsRevok return expectedPlaintext; }); var mockDescriptor = new Mock(); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(mockEncryptor.Object); + var mockEncryptorFactory = new Mock(); + mockEncryptorFactory.Setup(o => o.CreateEncryptorInstance(It.IsAny())).Returns(mockEncryptor.Object); Key defaultKey = new Key(defaultKeyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object); defaultKey.SetRevoked(); - var keyRing = new KeyRing(defaultKey, new[] { defaultKey }); + var keyRing = new KeyRing(defaultKey, new[] { defaultKey }, new[] { mockEncryptorFactory.Object }); var mockKeyRingProvider = new Mock(); mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing); IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -317,16 +323,17 @@ public void Unprotect_IsAlsoDefaultKey_Success_NoMigrationRequired() return expectedPlaintext; }); var mockDescriptor = new Mock(); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(mockEncryptor.Object); + var mockEncryptorFactory = new Mock(); + mockEncryptorFactory.Setup(o => o.CreateEncryptorInstance(It.IsAny())).Returns(mockEncryptor.Object); Key defaultKey = new Key(defaultKeyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object); - var keyRing = new KeyRing(defaultKey, new[] { defaultKey }); + var keyRing = new KeyRing(defaultKey, new[] { defaultKey }, new[] { mockEncryptorFactory.Object }); var mockKeyRingProvider = new Mock(); mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing); IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -366,17 +373,18 @@ public void Unprotect_IsNotDefaultKey_Success_RequiresMigration() return expectedPlaintext; }); var mockDescriptor = new Mock(); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(mockEncryptor.Object); + var mockEncryptorFactory = new Mock(); + mockEncryptorFactory.Setup(o => o.CreateEncryptorInstance(It.IsAny())).Returns(mockEncryptor.Object); Key defaultKey = new Key(defaultKeyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new Mock().Object); Key embeddedKey = new Key(embeddedKeyId, DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, mockDescriptor.Object); - var keyRing = new KeyRing(defaultKey, new[] { defaultKey, embeddedKey }); + var keyRing = new KeyRing(defaultKey, new[] { defaultKey, embeddedKey }, new[] { mockEncryptorFactory.Object }); var mockKeyRingProvider = new Mock(); mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing); IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -400,14 +408,15 @@ public void Protect_Unprotect_RoundTripsProperly() { // Arrange byte[] plaintext = new byte[] { 0x10, 0x20, 0x30, 0x40, 0x50 }; - Key key = new Key(Guid.NewGuid(), DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new AuthenticatedEncryptorConfiguration(new AuthenticatedEncryptionSettings()).CreateNewDescriptor()); - var keyRing = new KeyRing(key, new[] { key }); + Key key = new Key(Guid.NewGuid(), DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new AuthenticatedEncryptorConfiguration().CreateNewDescriptor()); + var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance); + var keyRing = new KeyRing(key, new[] { key }, new[] { encryptorFactory }); var mockKeyRingProvider = new Mock(); mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing); var protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose"); @@ -448,7 +457,7 @@ public void CreateProtector_ChainsPurposes() IDataProtector protector = new KeyRingBasedDataProtector( keyRingProvider: mockKeyRingProvider.Object, - logger: null, + logger: GetLogger(), originalPurposes: null, newPurpose: "purpose1").CreateProtector("purpose2"); @@ -484,5 +493,11 @@ private static byte[] BuildProtectedDataFromCiphertext(Guid keyId, byte[] cipher .Concat(ciphertext).ToArray(); } + + private static ILogger GetLogger() + { + var loggerFactory = NullLoggerFactory.Instance; + return loggerFactory.CreateLogger(typeof(KeyRingBasedDataProtector)); + } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingProviderTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingProviderTests.cs index 2f7517c8..7337c779 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingProviderTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingProviderTests.cs @@ -7,14 +7,15 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using Xunit; -using static System.FormattableString; - namespace Microsoft.AspNetCore.DataProtection.KeyManagement { public class KeyRingProviderTests @@ -353,62 +354,6 @@ public void CreateCacheableKeyRing_GenerationRequired_WithFallbackKey_KeyGenerat Assert.Equal(new[] { "GetCacheExpirationToken", "GetAllKeys", "ResolveDefaultKeyPolicy" }, callSequence); } - private static ICacheableKeyRingProvider SetupCreateCacheableKeyRingTestAndCreateKeyManager( - IList callSequence, - IEnumerable getCacheExpirationTokenReturnValues, - IEnumerable> getAllKeysReturnValues, - IEnumerable> createNewKeyCallbacks, - IEnumerable, DefaultKeyResolution>> resolveDefaultKeyPolicyReturnValues, - KeyManagementOptions keyManagementOptions = null) - { - var getCacheExpirationTokenReturnValuesEnumerator = getCacheExpirationTokenReturnValues.GetEnumerator(); - var mockKeyManager = new Mock(MockBehavior.Strict); - mockKeyManager.Setup(o => o.GetCacheExpirationToken()) - .Returns(() => - { - callSequence.Add("GetCacheExpirationToken"); - getCacheExpirationTokenReturnValuesEnumerator.MoveNext(); - return getCacheExpirationTokenReturnValuesEnumerator.Current; - }); - - var getAllKeysReturnValuesEnumerator = getAllKeysReturnValues.GetEnumerator(); - mockKeyManager.Setup(o => o.GetAllKeys()) - .Returns(() => - { - callSequence.Add("GetAllKeys"); - getAllKeysReturnValuesEnumerator.MoveNext(); - return getAllKeysReturnValuesEnumerator.Current; - }); - - if (createNewKeyCallbacks != null) - { - var createNewKeyCallbacksEnumerator = createNewKeyCallbacks.GetEnumerator(); - mockKeyManager.Setup(o => o.CreateNewKey(It.IsAny(), It.IsAny())) - .Returns((activationDate, expirationDate) => - { - callSequence.Add("CreateNewKey"); - createNewKeyCallbacksEnumerator.MoveNext(); - Assert.Equal(createNewKeyCallbacksEnumerator.Current.Item1, activationDate); - Assert.Equal(createNewKeyCallbacksEnumerator.Current.Item2, expirationDate); - return createNewKeyCallbacksEnumerator.Current.Item3; - }); - } - - var resolveDefaultKeyPolicyReturnValuesEnumerator = resolveDefaultKeyPolicyReturnValues.GetEnumerator(); - var mockDefaultKeyResolver = new Mock(MockBehavior.Strict); - mockDefaultKeyResolver.Setup(o => o.ResolveDefaultKeyPolicy(It.IsAny(), It.IsAny>())) - .Returns>((now, allKeys) => - { - callSequence.Add("ResolveDefaultKeyPolicy"); - resolveDefaultKeyPolicyReturnValuesEnumerator.MoveNext(); - Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item1, now); - Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item2, allKeys); - return resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item3; - }); - - return CreateKeyRingProvider(mockKeyManager.Object, mockDefaultKeyResolver.Object, keyManagementOptions); - } - [Fact] public void GetCurrentKeyRing_NoKeyRingCached_CachesAndReturns() { @@ -586,24 +531,90 @@ public void GetCurrentKeyRing_WithExpiredExistingKeyRing_UpdateFails_ThrowsButCa mockCacheableKeyRingProvider.Verify(o => o.GetCacheableKeyRing(updatedKeyRingTime), Times.Once); } + private static ICacheableKeyRingProvider SetupCreateCacheableKeyRingTestAndCreateKeyManager( + IList callSequence, + IEnumerable getCacheExpirationTokenReturnValues, + IEnumerable> getAllKeysReturnValues, + IEnumerable> createNewKeyCallbacks, + IEnumerable, DefaultKeyResolution>> resolveDefaultKeyPolicyReturnValues, + KeyManagementOptions keyManagementOptions = null) + { + var getCacheExpirationTokenReturnValuesEnumerator = getCacheExpirationTokenReturnValues.GetEnumerator(); + var mockKeyManager = new Mock(MockBehavior.Strict); + mockKeyManager.Setup(o => o.GetCacheExpirationToken()) + .Returns(() => + { + callSequence.Add("GetCacheExpirationToken"); + getCacheExpirationTokenReturnValuesEnumerator.MoveNext(); + return getCacheExpirationTokenReturnValuesEnumerator.Current; + }); + + var getAllKeysReturnValuesEnumerator = getAllKeysReturnValues.GetEnumerator(); + mockKeyManager.Setup(o => o.GetAllKeys()) + .Returns(() => + { + callSequence.Add("GetAllKeys"); + getAllKeysReturnValuesEnumerator.MoveNext(); + return getAllKeysReturnValuesEnumerator.Current; + }); + + if (createNewKeyCallbacks != null) + { + var createNewKeyCallbacksEnumerator = createNewKeyCallbacks.GetEnumerator(); + mockKeyManager.Setup(o => o.CreateNewKey(It.IsAny(), It.IsAny())) + .Returns((activationDate, expirationDate) => + { + callSequence.Add("CreateNewKey"); + createNewKeyCallbacksEnumerator.MoveNext(); + Assert.Equal(createNewKeyCallbacksEnumerator.Current.Item1, activationDate); + Assert.Equal(createNewKeyCallbacksEnumerator.Current.Item2, expirationDate); + return createNewKeyCallbacksEnumerator.Current.Item3; + }); + } + + var resolveDefaultKeyPolicyReturnValuesEnumerator = resolveDefaultKeyPolicyReturnValues.GetEnumerator(); + var mockDefaultKeyResolver = new Mock(MockBehavior.Strict); + mockDefaultKeyResolver.Setup(o => o.ResolveDefaultKeyPolicy(It.IsAny(), It.IsAny>())) + .Returns>((now, allKeys) => + { + callSequence.Add("ResolveDefaultKeyPolicy"); + resolveDefaultKeyPolicyReturnValuesEnumerator.MoveNext(); + Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item1, now); + Assert.Equal(resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item2, allKeys); + return resolveDefaultKeyPolicyReturnValuesEnumerator.Current.Item3; + }); + + return CreateKeyRingProvider(mockKeyManager.Object, mockDefaultKeyResolver.Object, keyManagementOptions); + } + private static KeyRingProvider CreateKeyRingProvider(ICacheableKeyRingProvider cacheableKeyRingProvider) { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(cacheableKeyRingProvider); + var mockEncryptorFactory = new Mock(); + mockEncryptorFactory.Setup(m => m.CreateEncryptorInstance(It.IsAny())).Returns(new Mock().Object); + var options = new KeyManagementOptions(); + options.AuthenticatedEncryptorFactories.Add(mockEncryptorFactory.Object); + return new KeyRingProvider( keyManager: null, - keyManagementOptions: null, - services: serviceCollection.BuildServiceProvider()); + keyManagementOptions: Options.Create(options), + cacheableKeyRingProvider: cacheableKeyRingProvider, + defaultKeyResolver: null, + loggerFactory: NullLoggerFactory.Instance); } private static ICacheableKeyRingProvider CreateKeyRingProvider(IKeyManager keyManager, IDefaultKeyResolver defaultKeyResolver, KeyManagementOptions keyManagementOptions= null) { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(defaultKeyResolver); + var mockEncryptorFactory = new Mock(); + mockEncryptorFactory.Setup(m => m.CreateEncryptorInstance(It.IsAny())).Returns(new Mock().Object); + keyManagementOptions = keyManagementOptions ?? new KeyManagementOptions(); + keyManagementOptions.AuthenticatedEncryptorFactories.Add(mockEncryptorFactory.Object); + return new KeyRingProvider( keyManager: keyManager, - keyManagementOptions: keyManagementOptions, - services: serviceCollection.BuildServiceProvider()); + keyManagementOptions: Options.Create(keyManagementOptions), + cacheableKeyRingProvider: null, + defaultKeyResolver: defaultKeyResolver, + loggerFactory: NullLoggerFactory.Instance); } private static void AssertWithinJitterRange(DateTimeOffset actual, DateTimeOffset now) @@ -620,7 +631,9 @@ private static DateTime StringToDateTime(string input) private static IKey CreateKey() { var now = DateTimeOffset.Now; - return CreateKey(Invariant($"{now:u}"), Invariant($"{now.AddDays(90):u}")); + return CreateKey( + string.Format(CultureInfo.InvariantCulture, "{0:u}", now), + string.Format(CultureInfo.InvariantCulture, "{0:u}", now.AddDays(90))); } private static IKey CreateKey(string activationDate, string expirationDate, bool isRevoked = false) @@ -630,7 +643,7 @@ private static IKey CreateKey(string activationDate, string expirationDate, bool mockKey.Setup(o => o.ActivationDate).Returns(DateTimeOffset.ParseExact(activationDate, "u", CultureInfo.InvariantCulture)); mockKey.Setup(o => o.ExpirationDate).Returns(DateTimeOffset.ParseExact(expirationDate, "u", CultureInfo.InvariantCulture)); mockKey.Setup(o => o.IsRevoked).Returns(isRevoked); - mockKey.Setup(o => o.CreateEncryptorInstance()).Returns(new Mock().Object); + mockKey.Setup(o => o.Descriptor).Returns(new Mock().Object); return mockKey.Object; } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingTests.cs index f973af9c..915b4704 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyRingTests.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Moq; using Xunit; @@ -15,19 +16,20 @@ public void DefaultAuthenticatedEncryptor_Prop_InstantiationIsDeferred() { // Arrange var expectedEncryptorInstance = new Mock().Object; + var encryptorFactory = new MyEncryptorFactory(expectedEncryptorInstance); - var key1 = new MyKey(expectedEncryptorInstance: expectedEncryptorInstance); + var key1 = new MyKey(); var key2 = new MyKey(); // Act - var keyRing = new KeyRing(key1, new[] { key1, key2 }); + var keyRing = new KeyRing(key1, new[] { key1, key2 }, new[] { encryptorFactory }); // Assert - Assert.Equal(0, key1.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(0, encryptorFactory.NumTimesCreateEncryptorInstanceCalled); Assert.Same(expectedEncryptorInstance, keyRing.DefaultAuthenticatedEncryptor); - Assert.Equal(1, key1.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(1, encryptorFactory.NumTimesCreateEncryptorInstanceCalled); Assert.Same(expectedEncryptorInstance, keyRing.DefaultAuthenticatedEncryptor); - Assert.Equal(1, key1.NumTimesCreateEncryptorInstanceCalled); // should've been cached + Assert.Equal(1, encryptorFactory.NumTimesCreateEncryptorInstanceCalled); // should've been cached } [Fact] @@ -36,9 +38,10 @@ public void DefaultKeyId_Prop() // Arrange var key1 = new MyKey(); var key2 = new MyKey(); + var encryptorFactory = new MyEncryptorFactory(); // Act - var keyRing = new KeyRing(key2, new[] { key1, key2 }); + var keyRing = new KeyRing(key2, new[] { key1, key2 }, new[] { encryptorFactory }); // Assert Assert.Equal(key2.KeyId, keyRing.DefaultKeyId); @@ -50,15 +53,16 @@ public void DefaultKeyIdAndEncryptor_IfDefaultKeyNotPresentInAllKeys() // Arrange var key1 = new MyKey(); var key2 = new MyKey(); - var key3 = new MyKey(expectedEncryptorInstance: new Mock().Object); + var key3 = new MyKey(); + var encryptorFactory = new MyEncryptorFactory(expectedEncryptorInstance: new Mock().Object); // Act - var keyRing = new KeyRing(key3, new[] { key1, key2 }); + var keyRing = new KeyRing(key3, new[] { key1, key2 }, new[] { encryptorFactory }); // Assert bool unused; Assert.Equal(key3.KeyId, keyRing.DefaultKeyId); - Assert.Equal(key3.CreateEncryptorInstance(), keyRing.GetAuthenticatedEncryptorByKeyId(key3.KeyId, out unused)); + Assert.Equal(encryptorFactory.CreateEncryptorInstance(key3), keyRing.GetAuthenticatedEncryptorByKeyId(key3.KeyId, out unused)); } [Fact] @@ -68,45 +72,44 @@ public void GetAuthenticatedEncryptorByKeyId_DefersInstantiation_AndReturnsRevoc var expectedEncryptorInstance1 = new Mock().Object; var expectedEncryptorInstance2 = new Mock().Object; - var key1 = new MyKey(expectedEncryptorInstance: expectedEncryptorInstance1, isRevoked: true); - var key2 = new MyKey(expectedEncryptorInstance: expectedEncryptorInstance2); + var key1 = new MyKey(isRevoked: true); + var key2 = new MyKey(); + + var encryptorFactory1 = new MyEncryptorFactory(expectedEncryptorInstance: expectedEncryptorInstance1, associatedKey: key1); + var encryptorFactory2 = new MyEncryptorFactory(expectedEncryptorInstance: expectedEncryptorInstance2, associatedKey: key2); // Act - var keyRing = new KeyRing(key2, new[] { key1, key2 }); + var keyRing = new KeyRing(key2, new[] { key1, key2 }, new[] { encryptorFactory1, encryptorFactory2 }); // Assert bool isRevoked; - Assert.Equal(0, key1.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(0, encryptorFactory1.NumTimesCreateEncryptorInstanceCalled); Assert.Same(expectedEncryptorInstance1, keyRing.GetAuthenticatedEncryptorByKeyId(key1.KeyId, out isRevoked)); Assert.True(isRevoked); - Assert.Equal(1, key1.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(1, encryptorFactory1.NumTimesCreateEncryptorInstanceCalled); Assert.Same(expectedEncryptorInstance1, keyRing.GetAuthenticatedEncryptorByKeyId(key1.KeyId, out isRevoked)); Assert.True(isRevoked); - Assert.Equal(1, key1.NumTimesCreateEncryptorInstanceCalled); - Assert.Equal(0, key2.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(1, encryptorFactory1.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(0, encryptorFactory2.NumTimesCreateEncryptorInstanceCalled); Assert.Same(expectedEncryptorInstance2, keyRing.GetAuthenticatedEncryptorByKeyId(key2.KeyId, out isRevoked)); Assert.False(isRevoked); - Assert.Equal(1, key2.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(1, encryptorFactory2.NumTimesCreateEncryptorInstanceCalled); Assert.Same(expectedEncryptorInstance2, keyRing.GetAuthenticatedEncryptorByKeyId(key2.KeyId, out isRevoked)); Assert.False(isRevoked); - Assert.Equal(1, key2.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(1, encryptorFactory2.NumTimesCreateEncryptorInstanceCalled); Assert.Same(expectedEncryptorInstance2, keyRing.DefaultAuthenticatedEncryptor); - Assert.Equal(1, key2.NumTimesCreateEncryptorInstanceCalled); + Assert.Equal(1, encryptorFactory2.NumTimesCreateEncryptorInstanceCalled); } private sealed class MyKey : IKey { - public int NumTimesCreateEncryptorInstanceCalled; - private readonly Func _encryptorFactory; - - public MyKey(bool isRevoked = false, IAuthenticatedEncryptor expectedEncryptorInstance = null) + public MyKey(bool isRevoked = false) { CreationDate = DateTimeOffset.Now; ActivationDate = CreationDate + TimeSpan.FromHours(1); ExpirationDate = CreationDate + TimeSpan.FromDays(30); IsRevoked = isRevoked; KeyId = Guid.NewGuid(); - _encryptorFactory = () => expectedEncryptorInstance ?? new Mock().Object; } public DateTimeOffset ActivationDate { get; } @@ -114,11 +117,31 @@ public MyKey(bool isRevoked = false, IAuthenticatedEncryptor expectedEncryptorIn public DateTimeOffset ExpirationDate { get; } public bool IsRevoked { get; } public Guid KeyId { get; } + public IAuthenticatedEncryptorDescriptor Descriptor => throw new NotImplementedException(); + } + + private sealed class MyEncryptorFactory : IAuthenticatedEncryptorFactory + { + public int NumTimesCreateEncryptorInstanceCalled; + private IAuthenticatedEncryptor _expectedEncryptorInstance; + private IKey _associatedKey; - public IAuthenticatedEncryptor CreateEncryptorInstance() + public MyEncryptorFactory(IAuthenticatedEncryptor expectedEncryptorInstance = null, IKey associatedKey = null) { + _expectedEncryptorInstance = expectedEncryptorInstance; + _associatedKey = associatedKey; + } + + public IAuthenticatedEncryptor CreateEncryptorInstance(IKey key) + { + if (_associatedKey != null && key != _associatedKey) + { + return null; + } + NumTimesCreateEncryptorInstanceCalled++; - return _encryptorFactory(); + + return _expectedEncryptorInstance ?? new Mock().Object; } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyTests.cs index e42632dd..5a205373 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/KeyTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Moq; using Xunit; @@ -19,15 +18,17 @@ public void Ctor_Properties() var creationDate = DateTimeOffset.Now; var activationDate = creationDate.AddDays(2); var expirationDate = creationDate.AddDays(90); + var descriptor = Mock.Of(); // Act - var key = new Key(keyId, creationDate, activationDate, expirationDate, new Mock().Object); + var key = new Key(keyId, creationDate, activationDate, expirationDate, descriptor); // Assert Assert.Equal(keyId, key.KeyId); Assert.Equal(creationDate, key.CreationDate); Assert.Equal(activationDate, key.ActivationDate); Assert.Equal(expirationDate, key.ExpirationDate); + Assert.Same(descriptor, key.Descriptor); } [Fact] @@ -42,23 +43,5 @@ public void SetRevoked_Respected() key.SetRevoked(); Assert.True(key.IsRevoked); } - - [Fact] - public void CreateEncryptorInstance() - { - // Arrange - var expected = new Mock().Object; - var mockDescriptor = new Mock(); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(expected); - - var now = DateTimeOffset.UtcNow; - var key = new Key(Guid.Empty, now, now, now, mockDescriptor.Object); - - // Act - var actual = key.CreateEncryptorInstance(); - - // Assert - Assert.Same(expected, actual); - } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/XmlKeyManagerTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/XmlKeyManagerTests.cs index ff991bf3..231e0c7b 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/XmlKeyManagerTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/KeyManagement/XmlKeyManagerTests.cs @@ -6,14 +6,16 @@ using System.Linq; using System.Xml; using System.Xml.Linq; +using Microsoft.AspNetCore.Cryptography.Cng; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.Internal; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.AspNetCore.DataProtection.XmlEncryption; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -32,41 +34,39 @@ public class XmlKeyManagerTests public void Ctor_WithoutEncryptorOrRepository_UsesFallback() { // Arrange - var expectedEncryptor = new Mock().Object; - var expectedRepository = new Mock().Object; - var mockFallback = new Mock(); - mockFallback.Setup(o => o.GetKeyEncryptor()).Returns(expectedEncryptor); - mockFallback.Setup(o => o.GetKeyRepository()).Returns(expectedRepository); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(mockFallback.Object); - serviceCollection.AddSingleton(new Mock().Object); - var services = serviceCollection.BuildServiceProvider(); + var options = Options.Create(new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = new Mock().Object, + XmlRepository = null, + XmlEncryptor = null + }); // Act - var keyManager = new XmlKeyManager(services); + var keyManager = new XmlKeyManager(options, SimpleActivator.DefaultWithoutServices, NullLoggerFactory.Instance); // Assert - Assert.Same(expectedEncryptor, keyManager.KeyEncryptor); - Assert.Same(expectedRepository, keyManager.KeyRepository); + Assert.NotNull(keyManager.KeyRepository); + + if (OSVersionUtil.IsWindows()) + { + Assert.NotNull(keyManager.KeyEncryptor); + } } [Fact] public void Ctor_WithEncryptorButNoRepository_IgnoresFallback_FailsWithServiceNotFound() { // Arrange - var mockFallback = new Mock(); - mockFallback.Setup(o => o.GetKeyEncryptor()).Returns(new Mock().Object); - mockFallback.Setup(o => o.GetKeyRepository()).Returns(new Mock().Object); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(mockFallback.Object); - serviceCollection.AddSingleton(new Mock().Object); - serviceCollection.AddSingleton(new Mock().Object); - var services = serviceCollection.BuildServiceProvider(); + var options = Options.Create(new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = new Mock().Object, + XmlRepository = null, + XmlEncryptor = new Mock().Object + }); // Act & assert - we don't care about exception type, only exception message - Exception ex = Assert.ThrowsAny(() => new XmlKeyManager(services)); + Exception ex = Assert.ThrowsAny( + () => new XmlKeyManager(options, SimpleActivator.DefaultWithoutServices, NullLoggerFactory.Instance)); Assert.Contains("IXmlRepository", ex.Message); } @@ -79,15 +79,16 @@ public void CreateNewKey_Internal_NoEscrowOrEncryption() var expirationDate = new DateTimeOffset(2014, 03, 01, 0, 0, 0, TimeSpan.Zero); var keyId = new Guid("3d6d01fd-c0e7-44ae-82dd-013b996b4093"); - // Arrange - mocks + // Arrange XElement elementStoredInRepository = null; string friendlyNameStoredInRepository = null; var expectedAuthenticatedEncryptor = new Mock().Object; var mockDescriptor = new Mock(); mockDescriptor.Setup(o => o.ExportToXml()).Returns(new XmlSerializedDescriptorInfo(serializedDescriptor, typeof(MyDeserializer))); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(expectedAuthenticatedEncryptor); - var mockConfiguration = new Mock(); - mockConfiguration.Setup(o => o.CreateNewDescriptor()).Returns(mockDescriptor.Object); + var expectedDescriptor = mockDescriptor.Object; + var testEncryptorFactory = new TestEncryptorFactory(expectedDescriptor, expectedAuthenticatedEncryptor); + var mockConfiguration = new Mock(); + mockConfiguration.Setup(o => o.CreateNewDescriptor()).Returns(expectedDescriptor); var mockXmlRepository = new Mock(); mockXmlRepository .Setup(o => o.StoreElement(It.IsAny(), It.IsAny())) @@ -96,13 +97,15 @@ public void CreateNewKey_Internal_NoEscrowOrEncryption() elementStoredInRepository = el; friendlyNameStoredInRepository = friendlyName; }); + var options = Options.Create(new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = mockConfiguration.Object, + XmlRepository = mockXmlRepository.Object, + XmlEncryptor = null + }); + options.Value.AuthenticatedEncryptorFactories.Add(testEncryptorFactory); - // Arrange - services - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(mockXmlRepository.Object); - serviceCollection.AddSingleton(mockConfiguration.Object); - var services = serviceCollection.BuildServiceProvider(); - var keyManager = new XmlKeyManager(services); + var keyManager = new XmlKeyManager(options, SimpleActivator.DefaultWithoutServices, NullLoggerFactory.Instance); // Act & assert @@ -126,11 +129,12 @@ public void CreateNewKey_Internal_NoEscrowOrEncryption() Assert.Equal(creationDate, newKey.CreationDate); Assert.Equal(activationDate, newKey.ActivationDate); Assert.Equal(expirationDate, newKey.ExpirationDate); + Assert.Same(expectedDescriptor, newKey.Descriptor); Assert.False(newKey.IsRevoked); - Assert.Same(expectedAuthenticatedEncryptor, newKey.CreateEncryptorInstance()); + Assert.Same(expectedAuthenticatedEncryptor, testEncryptorFactory.CreateEncryptorInstance(newKey)); // Finally, was the correct element stored in the repository? - string expectedXml = String.Format(@" + string expectedXml = string.Format(@" {1} {2} @@ -160,7 +164,7 @@ public void CreateNewKey_Internal_WithEscrowAndEncryption() var expirationDate = new DateTimeOffset(2014, 03, 01, 0, 0, 0, TimeSpan.Zero); var keyId = new Guid("3d6d01fd-c0e7-44ae-82dd-013b996b4093"); - // Arrange - mocks + // Arrange XElement elementStoredInEscrow = null; Guid? keyIdStoredInEscrow = null; XElement elementStoredInRepository = null; @@ -168,9 +172,10 @@ public void CreateNewKey_Internal_WithEscrowAndEncryption() var expectedAuthenticatedEncryptor = new Mock().Object; var mockDescriptor = new Mock(); mockDescriptor.Setup(o => o.ExportToXml()).Returns(new XmlSerializedDescriptorInfo(serializedDescriptor, typeof(MyDeserializer))); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(expectedAuthenticatedEncryptor); - var mockConfiguration = new Mock(); - mockConfiguration.Setup(o => o.CreateNewDescriptor()).Returns(mockDescriptor.Object); + var expectedDescriptor = mockDescriptor.Object; + var testEncryptorFactory = new TestEncryptorFactory(expectedDescriptor, expectedAuthenticatedEncryptor); + var mockConfiguration = new Mock(); + mockConfiguration.Setup(o => o.CreateNewDescriptor()).Returns(expectedDescriptor); var mockXmlRepository = new Mock(); mockXmlRepository .Setup(o => o.StoreElement(It.IsAny(), It.IsAny())) @@ -188,14 +193,15 @@ public void CreateNewKey_Internal_WithEscrowAndEncryption() elementStoredInEscrow = el; }); - // Arrange - services - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(mockXmlRepository.Object); - serviceCollection.AddSingleton(mockConfiguration.Object); - serviceCollection.AddSingleton(mockKeyEscrow.Object); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); - var keyManager = new XmlKeyManager(services); + var options = Options.Create(new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = mockConfiguration.Object, + XmlRepository = mockXmlRepository.Object, + XmlEncryptor = new NullXmlEncryptor() + }); + options.Value.AuthenticatedEncryptorFactories.Add(testEncryptorFactory); + options.Value.KeyEscrowSinks.Add(mockKeyEscrow.Object); + var keyManager = new XmlKeyManager(options, SimpleActivator.DefaultWithoutServices, NullLoggerFactory.Instance); // Act & assert @@ -219,12 +225,13 @@ public void CreateNewKey_Internal_WithEscrowAndEncryption() Assert.Equal(creationDate, newKey.CreationDate); Assert.Equal(activationDate, newKey.ActivationDate); Assert.Equal(expirationDate, newKey.ExpirationDate); + Assert.Same(expectedDescriptor, newKey.Descriptor); Assert.False(newKey.IsRevoked); - Assert.Same(expectedAuthenticatedEncryptor, newKey.CreateEncryptorInstance()); + Assert.Same(expectedAuthenticatedEncryptor, testEncryptorFactory.CreateEncryptorInstance(newKey)); // Was the correct element stored in escrow? // This should not have gone through the encryptor. - string expectedEscrowXml = String.Format(@" + string expectedEscrowXml = string.Format(@" {1} {2} @@ -275,7 +282,7 @@ public void CreateNewKey_Internal_WithEscrowAndEncryption() [Fact] public void CreateNewKey_CallsInternalManager() { - // Arrange - mocks + // Arrange DateTimeOffset minCreationDate = DateTimeOffset.UtcNow; DateTimeOffset? actualCreationDate = null; DateTimeOffset activationDate = minCreationDate + TimeSpan.FromDays(7); @@ -288,13 +295,13 @@ public void CreateNewKey_CallsInternalManager() actualCreationDate = innerCreationDate; }); - // Arrange - services - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(new Mock().Object); - serviceCollection.AddSingleton(new Mock().Object); - serviceCollection.AddSingleton(mockInternalKeyManager.Object); - var services = serviceCollection.BuildServiceProvider(); - var keyManager = new XmlKeyManager(services); + var options = Options.Create(new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = new Mock().Object, + XmlRepository = new Mock().Object, + XmlEncryptor = null + }); + var keyManager = new XmlKeyManager(options, SimpleActivator.DefaultWithoutServices, NullLoggerFactory.Instance, mockInternalKeyManager.Object); // Act keyManager.CreateNewKey(activationDate, expirationDate); @@ -344,11 +351,11 @@ public void GetAllKeys_IgnoresUnknownElements() "; - var encryptorA = new Mock().Object; - var encryptorB = new Mock().Object; + var descriptorA = new Mock().Object; + var descriptorB = new Mock().Object; var mockActivator = new Mock(); - mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("deserializer-A", "", encryptorA); - mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("deserializer-B", "", encryptorB); + mockActivator.ReturnDescriptorGivenDeserializerTypeNameAndInput("deserializer-A", "", descriptorA); + mockActivator.ReturnDescriptorGivenDeserializerTypeNameAndInput("deserializer-B", "", descriptorB); // Act var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray(); @@ -360,13 +367,13 @@ public void GetAllKeys_IgnoresUnknownElements() Assert.Equal(XmlConvert.ToDateTimeOffset("2015-02-01T00:00:00Z"), keys[0].ActivationDate); Assert.Equal(XmlConvert.ToDateTimeOffset("2015-03-01T00:00:00Z"), keys[0].ExpirationDate); Assert.False(keys[0].IsRevoked); - Assert.Same(encryptorA, keys[0].CreateEncryptorInstance()); + Assert.Same(descriptorA, keys[0].Descriptor); Assert.Equal(new Guid("041be4c0-52d7-48b4-8d32-f8c0ff315459"), keys[1].KeyId); Assert.Equal(XmlConvert.ToDateTimeOffset("2015-04-01T00:00:00Z"), keys[1].CreationDate); Assert.Equal(XmlConvert.ToDateTimeOffset("2015-05-01T00:00:00Z"), keys[1].ActivationDate); Assert.Equal(XmlConvert.ToDateTimeOffset("2015-06-01T00:00:00Z"), keys[1].ExpirationDate); Assert.False(keys[1].IsRevoked); - Assert.Same(encryptorB, keys[1].CreateEncryptorInstance()); + Assert.Same(descriptorB, keys[1].Descriptor); } [Fact] @@ -425,7 +432,7 @@ public void GetAllKeys_UnderstandsRevocations() "; var mockActivator = new Mock(); - mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("theDeserializer", "", new Mock().Object); + mockActivator.ReturnDescriptorGivenDeserializerTypeNameAndInput("theDeserializer", "", new Mock().Object); // Act var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray(); @@ -460,10 +467,10 @@ public void GetAllKeys_PerformsDecryption() "; - var expectedEncryptor = new Mock().Object; + var expectedDescriptor = new Mock().Object; var mockActivator = new Mock(); mockActivator.ReturnDecryptedElementGivenDecryptorTypeNameAndInput("theDecryptor", "", ""); - mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("theDeserializer", "", expectedEncryptor); + mockActivator.ReturnDescriptorGivenDeserializerTypeNameAndInput("theDeserializer", "", expectedDescriptor); // Act var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray(); @@ -471,7 +478,7 @@ public void GetAllKeys_PerformsDecryption() // Assert Assert.Equal(1, keys.Length); Assert.Equal(new Guid("09712588-ba68-438a-a5ee-fe842b3453b2"), keys[0].KeyId); - Assert.Same(expectedEncryptor, keys[0].CreateEncryptorInstance()); + Assert.Same(expectedDescriptor, keys[0].Descriptor); } [Fact] @@ -500,9 +507,9 @@ public void GetAllKeys_SwallowsKeyDeserializationErrors() "; - var expectedEncryptor = new Mock().Object; + var expectedDescriptor = new Mock().Object; var mockActivator = new Mock(); - mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("goodDeserializer", "", expectedEncryptor); + mockActivator.ReturnDescriptorGivenDeserializerTypeNameAndInput("goodDeserializer", "", expectedDescriptor); // Act var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray(); @@ -510,7 +517,7 @@ public void GetAllKeys_SwallowsKeyDeserializationErrors() // Assert Assert.Equal(1, keys.Length); Assert.Equal(new Guid("49c0cda9-0232-4d8c-a541-de20cc5a73d6"), keys[0].KeyId); - Assert.Same(expectedEncryptor, keys[0].CreateEncryptorInstance()); + Assert.Same(expectedDescriptor, keys[0].Descriptor); } [Fact] @@ -580,21 +587,16 @@ public void GetAllKeys_SurfacesRevocationDeserializationErrors() private static IReadOnlyCollection RunGetAllKeysCore(string xml, IActivator activator, ILoggerFactory loggerFactory = null) { - // Arrange - mocks + // Arrange var mockXmlRepository = new Mock(); mockXmlRepository.Setup(o => o.GetAllElements()).Returns(XElement.Parse(xml).Elements().ToArray()); - - // Arrange - services - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(mockXmlRepository.Object); - serviceCollection.AddSingleton(activator); - serviceCollection.AddSingleton(new Mock().Object); - if (loggerFactory != null) + var options = Options.Create(new KeyManagementOptions() { - serviceCollection.AddSingleton(loggerFactory); - } - var services = serviceCollection.BuildServiceProvider(); - var keyManager = new XmlKeyManager(services); + AuthenticatedEncryptorConfiguration = new Mock().Object, + XmlRepository = mockXmlRepository.Object, + XmlEncryptor = null + }); + var keyManager = new XmlKeyManager(options, activator, loggerFactory ?? NullLoggerFactory.Instance); // Act return keyManager.GetAllKeys(); @@ -603,7 +605,7 @@ private static IReadOnlyCollection RunGetAllKeysCore(string xml, IActivato [Fact] public void RevokeAllKeys() { - // Arrange - mocks + // Arrange XElement elementStoredInRepository = null; string friendlyNameStoredInRepository = null; var mockXmlRepository = new Mock(); @@ -615,12 +617,13 @@ public void RevokeAllKeys() friendlyNameStoredInRepository = friendlyName; }); - // Arrange - services - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(mockXmlRepository.Object); - serviceCollection.AddSingleton(new Mock().Object); - var services = serviceCollection.BuildServiceProvider(); - var keyManager = new XmlKeyManager(services); + var options = Options.Create(new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = new Mock().Object, + XmlRepository = mockXmlRepository.Object, + XmlEncryptor = null + }); + var keyManager = new XmlKeyManager(options, SimpleActivator.DefaultWithoutServices, NullLoggerFactory.Instance); var revocationDate = XmlConvert.ToDateTimeOffset("2015-03-01T19:13:19.7573854-08:00"); @@ -664,12 +667,13 @@ public void RevokeSingleKey_Internal() friendlyNameStoredInRepository = friendlyName; }); - // Arrange - services - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(mockXmlRepository.Object); - serviceCollection.AddSingleton(new Mock().Object); - var services = serviceCollection.BuildServiceProvider(); - var keyManager = new XmlKeyManager(services); + var options = Options.Create(new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = new Mock().Object, + XmlRepository = mockXmlRepository.Object, + XmlEncryptor = null + }); + var keyManager = new XmlKeyManager(options, SimpleActivator.DefaultWithoutServices, NullLoggerFactory.Instance); var revocationDate = new DateTimeOffset(2014, 01, 01, 0, 0, 0, TimeSpan.Zero); @@ -704,7 +708,7 @@ public void RevokeSingleKey_Internal() [Fact] public void RevokeKey_CallsInternalManager() { - // Arrange - mocks + // Arrange var keyToRevoke = new Guid("a11f35fc-1fed-4bd4-b727-056a63b70932"); DateTimeOffset minRevocationDate = DateTimeOffset.UtcNow; DateTimeOffset? actualRevocationDate = null; @@ -716,13 +720,13 @@ public void RevokeKey_CallsInternalManager() actualRevocationDate = innerRevocationDate; }); - // Arrange - services - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(new Mock().Object); - serviceCollection.AddSingleton(new Mock().Object); - serviceCollection.AddSingleton(mockInternalKeyManager.Object); - var services = serviceCollection.BuildServiceProvider(); - var keyManager = new XmlKeyManager(services); + var options = Options.Create(new KeyManagementOptions() + { + AuthenticatedEncryptorConfiguration = new Mock().Object, + XmlRepository = new Mock().Object, + XmlEncryptor = null + }); + var keyManager = new XmlKeyManager(options, SimpleActivator.DefaultWithoutServices, NullLoggerFactory.Instance, mockInternalKeyManager.Object); // Act keyManager.RevokeKey(keyToRevoke, "Here's some reason text."); @@ -738,5 +742,27 @@ public IAuthenticatedEncryptorDescriptor ImportFromXml(XElement element) throw new NotImplementedException(); } } + + private class TestEncryptorFactory : IAuthenticatedEncryptorFactory + { + private IAuthenticatedEncryptorDescriptor _associatedDescriptor; + private IAuthenticatedEncryptor _expectedEncryptor; + + public TestEncryptorFactory(IAuthenticatedEncryptorDescriptor associatedDescriptor = null, IAuthenticatedEncryptor expectedEncryptor = null) + { + _associatedDescriptor = associatedDescriptor; + _expectedEncryptor = expectedEncryptor; + } + + public IAuthenticatedEncryptor CreateEncryptorInstance(IKey key) + { + if (_associatedDescriptor != null && _associatedDescriptor != key.Descriptor) + { + return null; + } + + return _expectedEncryptor ?? new Mock().Object; + } + } } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/Microsoft.AspNetCore.DataProtection.Test.csproj b/test/Microsoft.AspNetCore.DataProtection.Test/Microsoft.AspNetCore.DataProtection.Test.csproj index a8bcd60f..fbf67448 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/Microsoft.AspNetCore.DataProtection.Test.csproj +++ b/test/Microsoft.AspNetCore.DataProtection.Test/Microsoft.AspNetCore.DataProtection.Test.csproj @@ -26,4 +26,8 @@ + + + + diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/MockExtensions.cs b/test/Microsoft.AspNetCore.DataProtection.Test/MockExtensions.cs index 40a34afc..76f5dc94 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/MockExtensions.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/MockExtensions.cs @@ -17,7 +17,7 @@ internal static class MockExtensions /// Sets up a mock such that given the name of a deserializer class and the XML node that class's /// Import method should expect returns a descriptor which produces the given authenticator. /// - public static void ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput(this Mock mockActivator, string typeName, string xml, IAuthenticatedEncryptor encryptor) + public static void ReturnDescriptorGivenDeserializerTypeNameAndInput(this Mock mockActivator, string typeName, string xml, IAuthenticatedEncryptorDescriptor descriptor) { mockActivator .Setup(o => o.CreateInstance(typeof(IAuthenticatedEncryptorDescriptorDeserializer), typeName)) @@ -30,9 +30,7 @@ public static void ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput { // Only return the descriptor if the XML matches XmlAssert.Equal(xml, el); - var mockDescriptor = new Mock(); - mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(encryptor); - return mockDescriptor.Object; + return descriptor; }); return mockDeserializer.Object; }); diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/RegistryPolicyResolverTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/RegistryPolicyResolverTests.cs index 1bf706dd..3ce7ee9f 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/RegistryPolicyResolverTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/RegistryPolicyResolverTests.cs @@ -7,12 +7,14 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Xml.Linq; -using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using Microsoft.AspNetCore.DataProtection.Internal; using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Win32; using Xunit; @@ -25,27 +27,33 @@ public class RegistryPolicyResolverTests [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_NoEntries_ResultsInNoPolicies() { - IServiceCollection serviceCollection = new ServiceCollection(); - RunTestWithRegValues(serviceCollection, new Dictionary() - { - ["unused"] = 42 - }); + // Arrange + var registryEntries = new Dictionary(); - Assert.Empty(serviceCollection); + // Act + var context = RunTestWithRegValues(registryEntries); + + // Assert + Assert.Null(context.EncryptorConfiguration); + Assert.Null(context.DefaultKeyLifetime); + Assert.Empty(context.KeyEscrowSinks); } [ConditionalFact] [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_KeyEscrowSinks() { - IServiceCollection serviceCollection = new ServiceCollection(); - RunTestWithRegValues(serviceCollection, new Dictionary() + // Arrange + var registryEntries = new Dictionary() { ["KeyEscrowSinks"] = String.Join(" ;; ; ", new Type[] { typeof(MyKeyEscrowSink1), typeof(MyKeyEscrowSink2) }.Select(t => t.AssemblyQualifiedName)) - }); + }; - var services = serviceCollection.BuildServiceProvider(); - var actualKeyEscrowSinks = services.GetService>().ToArray(); + // Act + var context = RunTestWithRegValues(registryEntries); + + // Assert + var actualKeyEscrowSinks = context.KeyEscrowSinks.ToArray(); Assert.Equal(2, actualKeyEscrowSinks.Length); Assert.IsType(typeof(MyKeyEscrowSink1), actualKeyEscrowSinks[0]); Assert.IsType(typeof(MyKeyEscrowSink2), actualKeyEscrowSinks[1]); @@ -55,45 +63,49 @@ public void ResolvePolicy_KeyEscrowSinks() [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_DefaultKeyLifetime() { - IServiceCollection serviceCollection = new ServiceCollection(); - serviceCollection.AddOptions(); - RunTestWithRegValues(serviceCollection, new Dictionary() + // Arrange + var registryEntries = new Dictionary() { ["DefaultKeyLifetime"] = 1024 // days - }); + }; - var services = serviceCollection.BuildServiceProvider(); - var keyManagementOptions = services.GetService>(); - Assert.Equal(TimeSpan.FromDays(1024), keyManagementOptions.Value.NewKeyLifetime); + // Act + var context = RunTestWithRegValues(registryEntries); + + // Assert + Assert.Equal(1024, context.DefaultKeyLifetime); } [ConditionalFact] [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_CngCbcEncryption_WithoutExplicitSettings() { - IServiceCollection serviceCollection = new ServiceCollection(); - RunTestWithRegValues(serviceCollection, new Dictionary() + // Arrange + var registryEntries = new Dictionary() { ["EncryptionType"] = "cng-cbc" - }); + }; + var expectedConfiguration = new CngCbcAuthenticatedEncryptorConfiguration(); - var services = serviceCollection.BuildServiceProvider(); - var expectedConfiguration = new CngCbcAuthenticatedEncryptorConfiguration(new CngCbcAuthenticatedEncryptionSettings()); - var actualConfiguration = (CngCbcAuthenticatedEncryptorConfiguration)services.GetService(); + // Act + var context = RunTestWithRegValues(registryEntries); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithm, actualConfiguration.Settings.EncryptionAlgorithm); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmKeySize, actualConfiguration.Settings.EncryptionAlgorithmKeySize); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmProvider, actualConfiguration.Settings.EncryptionAlgorithmProvider); - Assert.Equal(expectedConfiguration.Settings.HashAlgorithm, actualConfiguration.Settings.HashAlgorithm); - Assert.Equal(expectedConfiguration.Settings.HashAlgorithmProvider, actualConfiguration.Settings.HashAlgorithmProvider); + // Assert + var actualConfiguration = (CngCbcAuthenticatedEncryptorConfiguration)context.EncryptorConfiguration; + + Assert.Equal(expectedConfiguration.EncryptionAlgorithm, actualConfiguration.EncryptionAlgorithm); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmKeySize, actualConfiguration.EncryptionAlgorithmKeySize); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmProvider, actualConfiguration.EncryptionAlgorithmProvider); + Assert.Equal(expectedConfiguration.HashAlgorithm, actualConfiguration.HashAlgorithm); + Assert.Equal(expectedConfiguration.HashAlgorithmProvider, actualConfiguration.HashAlgorithmProvider); } [ConditionalFact] [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_CngCbcEncryption_WithExplicitSettings() { - IServiceCollection serviceCollection = new ServiceCollection(); - RunTestWithRegValues(serviceCollection, new Dictionary() + // Arrange + var registryEntries = new Dictionary() { ["EncryptionType"] = "cng-cbc", ["EncryptionAlgorithm"] = "enc-alg", @@ -101,142 +113,161 @@ public void ResolvePolicy_CngCbcEncryption_WithExplicitSettings() ["EncryptionAlgorithmProvider"] = "my-enc-alg-provider", ["HashAlgorithm"] = "hash-alg", ["HashAlgorithmProvider"] = "my-hash-alg-provider" - }); - - var services = serviceCollection.BuildServiceProvider(); - var expectedConfiguration = new CngCbcAuthenticatedEncryptorConfiguration(new CngCbcAuthenticatedEncryptionSettings() + }; + var expectedConfiguration = new CngCbcAuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = "enc-alg", EncryptionAlgorithmKeySize = 2048, EncryptionAlgorithmProvider = "my-enc-alg-provider", HashAlgorithm = "hash-alg", HashAlgorithmProvider = "my-hash-alg-provider" - }); - var actualConfiguration = (CngCbcAuthenticatedEncryptorConfiguration)services.GetService(); + }; - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithm, actualConfiguration.Settings.EncryptionAlgorithm); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmKeySize, actualConfiguration.Settings.EncryptionAlgorithmKeySize); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmProvider, actualConfiguration.Settings.EncryptionAlgorithmProvider); - Assert.Equal(expectedConfiguration.Settings.HashAlgorithm, actualConfiguration.Settings.HashAlgorithm); - Assert.Equal(expectedConfiguration.Settings.HashAlgorithmProvider, actualConfiguration.Settings.HashAlgorithmProvider); + // Act + var context = RunTestWithRegValues(registryEntries); + + // Assert + var actualConfiguration = (CngCbcAuthenticatedEncryptorConfiguration)context.EncryptorConfiguration; + + Assert.Equal(expectedConfiguration.EncryptionAlgorithm, actualConfiguration.EncryptionAlgorithm); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmKeySize, actualConfiguration.EncryptionAlgorithmKeySize); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmProvider, actualConfiguration.EncryptionAlgorithmProvider); + Assert.Equal(expectedConfiguration.HashAlgorithm, actualConfiguration.HashAlgorithm); + Assert.Equal(expectedConfiguration.HashAlgorithmProvider, actualConfiguration.HashAlgorithmProvider); } [ConditionalFact] [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_CngGcmEncryption_WithoutExplicitSettings() { - IServiceCollection serviceCollection = new ServiceCollection(); - RunTestWithRegValues(serviceCollection, new Dictionary() + // Arrange + var registryEntries = new Dictionary() { ["EncryptionType"] = "cng-gcm" - }); + }; + var expectedConfiguration = new CngGcmAuthenticatedEncryptorConfiguration(); - var services = serviceCollection.BuildServiceProvider(); - var expectedConfiguration = new CngGcmAuthenticatedEncryptorConfiguration(new CngGcmAuthenticatedEncryptionSettings()); - var actualConfiguration = (CngGcmAuthenticatedEncryptorConfiguration)services.GetService(); + // Act + var context = RunTestWithRegValues(registryEntries); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithm, actualConfiguration.Settings.EncryptionAlgorithm); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmKeySize, actualConfiguration.Settings.EncryptionAlgorithmKeySize); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmProvider, actualConfiguration.Settings.EncryptionAlgorithmProvider); + // Assert + var actualConfiguration = (CngGcmAuthenticatedEncryptorConfiguration)context.EncryptorConfiguration; + + Assert.Equal(expectedConfiguration.EncryptionAlgorithm, actualConfiguration.EncryptionAlgorithm); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmKeySize, actualConfiguration.EncryptionAlgorithmKeySize); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmProvider, actualConfiguration.EncryptionAlgorithmProvider); } [ConditionalFact] [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_CngGcmEncryption_WithExplicitSettings() { - IServiceCollection serviceCollection = new ServiceCollection(); - RunTestWithRegValues(serviceCollection, new Dictionary() + // Arrange + var registryEntries = new Dictionary() { ["EncryptionType"] = "cng-gcm", ["EncryptionAlgorithm"] = "enc-alg", ["EncryptionAlgorithmKeySize"] = 2048, ["EncryptionAlgorithmProvider"] = "my-enc-alg-provider" - }); - - var services = serviceCollection.BuildServiceProvider(); - var expectedConfiguration = new CngGcmAuthenticatedEncryptorConfiguration(new CngGcmAuthenticatedEncryptionSettings() + }; + var expectedConfiguration = new CngGcmAuthenticatedEncryptorConfiguration() { EncryptionAlgorithm = "enc-alg", EncryptionAlgorithmKeySize = 2048, EncryptionAlgorithmProvider = "my-enc-alg-provider" - }); - var actualConfiguration = (CngGcmAuthenticatedEncryptorConfiguration)services.GetService(); + }; - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithm, actualConfiguration.Settings.EncryptionAlgorithm); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmKeySize, actualConfiguration.Settings.EncryptionAlgorithmKeySize); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmProvider, actualConfiguration.Settings.EncryptionAlgorithmProvider); + // Act + var context = RunTestWithRegValues(registryEntries); + + // Assert + var actualConfiguration = (CngGcmAuthenticatedEncryptorConfiguration)context.EncryptorConfiguration; + + Assert.Equal(expectedConfiguration.EncryptionAlgorithm, actualConfiguration.EncryptionAlgorithm); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmKeySize, actualConfiguration.EncryptionAlgorithmKeySize); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmProvider, actualConfiguration.EncryptionAlgorithmProvider); } [ConditionalFact] [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_ManagedEncryption_WithoutExplicitSettings() { - IServiceCollection serviceCollection = new ServiceCollection(); - RunTestWithRegValues(serviceCollection, new Dictionary() + // Arrange + var registryEntries = new Dictionary() { ["EncryptionType"] = "managed" - }); + }; + var expectedConfiguration = new ManagedAuthenticatedEncryptorConfiguration(); - var services = serviceCollection.BuildServiceProvider(); - var expectedConfiguration = new ManagedAuthenticatedEncryptorConfiguration(new ManagedAuthenticatedEncryptionSettings()); - var actualConfiguration = (ManagedAuthenticatedEncryptorConfiguration)services.GetService(); + // Act + var context = RunTestWithRegValues(registryEntries); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmType, actualConfiguration.Settings.EncryptionAlgorithmType); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmKeySize, actualConfiguration.Settings.EncryptionAlgorithmKeySize); - Assert.Equal(expectedConfiguration.Settings.ValidationAlgorithmType, actualConfiguration.Settings.ValidationAlgorithmType); + // Assert + var actualConfiguration = (ManagedAuthenticatedEncryptorConfiguration)context.EncryptorConfiguration; + + Assert.Equal(expectedConfiguration.EncryptionAlgorithmType, actualConfiguration.EncryptionAlgorithmType); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmKeySize, actualConfiguration.EncryptionAlgorithmKeySize); + Assert.Equal(expectedConfiguration.ValidationAlgorithmType, actualConfiguration.ValidationAlgorithmType); } [ConditionalFact] [ConditionalRunTestOnlyIfHkcuRegistryAvailable] public void ResolvePolicy_ManagedEncryption_WithExplicitSettings() { - IServiceCollection serviceCollection = new ServiceCollection(); - RunTestWithRegValues(serviceCollection, new Dictionary() + // Arrange + var registryEntries = new Dictionary() { ["EncryptionType"] = "managed", ["EncryptionAlgorithmType"] = typeof(TripleDES).AssemblyQualifiedName, ["EncryptionAlgorithmKeySize"] = 2048, ["ValidationAlgorithmType"] = typeof(HMACSHA1).AssemblyQualifiedName - }); - - var services = serviceCollection.BuildServiceProvider(); - var expectedConfiguration = new ManagedAuthenticatedEncryptorConfiguration(new ManagedAuthenticatedEncryptionSettings() + }; + var expectedConfiguration = new ManagedAuthenticatedEncryptorConfiguration() { EncryptionAlgorithmType = typeof(TripleDES), EncryptionAlgorithmKeySize = 2048, ValidationAlgorithmType = typeof(HMACSHA1) - }); - var actualConfiguration = (ManagedAuthenticatedEncryptorConfiguration)services.GetService(); + }; - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmType, actualConfiguration.Settings.EncryptionAlgorithmType); - Assert.Equal(expectedConfiguration.Settings.EncryptionAlgorithmKeySize, actualConfiguration.Settings.EncryptionAlgorithmKeySize); - Assert.Equal(expectedConfiguration.Settings.ValidationAlgorithmType, actualConfiguration.Settings.ValidationAlgorithmType); + // Act + var context = RunTestWithRegValues(registryEntries); + + // Assert + var actualConfiguration = (ManagedAuthenticatedEncryptorConfiguration)context.EncryptorConfiguration; + + Assert.Equal(expectedConfiguration.EncryptionAlgorithmType, actualConfiguration.EncryptionAlgorithmType); + Assert.Equal(expectedConfiguration.EncryptionAlgorithmKeySize, actualConfiguration.EncryptionAlgorithmKeySize); + Assert.Equal(expectedConfiguration.ValidationAlgorithmType, actualConfiguration.ValidationAlgorithmType); } - private static void RunTestWithRegValues(IServiceCollection services, Dictionary regValues) + private static RegistryPolicy RunTestWithRegValues(Dictionary regValues) { - WithUniqueTempRegKey(registryKey => + return WithUniqueTempRegKey(registryKey => { foreach (var entry in regValues) { registryKey.SetValue(entry.Key, entry.Value); } - var policyResolver = new RegistryPolicyResolver(registryKey); - services.Add(policyResolver.ResolvePolicy()); + var policyResolver = new RegistryPolicyResolver( + registryKey, + activator: SimpleActivator.DefaultWithoutServices, + loggerFactory: NullLoggerFactory.Instance); + + return policyResolver.ResolvePolicy(); }); } /// /// Runs a test and cleans up the registry key afterward. /// - private static void WithUniqueTempRegKey(Action testCode) + private static RegistryPolicy WithUniqueTempRegKey(Func testCode) { string uniqueName = Guid.NewGuid().ToString(); var uniqueSubkey = LazyHkcuTempKey.Value.CreateSubKey(uniqueName); try { - testCode(uniqueSubkey); + return testCode(uniqueSubkey); } finally { diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/EphemeralXmlRepositoryTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/EphemeralXmlRepositoryTests.cs index 2690da82..b9032674 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/EphemeralXmlRepositoryTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/EphemeralXmlRepositoryTests.cs @@ -3,6 +3,7 @@ using System; using System.Xml.Linq; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.Repositories @@ -13,7 +14,7 @@ public class EphemeralXmlRepositoryTests public void GetAllElements_Empty() { // Arrange - var repository = new EphemeralXmlRepository(null); + var repository = new EphemeralXmlRepository(NullLoggerFactory.Instance); // Act & assert Assert.Empty(repository.GetAllElements()); @@ -26,7 +27,7 @@ public void Store_Then_Get() var element1 = XElement.Parse(@""); var element2 = XElement.Parse(@""); var element3 = XElement.Parse(@""); - var repository = new EphemeralXmlRepository(null); + var repository = new EphemeralXmlRepository(NullLoggerFactory.Instance); // Act & assert repository.StoreElement(element1, "Invalid friendly name."); // nobody should care about the friendly name diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/FileSystemXmlRepositoryTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/FileSystemXmlRepositoryTests.cs index 37b603f1..28cb78c8 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/FileSystemXmlRepositoryTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/FileSystemXmlRepositoryTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Xml.Linq; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.Repositories @@ -30,7 +31,7 @@ public void Directory_Property() WithUniqueTempDirectory(dirInfo => { // Arrange - var repository = new FileSystemXmlRepository(dirInfo); + var repository = new FileSystemXmlRepository(dirInfo, NullLoggerFactory.Instance); // Act var retVal = repository.Directory; @@ -46,7 +47,7 @@ public void GetAllElements_EmptyOrNonexistentDirectory_ReturnsEmptyCollection() WithUniqueTempDirectory(dirInfo => { // Arrange - var repository = new FileSystemXmlRepository(dirInfo); + var repository = new FileSystemXmlRepository(dirInfo, NullLoggerFactory.Instance); // Act var allElements = repository.GetAllElements(); @@ -63,7 +64,7 @@ public void StoreElement_WithValidFriendlyName_UsesFriendlyName() { // Arrange var element = XElement.Parse(""); - var repository = new FileSystemXmlRepository(dirInfo); + var repository = new FileSystemXmlRepository(dirInfo, NullLoggerFactory.Instance); // Act repository.StoreElement(element, "valid-friendly-name"); @@ -93,7 +94,7 @@ public void StoreElement_WithInvalidFriendlyName_CreatesNewGuidAsName(string fri { // Arrange var element = XElement.Parse(""); - var repository = new FileSystemXmlRepository(dirInfo); + var repository = new FileSystemXmlRepository(dirInfo, NullLoggerFactory.Instance); // Act repository.StoreElement(element, friendlyName); @@ -121,7 +122,7 @@ public void StoreElements_ThenRetrieve_SeesAllElements() WithUniqueTempDirectory(dirInfo => { // Arrange - var repository = new FileSystemXmlRepository(dirInfo); + var repository = new FileSystemXmlRepository(dirInfo, NullLoggerFactory.Instance); // Act repository.StoreElement(new XElement("element1"), friendlyName: null); diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/RegistryXmlRepositoryTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/RegistryXmlRepositoryTests.cs index 92c16a78..11f0060c 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/RegistryXmlRepositoryTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/Repositories/RegistryXmlRepositoryTests.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Xml.Linq; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Win32; using Xunit; @@ -20,7 +21,7 @@ public void RegistryKey_Property() WithUniqueTempRegKey(regKey => { // Arrange - var repository = new RegistryXmlRepository(regKey); + var repository = new RegistryXmlRepository(regKey, NullLoggerFactory.Instance); // Act var retVal = repository.RegistryKey; @@ -37,7 +38,7 @@ public void GetAllElements_EmptyOrNonexistentDirectory_ReturnsEmptyCollection() WithUniqueTempRegKey(regKey => { // Arrange - var repository = new RegistryXmlRepository(regKey); + var repository = new RegistryXmlRepository(regKey, NullLoggerFactory.Instance); // Act var allElements = repository.GetAllElements(); @@ -55,7 +56,7 @@ public void StoreElement_WithValidFriendlyName_UsesFriendlyName() { // Arrange var element = XElement.Parse(""); - var repository = new RegistryXmlRepository(regKey); + var repository = new RegistryXmlRepository(regKey, NullLoggerFactory.Instance); // Act repository.StoreElement(element, "valid-friendly-name"); @@ -86,7 +87,7 @@ public void StoreElement_WithInvalidFriendlyName_CreatesNewGuidAsName(string fri { // Arrange var element = XElement.Parse(""); - var repository = new RegistryXmlRepository(regKey); + var repository = new RegistryXmlRepository(regKey, NullLoggerFactory.Instance); // Act repository.StoreElement(element, friendlyName); @@ -112,7 +113,7 @@ public void StoreElements_ThenRetrieve_SeesAllElements() WithUniqueTempRegKey(regKey => { // Arrange - var repository = new RegistryXmlRepository(regKey); + var repository = new RegistryXmlRepository(regKey, NullLoggerFactory.Instance); // Act repository.StoreElement(new XElement("element1"), friendlyName: null); diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/StringLoggerFactory.cs b/test/Microsoft.AspNetCore.DataProtection.Test/StringLoggerFactory.cs index 7a7596cc..8d2b146b 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/StringLoggerFactory.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/StringLoggerFactory.cs @@ -61,7 +61,7 @@ public bool IsEnabled(LogLevel logLevel) public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - string message = String.Format(CultureInfo.InvariantCulture, + string message = string.Format(CultureInfo.InvariantCulture, "Provider: {0}" + Environment.NewLine + "Log level: {1}" + Environment.NewLine + "Event id: {2}" + Environment.NewLine + diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/XmlAssert.cs b/test/Microsoft.AspNetCore.DataProtection.Test/XmlAssert.cs index e33bc0d8..3bd5ccdc 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/XmlAssert.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/XmlAssert.cs @@ -124,7 +124,7 @@ private static bool ShouldIncludeNodeDuringComparison(XNode node) return true; // relevant } - throw new NotSupportedException(String.Format("Node of type '{0}' is not supported.", node.GetType().Name)); + throw new NotSupportedException(string.Format("Node of type '{0}' is not supported.", node.GetType().Name)); } } diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/CertificateXmlEncryptionTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/CertificateXmlEncryptionTests.cs index e73a4912..23fd3bd0 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/CertificateXmlEncryptionTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/CertificateXmlEncryptionTests.cs @@ -8,6 +8,8 @@ using System.Xml; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; @@ -22,7 +24,6 @@ public void Encrypt_Decrypt_RoundTrips() var symmetricAlgorithm = new TripleDESCryptoServiceProvider(); symmetricAlgorithm.GenerateKey(); - var serviceCollection = new ServiceCollection(); var mockInternalEncryptor = new Mock(); mockInternalEncryptor.Setup(o => o.PerformEncryption(It.IsAny(), It.IsAny())) .Returns((encryptedXml, element) => @@ -30,7 +31,6 @@ public void Encrypt_Decrypt_RoundTrips() encryptedXml.AddKeyNameMapping("theKey", symmetricAlgorithm); // use symmetric encryption return encryptedXml.Encrypt(element, "theKey"); }); - serviceCollection.AddSingleton(mockInternalEncryptor.Object); var mockInternalDecryptor = new Mock(); mockInternalDecryptor.Setup(o => o.PerformPreDecryptionSetup(It.IsAny())) @@ -38,10 +38,12 @@ public void Encrypt_Decrypt_RoundTrips() { encryptedXml.AddKeyNameMapping("theKey", symmetricAlgorithm); // use symmetric encryption }); + + var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(mockInternalDecryptor.Object); var services = serviceCollection.BuildServiceProvider(); - var encryptor = new CertificateXmlEncryptor(services); + var encryptor = new CertificateXmlEncryptor(NullLoggerFactory.Instance, mockInternalEncryptor.Object); var decryptor = new EncryptedXmlDecryptor(services); var originalXml = XElement.Parse(@""); diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/DpapiNGXmlEncryptionTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/DpapiNGXmlEncryptionTests.cs index 2a4f19df..6b16c638 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/DpapiNGXmlEncryptionTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/DpapiNGXmlEncryptionTests.cs @@ -5,6 +5,7 @@ using System.Xml.Linq; using Microsoft.AspNetCore.DataProtection.Test.Shared; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.XmlEncryption @@ -17,7 +18,7 @@ public void Encrypt_Decrypt_RoundTrips() { // Arrange var originalXml = XElement.Parse(@""); - var encryptor = new DpapiNGXmlEncryptor("LOCAL=user", DpapiNGProtectionDescriptorFlags.None); + var encryptor = new DpapiNGXmlEncryptor("LOCAL=user", DpapiNGProtectionDescriptorFlags.None, NullLoggerFactory.Instance); var decryptor = new DpapiNGXmlDecryptor(); // Act & assert - run through encryptor and make sure we get back an obfuscated element diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/DpapiXmlEncryptionTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/DpapiXmlEncryptionTests.cs index 1d6d8208..a397337f 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/DpapiXmlEncryptionTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/DpapiXmlEncryptionTests.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.DataProtection.Test.Shared; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection.XmlEncryption @@ -20,7 +21,7 @@ public void Encrypt_CurrentUserOrLocalMachine_Decrypt_RoundTrips(bool protectToL { // Arrange var originalXml = XElement.Parse(@""); - var encryptor = new DpapiXmlEncryptor(protectToLocalMachine); + var encryptor = new DpapiXmlEncryptor(protectToLocalMachine, NullLoggerFactory.Instance); var decryptor = new DpapiXmlDecryptor(); // Act & assert - run through encryptor and make sure we get back an obfuscated element @@ -40,7 +41,7 @@ public void Encrypt_CurrentUser_Decrypt_ImpersonatedAsAnonymous_Fails() { // Arrange var originalXml = XElement.Parse(@""); - var encryptor = new DpapiXmlEncryptor(protectToLocalMachine: false); + var encryptor = new DpapiXmlEncryptor(protectToLocalMachine: false, loggerFactory: NullLoggerFactory.Instance); var decryptor = new DpapiXmlDecryptor(); // Act & assert - run through encryptor and make sure we get back an obfuscated element diff --git a/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/XmlEncryptionExtensionsTests.cs b/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/XmlEncryptionExtensionsTests.cs index d03fee0c..bf3c455b 100644 --- a/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/XmlEncryptionExtensionsTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.Test/XmlEncryption/XmlEncryptionExtensionsTests.cs @@ -151,7 +151,7 @@ public void EncryptIfNecessary_MultipleNodesRequireEncryption_Success() "); - var expected = String.Format(@" + var expected = string.Format(@" @@ -194,7 +194,7 @@ public void EncryptIfNecessary_NullEncryptorWithRecursion_NoStackDive_Success() "); - var expected = String.Format(@" + var expected = string.Format(@"