diff --git a/src/DataProtection/DataProtection.sln b/src/DataProtection/DataProtection.sln
new file mode 100644
index 0000000000..c08ab6a1ce
--- /dev/null
+++ b/src/DataProtection/DataProtection.sln
@@ -0,0 +1,300 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26814.1
+MinimumVisualStudioVersion = 15.0.26730.03
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{60336AB3-948D-4D15-A5FB-F32A2B91E814}"
+ ProjectSection(SolutionItems) = preProject
+ test\CreateTestCert.ps1 = test\CreateTestCert.ps1
+ test\Directory.Build.props = test\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5A3A5DE3-49AD-431C-971D-B01B62D94AE2}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E1D86B1B-41D8-43C9-97FD-C2BF65C414E2}"
+ ProjectSection(SolutionItems) = preProject
+ .appveyor.yml = .appveyor.yml
+ .gitattributes = .gitattributes
+ .gitignore = .gitignore
+ .travis.yml = .travis.yml
+ CONTRIBUTING.md = CONTRIBUTING.md
+ build\dependencies.props = build\dependencies.props
+ Directory.Build.props = Directory.Build.props
+ Directory.Build.targets = Directory.Build.targets
+ korebuild.json = korebuild.json
+ LICENSE.txt = LICENSE.txt
+ NuGet.config = NuGet.config
+ NuGetPackageVerifier.json = NuGetPackageVerifier.json
+ Provision-AutoGenKeys.ps1 = Provision-AutoGenKeys.ps1
+ README.md = README.md
+ version.props = version.props
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection", "src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj", "{1E570CD4-6F12-44F4-961E-005EE2002BC2}"
+EndProject
+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("{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("{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("{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("{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("{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("{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("{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("{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("{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("{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("{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("{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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyManagementSample", "samples\KeyManagementSample\KeyManagementSample.csproj", "{6E066F8D-2910-404F-8949-F58125E28495}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomEncryptorSample", "samples\CustomEncryptorSample\CustomEncryptorSample.csproj", "{F4D59BBD-6145-4EE0-BA6E-AD03605BF151}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureKeyVault", "src\Microsoft.AspNetCore.DataProtection.AzureKeyVault\Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj", "{4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureKeyVault", "samples\AzureKeyVault\AzureKeyVault.csproj", "{295E8539-5450-4764-B3F5-51F968628022}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test", "test\Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test\Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test.csproj", "{C85ED942-8121-453F-8308-9DB730843B63}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Release|x86.ActiveCfg = Release|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Release|x86.ActiveCfg = Release|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Debug|x86.Build.0 = Debug|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Release|x86.ActiveCfg = Release|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Release|x86.Build.0 = Release|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Debug|x86.Build.0 = Debug|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Release|x86.ActiveCfg = Release|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Release|x86.Build.0 = Release|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Debug|x86.Build.0 = Debug|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Release|x86.ActiveCfg = Release|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Release|x86.Build.0 = Release|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Debug|x86.Build.0 = Debug|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|x86.ActiveCfg = Release|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|x86.Build.0 = Release|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|x86.Build.0 = Debug|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|x86.ActiveCfg = Release|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|x86.Build.0 = Release|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|x86.Build.0 = Debug|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|x86.ActiveCfg = Release|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|x86.Build.0 = Release|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|x86.Build.0 = Debug|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|x86.ActiveCfg = Release|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|x86.Build.0 = Release|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|x86.Build.0 = Debug|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|Any CPU.Build.0 = Release|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|x86.ActiveCfg = Release|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|x86.Build.0 = Release|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|x86.Build.0 = Debug|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|x86.ActiveCfg = Release|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|x86.Build.0 = Release|Any CPU
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}.Debug|x86.Build.0 = Debug|Any CPU
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}.Release|x86.ActiveCfg = Release|Any CPU
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9}.Release|x86.Build.0 = Release|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Debug|x86.Build.0 = Debug|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Release|x86.ActiveCfg = Release|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Release|x86.Build.0 = Release|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Debug|x86.Build.0 = Debug|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Release|x86.ActiveCfg = Release|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Release|x86.Build.0 = Release|Any CPU
+ {ABCF00E5-5B2F-469C-90DC-908C5A04C08D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ABCF00E5-5B2F-469C-90DC-908C5A04C08D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ABCF00E5-5B2F-469C-90DC-908C5A04C08D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ABCF00E5-5B2F-469C-90DC-908C5A04C08D}.Debug|x86.Build.0 = Debug|Any CPU
+ {ABCF00E5-5B2F-469C-90DC-908C5A04C08D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ABCF00E5-5B2F-469C-90DC-908C5A04C08D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ABCF00E5-5B2F-469C-90DC-908C5A04C08D}.Release|x86.ActiveCfg = Release|Any CPU
+ {ABCF00E5-5B2F-469C-90DC-908C5A04C08D}.Release|x86.Build.0 = Release|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Debug|x86.Build.0 = Debug|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Release|x86.ActiveCfg = Release|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Release|x86.Build.0 = Release|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Debug|x86.Build.0 = Debug|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {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
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Debug|x86.Build.0 = Debug|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Release|x86.ActiveCfg = Release|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Release|x86.Build.0 = Release|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Debug|x86.Build.0 = Debug|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Release|Any CPU.Build.0 = Release|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Release|x86.ActiveCfg = Release|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Release|x86.Build.0 = Release|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Debug|x86.Build.0 = Debug|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Release|x86.ActiveCfg = Release|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {421F0383-34B1-402D-807B-A94542513ABA} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {42C97F52-8D56-46BD-A712-4F22BED157A7} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {0508ADB0-9D2E-4506-9AA3-C15D7BEAE7C9} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {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}
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {295E8539-5450-4764-B3F5-51F968628022} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {C85ED942-8121-453F-8308-9DB730843B63} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DD305D75-BD1B-43AE-BF04-869DA6A0858F}
+ EndGlobalSection
+EndGlobal
diff --git a/src/DataProtection/Directory.Build.props b/src/DataProtection/Directory.Build.props
new file mode 100644
index 0000000000..deb7bb4ee6
--- /dev/null
+++ b/src/DataProtection/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/Provision-AutoGenKeys.ps1 b/src/DataProtection/Provision-AutoGenKeys.ps1
new file mode 100644
index 0000000000..9be7e1601d
--- /dev/null
+++ b/src/DataProtection/Provision-AutoGenKeys.ps1
@@ -0,0 +1,117 @@
+param (
+ [Parameter(Mandatory = $True)]
+ [string] $appPoolName
+ )
+
+# Provisions the HKLM registry so that the specified user account can persist auto-generated machine keys.
+function Provision-AutoGenKeys {
+ [CmdletBinding()]
+ param (
+ [ValidateSet("2.0", "4.0")]
+ [Parameter(Mandatory = $True)]
+ [string] $frameworkVersion,
+ [ValidateSet("32", "64")]
+ [Parameter(Mandatory = $True)]
+ [string] $architecture,
+ [Parameter(Mandatory = $True)]
+ [string] $sid
+ )
+ process {
+ # We require administrative permissions to continue.
+ if (-Not (new-object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
+ Write-Error "This cmdlet requires Administrator permissions."
+ return
+ }
+ # Open HKLM with an appropriate view into the registry
+ if ($architecture -eq "32") {
+ $regView = [Microsoft.Win32.RegistryView]::Registry32;
+ } else {
+ $regView = [Microsoft.Win32.RegistryView]::Registry64;
+ }
+ $baseRegKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $regView)
+ # Open ASP.NET base key
+ if ($frameworkVersion -eq "2.0") {
+ $expandedVersion = "2.0.50727.0"
+ } else {
+ $expandedVersion = "4.0.30319.0"
+ }
+ $softwareMicrosoftKey = $baseRegKey.OpenSubKey("SOFTWARE\Microsoft\", $True);
+
+ $aspNetKey = $softwareMicrosoftKey.OpenSubKey("ASP.NET", $True);
+ if ($aspNetKey -eq $null)
+ {
+ $aspNetKey = $softwareMicrosoftKey.CreateSubKey("ASP.NET")
+ }
+
+ $aspNetBaseKey = $aspNetKey.OpenSubKey("$expandedVersion", $True);
+ if ($aspNetBaseKey -eq $null)
+ {
+ $aspNetBaseKey = $aspNetKey.CreateSubKey("$expandedVersion")
+ }
+
+ # Create AutoGenKeys subkey if it doesn't already exist
+ $autoGenBaseKey = $aspNetBaseKey.OpenSubKey("AutoGenKeys", $True)
+ if ($autoGenBaseKey -eq $null) {
+ $autoGenBaseKey = $aspNetBaseKey.CreateSubKey("AutoGenKeys")
+ }
+ # SYSTEM, ADMINISTRATORS, and the target SID get full access
+ $regSec = New-Object System.Security.AccessControl.RegistrySecurity
+ $regSec.SetSecurityDescriptorSddlForm("D:P(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)(A;OICI;GA;;;$sid)")
+ $userAutoGenKey = $autoGenBaseKey.OpenSubKey($sid, $True)
+ if ($userAutoGenKey -eq $null) {
+ # Subkey didn't exist; create and ACL appropriately
+ $userAutoGenKey = $autoGenBaseKey.CreateSubKey($sid, [Microsoft.Win32.RegistryKeyPermissionCheck]::Default, $regSec)
+ } else {
+ # Subkey existed; make sure ACLs are correct
+ $userAutoGenKey.SetAccessControl($regSec)
+ }
+ }
+}
+
+$ErrorActionPreference = "Stop"
+if (Get-Command Get-IISAppPool -errorAction SilentlyContinue)
+{
+ $processModel = (Get-IISAppPool $appPoolName).processModel
+}
+else
+{
+ Import-Module WebAdministration
+ $processModel = Get-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name "processModel"
+}
+
+$identityType = $processModel.identityType
+Write-Output "Pool process model: '$identityType'"
+
+Switch ($identityType)
+{
+ "LocalService" {
+ $userName = "LocalService";
+ }
+ "LocalSystem" {
+ $userName = "System";
+ }
+ "NetworkService" {
+ $userName = "NetworkService";
+ }
+ "ApplicationPoolIdentity" {
+ $userName = "IIS APPPOOL\$appPoolName";
+ }
+ "SpecificUser" {
+ $userName = $processModel.userName;
+ }
+}
+Write-Output "Pool user name: '$userName'"
+
+Try
+{
+ $poolSid = (New-Object System.Security.Principal.NTAccount($userName)).Translate([System.Security.Principal.SecurityIdentifier]).Value
+}
+Catch [System.Security.Principal.IdentityNotMappedException]
+{
+ Write-Error "Application pool '$appPoolName' account cannot be resolved."
+}
+
+Write-Output "Pool SID: '$poolSid'"
+
+Provision-AutoGenKeys "4.0" "32" $poolSid
+Provision-AutoGenKeys "4.0" "64" $poolSid
diff --git a/src/DataProtection/README.md b/src/DataProtection/README.md
new file mode 100644
index 0000000000..cd58074d9e
--- /dev/null
+++ b/src/DataProtection/README.md
@@ -0,0 +1,8 @@
+DataProtection
+==============
+
+Data Protection APIs for protecting and unprotecting data. You can find documentation for Data Protection in the [ASP.NET Core Documentation](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/).
+
+## Community Maintained Data Protection Providers & Projects
+
+ - [ASP.NET Core DataProtection for Service Fabric](https://github.com/MedAnd/AspNetCore.DataProtection.ServiceFabric)
diff --git a/src/DataProtection/dependencies.props b/src/DataProtection/dependencies.props
new file mode 100644
index 0000000000..7a7089d81f
--- /dev/null
+++ b/src/DataProtection/dependencies.props
@@ -0,0 +1,29 @@
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+ 2.1.3-rtm-15822
+
+
+
+
+ 2.1.1
+ 2.1.1
+ 2.1.0
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 2.1.1
+
+
diff --git a/src/DataProtection/samples/AzureBlob/AzureBlob.csproj b/src/DataProtection/samples/AzureBlob/AzureBlob.csproj
new file mode 100644
index 0000000000..8ba3d51f0f
--- /dev/null
+++ b/src/DataProtection/samples/AzureBlob/AzureBlob.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netcoreapp2.1
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/AzureBlob/Program.cs b/src/DataProtection/samples/AzureBlob/Program.cs
new file mode 100644
index 0000000000..cce8604648
--- /dev/null
+++ b/src/DataProtection/samples/AzureBlob/Program.cs
@@ -0,0 +1,43 @@
+// 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;
+using Microsoft.WindowsAzure.Storage;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+
+namespace AzureBlob
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
+ var client = storageAccount.CreateCloudBlobClient();
+ var container = client.GetContainerReference("key-container");
+
+ // The container must exist before calling the DataProtection APIs.
+ // The specific file within the container does not have to exist,
+ // as it will be created on-demand.
+
+ container.CreateIfNotExistsAsync().GetAwaiter().GetResult();
+
+ // Configure
+ using (var services = new ServiceCollection()
+ .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
+ .AddDataProtection()
+ .PersistKeysToAzureBlobStorage(container, "keys.xml")
+ .Services
+ .BuildServiceProvider())
+ {
+ // Run a sample payload
+
+ var protector = services.GetDataProtector("sample-purpose");
+ var protectedData = protector.Protect("Hello world!");
+ Console.WriteLine(protectedData);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj b/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj
new file mode 100644
index 0000000000..ce4ae01408
--- /dev/null
+++ b/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp2.1
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/AzureKeyVault/Program.cs b/src/DataProtection/samples/AzureKeyVault/Program.cs
new file mode 100644
index 0000000000..7d6299f3e5
--- /dev/null
+++ b/src/DataProtection/samples/AzureKeyVault/Program.cs
@@ -0,0 +1,44 @@
+// 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.Linq;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace ConsoleApplication
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var builder = new ConfigurationBuilder();
+ builder.SetBasePath(Directory.GetCurrentDirectory());
+ builder.AddJsonFile("settings.json");
+ var config = builder.Build();
+
+ var store = new X509Store(StoreLocation.CurrentUser);
+ store.Open(OpenFlags.ReadOnly);
+ var cert = store.Certificates.Find(X509FindType.FindByThumbprint, config["CertificateThumbprint"], false);
+
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddLogging();
+ serviceCollection.AddDataProtection()
+ .PersistKeysToFileSystem(new DirectoryInfo("."))
+ .ProtectKeysWithAzureKeyVault(config["KeyId"], config["ClientId"], cert.OfType().Single());
+
+ var serviceProvider = serviceCollection.BuildServiceProvider();
+
+ var loggerFactory = serviceProvider.GetService();
+ loggerFactory.AddConsole();
+
+ var protector = serviceProvider.GetDataProtector("Test");
+
+ Console.WriteLine(protector.Protect("Hello world"));
+ }
+ }
+}
diff --git a/src/DataProtection/samples/AzureKeyVault/settings.json b/src/DataProtection/samples/AzureKeyVault/settings.json
new file mode 100644
index 0000000000..ef7d4d81b8
--- /dev/null
+++ b/src/DataProtection/samples/AzureKeyVault/settings.json
@@ -0,0 +1,5 @@
+{
+ "CertificateThumbprint": "",
+ "KeyId": "",
+ "ClientId": ""
+}
\ No newline at end of file
diff --git a/src/DataProtection/samples/CustomEncryptorSample/CustomBuilderExtensions.cs b/src/DataProtection/samples/CustomEncryptorSample/CustomBuilderExtensions.cs
new file mode 100644
index 0000000000..faa99a4a5d
--- /dev/null
+++ b/src/DataProtection/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/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj b/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj
new file mode 100644
index 0000000000..1cfe237b50
--- /dev/null
+++ b/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net461;netcoreapp2.1
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/CustomEncryptorSample/CustomXmlDecryptor.cs b/src/DataProtection/samples/CustomEncryptorSample/CustomXmlDecryptor.cs
new file mode 100644
index 0000000000..a8925f12f6
--- /dev/null
+++ b/src/DataProtection/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/src/DataProtection/samples/CustomEncryptorSample/CustomXmlEncryptor.cs b/src/DataProtection/samples/CustomEncryptorSample/CustomXmlEncryptor.cs
new file mode 100644
index 0000000000..f6653f776a
--- /dev/null
+++ b/src/DataProtection/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/src/DataProtection/samples/CustomEncryptorSample/Program.cs b/src/DataProtection/samples/CustomEncryptorSample/Program.cs
new file mode 100644
index 0000000000..9079aeee3f
--- /dev/null
+++ b/src/DataProtection/samples/CustomEncryptorSample/Program.cs
@@ -0,0 +1,36 @@
+// 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");
+ using (var services = new ServiceCollection()
+ .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
+ .AddDataProtection()
+ .PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
+ .UseXmlEncryptor(s => new CustomXmlEncryptor(s))
+ .Services.BuildServiceProvider())
+ {
+ 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/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj b/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj
new file mode 100644
index 0000000000..e240e30b6d
--- /dev/null
+++ b/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net461;netcoreapp2.1
+ exe
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/KeyManagementSample/Program.cs b/src/DataProtection/samples/KeyManagementSample/Program.cs
new file mode 100644
index 0000000000..be128aa11c
--- /dev/null
+++ b/src/DataProtection/samples/KeyManagementSample/Program.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 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();
+ }
+
+ using (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/src/DataProtection/samples/NonDISample/NonDISample.csproj b/src/DataProtection/samples/NonDISample/NonDISample.csproj
new file mode 100644
index 0000000000..168e26a249
--- /dev/null
+++ b/src/DataProtection/samples/NonDISample/NonDISample.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net461;netcoreapp2.1
+ exe
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/NonDISample/Program.cs b/src/DataProtection/samples/NonDISample/Program.cs
new file mode 100644
index 0000000000..f9ccd92603
--- /dev/null
+++ b/src/DataProtection/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/src/DataProtection/samples/Redis/Program.cs b/src/DataProtection/samples/Redis/Program.cs
new file mode 100644
index 0000000000..f8f213cfad
--- /dev/null
+++ b/src/DataProtection/samples/Redis/Program.cs
@@ -0,0 +1,34 @@
+// 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;
+using StackExchange.Redis;
+
+namespace Redis
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ // Connect
+ var redis = ConnectionMultiplexer.Connect("localhost:6379");
+
+ // Configure
+ using (var services = new ServiceCollection()
+ .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
+ .AddDataProtection()
+ .PersistKeysToRedis(redis, "DataProtection-Keys")
+ .Services
+ .BuildServiceProvider())
+ {
+ // Run a sample payload
+ var protector = services.GetDataProtector("sample-purpose");
+ var protectedData = protector.Protect("Hello world!");
+ Console.WriteLine(protectedData);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/samples/Redis/Redis.csproj b/src/DataProtection/samples/Redis/Redis.csproj
new file mode 100644
index 0000000000..dc79399c6c
--- /dev/null
+++ b/src/DataProtection/samples/Redis/Redis.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net461;netcoreapp2.1
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/shared/EncodingUtil.cs b/src/DataProtection/shared/EncodingUtil.cs
new file mode 100644
index 0000000000..67b99eac3b
--- /dev/null
+++ b/src/DataProtection/shared/EncodingUtil.cs
@@ -0,0 +1,14 @@
+// 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.Text;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class EncodingUtil
+ {
+ // UTF8 encoding that fails on invalid chars
+ public static readonly UTF8Encoding SecureUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
+ }
+}
diff --git a/src/DataProtection/shared/ExceptionExtensions.cs b/src/DataProtection/shared/ExceptionExtensions.cs
new file mode 100644
index 0000000000..f441935d13
--- /dev/null
+++ b/src/DataProtection/shared/ExceptionExtensions.cs
@@ -0,0 +1,20 @@
+// 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;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class ExceptionExtensions
+ {
+ ///
+ /// Determines whether an exception must be homogenized by being wrapped inside a
+ /// CryptographicException before being rethrown.
+ ///
+ public static bool RequiresHomogenization(this Exception ex)
+ {
+ return !(ex is CryptographicException);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Directory.Build.props b/src/DataProtection/src/Directory.Build.props
new file mode 100644
index 0000000000..4b89a431e7
--- /dev/null
+++ b/src/DataProtection/src/Directory.Build.props
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.cs
new file mode 100644
index 0000000000..0c074b8280
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.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.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/cc562981(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
+ {
+ public uint cbSize;
+ public uint dwInfoVersion;
+ public byte* pbNonce;
+ public uint cbNonce;
+ public byte* pbAuthData;
+ public uint cbAuthData;
+ public byte* pbTag;
+ public uint cbTag;
+ public byte* pbMacContext;
+ public uint cbMacContext;
+ public uint cbAAD;
+ public ulong cbData;
+ public uint dwFlags;
+
+ // corresponds to the BCRYPT_INIT_AUTH_MODE_INFO macro in bcrypt.h
+ public static void Init(out BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO info)
+ {
+ const uint BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION = 1;
+ info = new BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
+ {
+ cbSize = (uint)sizeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO),
+ dwInfoVersion = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION
+ };
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_KEY_LENGTHS_STRUCT.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_KEY_LENGTHS_STRUCT.cs
new file mode 100644
index 0000000000..0d4139018f
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_KEY_LENGTHS_STRUCT.cs
@@ -0,0 +1,46 @@
+// 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.Runtime.InteropServices;
+using Microsoft.AspNetCore.Cryptography.Internal;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375525(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct BCRYPT_KEY_LENGTHS_STRUCT
+ {
+ // MSDN says these fields represent the key length in bytes.
+ // It's wrong: these key lengths are all actually in bits.
+ internal uint dwMinLength;
+ internal uint dwMaxLength;
+ internal uint dwIncrement;
+
+ public void EnsureValidKeyLength(uint keyLengthInBits)
+ {
+ if (!IsValidKeyLength(keyLengthInBits))
+ {
+ string message = Resources.FormatBCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength(keyLengthInBits, dwMinLength, dwMaxLength, dwIncrement);
+ throw new ArgumentOutOfRangeException(nameof(keyLengthInBits), message);
+ }
+ CryptoUtil.Assert(keyLengthInBits % 8 == 0, "keyLengthInBits % 8 == 0");
+ }
+
+ private bool IsValidKeyLength(uint keyLengthInBits)
+ {
+ // If the step size is zero, then the key length must be exactly the min or the max. Otherwise,
+ // key length must be between min and max (inclusive) and a whole number of increments away from min.
+ if (dwIncrement == 0)
+ {
+ return (keyLengthInBits == dwMinLength || keyLengthInBits == dwMaxLength);
+ }
+ else
+ {
+ return (dwMinLength <= keyLengthInBits)
+ && (keyLengthInBits <= dwMaxLength)
+ && ((keyLengthInBits - dwMinLength) % dwIncrement == 0);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBuffer.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBuffer.cs
new file mode 100644
index 0000000000..c091859729
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBuffer.cs
@@ -0,0 +1,17 @@
+// 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.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375368(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct BCryptBuffer
+ {
+ public uint cbBuffer; // Length of buffer, in bytes
+ public BCryptKeyDerivationBufferType BufferType; // Buffer type
+ public IntPtr pvBuffer; // Pointer to buffer
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBufferDesc.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBufferDesc.cs
new file mode 100644
index 0000000000..8fd699643e
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBufferDesc.cs
@@ -0,0 +1,26 @@
+// 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.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375370(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct BCryptBufferDesc
+ {
+ private const int BCRYPTBUFFER_VERSION = 0;
+
+ public uint ulVersion; // Version number
+ public uint cBuffers; // Number of buffers
+ public BCryptBuffer* pBuffers; // Pointer to array of buffers
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Initialize(ref BCryptBufferDesc bufferDesc)
+ {
+ bufferDesc.ulVersion = BCRYPTBUFFER_VERSION;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptEncryptFlags.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptEncryptFlags.cs
new file mode 100644
index 0000000000..81ae0105cc
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptEncryptFlags.cs
@@ -0,0 +1,13 @@
+// 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.Cryptography.Cng
+{
+ [Flags]
+ internal enum BCryptEncryptFlags
+ {
+ BCRYPT_BLOCK_PADDING = 0x00000001,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptGenRandomFlags.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptGenRandomFlags.cs
new file mode 100644
index 0000000000..ed20fec309
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptGenRandomFlags.cs
@@ -0,0 +1,15 @@
+// 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.Cryptography.Cng
+{
+ // from bcrypt.h
+ [Flags]
+ internal enum BCryptGenRandomFlags
+ {
+ BCRYPT_RNG_USE_ENTROPY_IN_BUFFER = 0x00000001,
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptKeyDerivationBufferType.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptKeyDerivationBufferType.cs
new file mode 100644
index 0000000000..a68569e799
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptKeyDerivationBufferType.cs
@@ -0,0 +1,29 @@
+// 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.Cryptography.Cng
+{
+ // from bcrypt.h
+ internal enum BCryptKeyDerivationBufferType
+ {
+ KDF_HASH_ALGORITHM = 0x0,
+ KDF_SECRET_PREPEND = 0x1,
+ KDF_SECRET_APPEND = 0x2,
+ KDF_HMAC_KEY = 0x3,
+ KDF_TLS_PRF_LABEL = 0x4,
+ KDF_TLS_PRF_SEED = 0x5,
+ KDF_SECRET_HANDLE = 0x6,
+ KDF_TLS_PRF_PROTOCOL = 0x7,
+ KDF_ALGORITHMID = 0x8,
+ KDF_PARTYUINFO = 0x9,
+ KDF_PARTYVINFO = 0xA,
+ KDF_SUPPPUBINFO = 0xB,
+ KDF_SUPPPRIVINFO = 0xC,
+ KDF_LABEL = 0xD,
+ KDF_CONTEXT = 0xE,
+ KDF_SALT = 0xF,
+ KDF_ITERATION_COUNT = 0x10,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptUtil.cs
new file mode 100644
index 0000000000..86c86c64a8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptUtil.cs
@@ -0,0 +1,29 @@
+// 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.Cryptography.Cng
+{
+ ///
+ /// Wraps utility BCRYPT APIs that don't work directly with handles.
+ ///
+ internal unsafe static class BCryptUtil
+ {
+ ///
+ /// Fills a buffer with cryptographically secure random data.
+ ///
+ public static void GenRandom(byte* pbBuffer, uint cbBuffer)
+ {
+ if (cbBuffer != 0)
+ {
+ int ntstatus = UnsafeNativeMethods.BCryptGenRandom(
+ hAlgorithm: IntPtr.Zero,
+ pbBuffer: pbBuffer,
+ cbBuffer: cbBuffer,
+ dwFlags: BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/CachedAlgorithmHandles.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/CachedAlgorithmHandles.cs
new file mode 100644
index 0000000000..48e63685fd
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/CachedAlgorithmHandles.cs
@@ -0,0 +1,93 @@
+// 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.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ ///
+ /// Provides cached CNG algorithm provider instances, as calling BCryptOpenAlgorithmProvider is expensive.
+ /// Callers should use caution never to dispose of the algorithm provider instances returned by this type.
+ ///
+ internal static class CachedAlgorithmHandles
+ {
+ private static CachedAlgorithmInfo _aesCbc = new CachedAlgorithmInfo(() => GetAesAlgorithm(chainingMode: Constants.BCRYPT_CHAIN_MODE_CBC));
+ private static CachedAlgorithmInfo _aesGcm = new CachedAlgorithmInfo(() => GetAesAlgorithm(chainingMode: Constants.BCRYPT_CHAIN_MODE_GCM));
+ private static CachedAlgorithmInfo _hmacSha1 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA1_ALGORITHM));
+ private static CachedAlgorithmInfo _hmacSha256 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA256_ALGORITHM));
+ private static CachedAlgorithmInfo _hmacSha512 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA512_ALGORITHM));
+ private static CachedAlgorithmInfo _pbkdf2 = new CachedAlgorithmInfo(GetPbkdf2Algorithm);
+ private static CachedAlgorithmInfo _sha1 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA1_ALGORITHM));
+ private static CachedAlgorithmInfo _sha256 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA256_ALGORITHM));
+ private static CachedAlgorithmInfo _sha512 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA512_ALGORITHM));
+ private static CachedAlgorithmInfo _sp800_108_ctr_hmac = new CachedAlgorithmInfo(GetSP800_108_CTR_HMACAlgorithm);
+
+ public static BCryptAlgorithmHandle AES_CBC => CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesCbc);
+
+ public static BCryptAlgorithmHandle AES_GCM => CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesGcm);
+
+ public static BCryptAlgorithmHandle HMAC_SHA1 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha1);
+
+ public static BCryptAlgorithmHandle HMAC_SHA256 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha256);
+
+ public static BCryptAlgorithmHandle HMAC_SHA512 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha512);
+
+ // Only available on Win8+.
+ public static BCryptAlgorithmHandle PBKDF2 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _pbkdf2);
+
+ public static BCryptAlgorithmHandle SHA1 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha1);
+
+ public static BCryptAlgorithmHandle SHA256 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha256);
+
+ public static BCryptAlgorithmHandle SHA512 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha512);
+
+ // Only available on Win8+.
+ public static BCryptAlgorithmHandle SP800_108_CTR_HMAC => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sp800_108_ctr_hmac);
+
+ private static BCryptAlgorithmHandle GetAesAlgorithm(string chainingMode)
+ {
+ var algHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_AES_ALGORITHM);
+ algHandle.SetChainingMode(chainingMode);
+ return algHandle;
+ }
+
+ private static BCryptAlgorithmHandle GetHashAlgorithm(string algorithm)
+ {
+ return BCryptAlgorithmHandle.OpenAlgorithmHandle(algorithm, hmac: false);
+ }
+
+ private static BCryptAlgorithmHandle GetHmacAlgorithm(string algorithm)
+ {
+ return BCryptAlgorithmHandle.OpenAlgorithmHandle(algorithm, hmac: true);
+ }
+
+ private static BCryptAlgorithmHandle GetPbkdf2Algorithm()
+ {
+ return BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_PBKDF2_ALGORITHM, implementation: Constants.MS_PRIMITIVE_PROVIDER);
+ }
+
+ private static BCryptAlgorithmHandle GetSP800_108_CTR_HMACAlgorithm()
+ {
+ return BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_SP800108_CTR_HMAC_ALGORITHM, implementation: Constants.MS_PRIMITIVE_PROVIDER);
+ }
+
+ // Warning: mutable struct!
+ private struct CachedAlgorithmInfo
+ {
+ private WeakReference _algorithmHandle;
+ private readonly Func _factory;
+
+ public CachedAlgorithmInfo(Func factory)
+ {
+ _algorithmHandle = null;
+ _factory = factory;
+ }
+
+ public static BCryptAlgorithmHandle GetAlgorithmHandle(ref CachedAlgorithmInfo cachedAlgorithmInfo)
+ {
+ return WeakReferenceHelpers.GetSharedInstance(ref cachedAlgorithmInfo._algorithmHandle, cachedAlgorithmInfo._factory);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/NCryptEncryptFlags.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/NCryptEncryptFlags.cs
new file mode 100644
index 0000000000..a0c1bc0fc4
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/NCryptEncryptFlags.cs
@@ -0,0 +1,17 @@
+// 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.Cryptography.Cng
+{
+ [Flags]
+ internal enum NCryptEncryptFlags
+ {
+ NCRYPT_NO_PADDING_FLAG = 0x00000001,
+ NCRYPT_PAD_PKCS1_FLAG = 0x00000002,
+ NCRYPT_PAD_OAEP_FLAG = 0x00000004,
+ NCRYPT_PAD_PSS_FLAG = 0x00000008,
+ NCRYPT_SILENT_FLAG = 0x00000040,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/OSVersionUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/OSVersionUtil.cs
new file mode 100644
index 0000000000..0d09a0b6f8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/OSVersionUtil.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.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ internal static class OSVersionUtil
+ {
+ private static readonly OSVersion _osVersion = GetOSVersion();
+
+ private static OSVersion GetOSVersion()
+ {
+ const string BCRYPT_LIB = "bcrypt.dll";
+ SafeLibraryHandle bcryptLibHandle = null;
+ try
+ {
+ bcryptLibHandle = SafeLibraryHandle.Open(BCRYPT_LIB);
+ }
+ catch
+ {
+ // we'll handle the exceptional case later
+ }
+
+ if (bcryptLibHandle != null)
+ {
+ using (bcryptLibHandle)
+ {
+ if (bcryptLibHandle.DoesProcExist("BCryptKeyDerivation"))
+ {
+ // We're running on Win8+.
+ return OSVersion.Win8OrLater;
+ }
+ else
+ {
+ // We're running on Win7+.
+ return OSVersion.Win7OrLater;
+ }
+ }
+ }
+ else
+ {
+ // Not running on Win7+.
+ return OSVersion.NotWindows;
+ }
+ }
+
+ public static bool IsWindows()
+ {
+ return (_osVersion >= OSVersion.Win7OrLater);
+ }
+
+ public static bool IsWindows8OrLater()
+ {
+ return (_osVersion >= OSVersion.Win8OrLater);
+ }
+
+ private enum OSVersion
+ {
+ NotWindows = 0,
+ Win7OrLater = 1,
+ Win8OrLater = 2
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Constants.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Constants.cs
new file mode 100644
index 0000000000..44b0568aa8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Constants.cs
@@ -0,0 +1,88 @@
+// 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.Cryptography
+{
+ // The majority of these are from bcrypt.h
+ internal static class Constants
+ {
+ internal const int MAX_STACKALLOC_BYTES = 256; // greatest number of bytes that we'll ever allow to stackalloc in a single frame
+
+ // BCrypt(Import/Export)Key BLOB types
+ internal const string BCRYPT_OPAQUE_KEY_BLOB = "OpaqueKeyBlob";
+ internal const string BCRYPT_KEY_DATA_BLOB = "KeyDataBlob";
+ internal const string BCRYPT_AES_WRAP_KEY_BLOB = "Rfc3565KeyWrapBlob";
+
+ // Microsoft built-in providers
+ internal const string MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider";
+ internal const string MS_PLATFORM_CRYPTO_PROVIDER = "Microsoft Platform Crypto Provider";
+
+ // Common algorithm identifiers
+ internal const string BCRYPT_RSA_ALGORITHM = "RSA";
+ internal const string BCRYPT_RSA_SIGN_ALGORITHM = "RSA_SIGN";
+ internal const string BCRYPT_DH_ALGORITHM = "DH";
+ internal const string BCRYPT_DSA_ALGORITHM = "DSA";
+ internal const string BCRYPT_RC2_ALGORITHM = "RC2";
+ internal const string BCRYPT_RC4_ALGORITHM = "RC4";
+ internal const string BCRYPT_AES_ALGORITHM = "AES";
+ internal const string BCRYPT_DES_ALGORITHM = "DES";
+ internal const string BCRYPT_DESX_ALGORITHM = "DESX";
+ internal const string BCRYPT_3DES_ALGORITHM = "3DES";
+ internal const string BCRYPT_3DES_112_ALGORITHM = "3DES_112";
+ internal const string BCRYPT_MD2_ALGORITHM = "MD2";
+ internal const string BCRYPT_MD4_ALGORITHM = "MD4";
+ internal const string BCRYPT_MD5_ALGORITHM = "MD5";
+ internal const string BCRYPT_SHA1_ALGORITHM = "SHA1";
+ internal const string BCRYPT_SHA256_ALGORITHM = "SHA256";
+ internal const string BCRYPT_SHA384_ALGORITHM = "SHA384";
+ internal const string BCRYPT_SHA512_ALGORITHM = "SHA512";
+ internal const string BCRYPT_AES_GMAC_ALGORITHM = "AES-GMAC";
+ internal const string BCRYPT_AES_CMAC_ALGORITHM = "AES-CMAC";
+ internal const string BCRYPT_ECDSA_P256_ALGORITHM = "ECDSA_P256";
+ internal const string BCRYPT_ECDSA_P384_ALGORITHM = "ECDSA_P384";
+ internal const string BCRYPT_ECDSA_P521_ALGORITHM = "ECDSA_P521";
+ internal const string BCRYPT_ECDH_P256_ALGORITHM = "ECDH_P256";
+ internal const string BCRYPT_ECDH_P384_ALGORITHM = "ECDH_P384";
+ internal const string BCRYPT_ECDH_P521_ALGORITHM = "ECDH_P521";
+ internal const string BCRYPT_RNG_ALGORITHM = "RNG";
+ internal const string BCRYPT_RNG_FIPS186_DSA_ALGORITHM = "FIPS186DSARNG";
+ internal const string BCRYPT_RNG_DUAL_EC_ALGORITHM = "DUALECRNG";
+ internal const string BCRYPT_SP800108_CTR_HMAC_ALGORITHM = "SP800_108_CTR_HMAC";
+ internal const string BCRYPT_SP80056A_CONCAT_ALGORITHM = "SP800_56A_CONCAT";
+ internal const string BCRYPT_PBKDF2_ALGORITHM = "PBKDF2";
+ internal const string BCRYPT_CAPI_KDF_ALGORITHM = "CAPI_KDF";
+
+ // BCryptGetProperty strings
+ internal const string BCRYPT_OBJECT_LENGTH = "ObjectLength";
+ internal const string BCRYPT_ALGORITHM_NAME = "AlgorithmName";
+ internal const string BCRYPT_PROVIDER_HANDLE = "ProviderHandle";
+ internal const string BCRYPT_CHAINING_MODE = "ChainingMode";
+ internal const string BCRYPT_BLOCK_LENGTH = "BlockLength";
+ internal const string BCRYPT_KEY_LENGTH = "KeyLength";
+ internal const string BCRYPT_KEY_OBJECT_LENGTH = "KeyObjectLength";
+ internal const string BCRYPT_KEY_STRENGTH = "KeyStrength";
+ internal const string BCRYPT_KEY_LENGTHS = "KeyLengths";
+ internal const string BCRYPT_BLOCK_SIZE_LIST = "BlockSizeList";
+ internal const string BCRYPT_EFFECTIVE_KEY_LENGTH = "EffectiveKeyLength";
+ internal const string BCRYPT_HASH_LENGTH = "HashDigestLength";
+ internal const string BCRYPT_HASH_OID_LIST = "HashOIDList";
+ internal const string BCRYPT_PADDING_SCHEMES = "PaddingSchemes";
+ internal const string BCRYPT_SIGNATURE_LENGTH = "SignatureLength";
+ internal const string BCRYPT_HASH_BLOCK_LENGTH = "HashBlockLength";
+ internal const string BCRYPT_AUTH_TAG_LENGTH = "AuthTagLength";
+ internal const string BCRYPT_PRIMITIVE_TYPE = "PrimitiveType";
+ internal const string BCRYPT_IS_KEYED_HASH = "IsKeyedHash";
+ internal const string BCRYPT_IS_REUSABLE_HASH = "IsReusableHash";
+ internal const string BCRYPT_MESSAGE_BLOCK_LENGTH = "MessageBlockLength";
+
+ // Property Strings
+ internal const string BCRYPT_CHAIN_MODE_NA = "ChainingModeN/A";
+ internal const string BCRYPT_CHAIN_MODE_CBC = "ChainingModeCBC";
+ internal const string BCRYPT_CHAIN_MODE_ECB = "ChainingModeECB";
+ internal const string BCRYPT_CHAIN_MODE_CFB = "ChainingModeCFB";
+ internal const string BCRYPT_CHAIN_MODE_CCM = "ChainingModeCCM";
+ internal const string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM";
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/CryptoUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/CryptoUtil.cs
new file mode 100644
index 0000000000..e60673634d
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/CryptoUtil.cs
@@ -0,0 +1,99 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.Internal;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ internal unsafe static class CryptoUtil
+ {
+ // This isn't a typical Debug.Assert; the check is always performed, even in retail builds.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Assert(bool condition, string message)
+ {
+ if (!condition)
+ {
+ Fail(message);
+ }
+ }
+
+ // This isn't a typical Debug.Assert; the check is always performed, even in retail builds.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void AssertSafeHandleIsValid(SafeHandle safeHandle)
+ {
+ Assert(safeHandle != null && !safeHandle.IsInvalid, "Safe handle is invalid.");
+ }
+
+ // Asserts that the current platform is Windows; throws PlatformNotSupportedException otherwise.
+ public static void AssertPlatformIsWindows()
+ {
+ if (!OSVersionUtil.IsWindows())
+ {
+ throw new PlatformNotSupportedException(Resources.Platform_Windows7Required);
+ }
+ }
+
+ // Asserts that the current platform is Windows 8 or above; throws PlatformNotSupportedException otherwise.
+ public static void AssertPlatformIsWindows8OrLater()
+ {
+ if (!OSVersionUtil.IsWindows8OrLater())
+ {
+ throw new PlatformNotSupportedException(Resources.Platform_Windows8Required);
+ }
+ }
+
+ // This isn't a typical Debug.Fail; an error always occurs, even in retail builds.
+ // This method doesn't return, but since the CLR doesn't allow specifying a 'never'
+ // return type, we mimic it by specifying our return type as Exception. That way
+ // callers can write 'throw Fail(...);' to make the C# compiler happy, as the
+ // throw keyword is implicitly of type O.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static Exception Fail(string message)
+ {
+ Debug.Fail(message);
+ throw new CryptographicException("Assertion failed: " + message);
+ }
+
+ // Allows callers to write "var x = Method() ?? Fail(message);" as a convenience to guard
+ // against a method returning null unexpectedly.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static T Fail(string message) where T : class
+ {
+ throw Fail(message);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static bool TimeConstantBuffersAreEqual(byte* bufA, byte* bufB, uint count)
+ {
+ bool areEqual = true;
+ for (uint i = 0; i < count; i++)
+ {
+ areEqual &= (bufA[i] == bufB[i]);
+ }
+ return areEqual;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ public static bool TimeConstantBuffersAreEqual(byte[] bufA, int offsetA, int countA, byte[] bufB, int offsetB, int countB)
+ {
+ // Technically this is an early exit scenario, but it means that the caller did something bizarre.
+ // An error at the call site isn't usable for timing attacks.
+ Assert(countA == countB, "countA == countB");
+
+ bool areEqual = true;
+ for (int i = 0; i < countA; i++)
+ {
+ areEqual &= (bufA[offsetA + i] == bufB[offsetB + i]);
+ }
+ return areEqual;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/DATA_BLOB.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/DATA_BLOB.cs
new file mode 100644
index 0000000000..3c307bebce
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/DATA_BLOB.cs
@@ -0,0 +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 System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa381414(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct DATA_BLOB
+ {
+ public uint cbData;
+ public byte* pbData;
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Microsoft.AspNetCore.Cryptography.Internal.csproj b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Microsoft.AspNetCore.Cryptography.Internal.csproj
new file mode 100644
index 0000000000..ff4ef3babe
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Microsoft.AspNetCore.Cryptography.Internal.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Infrastructure for ASP.NET Core cryptographic packages. Applications and libraries should not reference this package directly.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ true
+ aspnetcore;dataprotection
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..62865ae945
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/AssemblyInfo.cs
@@ -0,0 +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.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// we only ever p/invoke into DLLs known to be in the System32 folder
+[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cryptography.Internal.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cryptography.KeyDerivation, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cryptography.KeyDerivation.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..df010bc683
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs
@@ -0,0 +1,86 @@
+//
+namespace Microsoft.AspNetCore.Cryptography.Internal
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.Cryptography.Internal.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// A provider could not be found for algorithm '{0}'.
+ ///
+ internal static string BCryptAlgorithmHandle_ProviderNotFound
+ {
+ get => GetString("BCryptAlgorithmHandle_ProviderNotFound");
+ }
+
+ ///
+ /// A provider could not be found for algorithm '{0}'.
+ ///
+ internal static string FormatBCryptAlgorithmHandle_ProviderNotFound(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("BCryptAlgorithmHandle_ProviderNotFound"), p0);
+
+ ///
+ /// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
+ ///
+ internal static string BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength
+ {
+ get => GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength");
+ }
+
+ ///
+ /// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
+ ///
+ internal static string FormatBCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength(object p0, object p1, object p2, object p3)
+ => string.Format(CultureInfo.CurrentCulture, GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength"), p0, p1, p2, p3);
+
+ ///
+ /// This operation requires Windows 7 / Windows Server 2008 R2 or later.
+ ///
+ internal static string Platform_Windows7Required
+ {
+ get => GetString("Platform_Windows7Required");
+ }
+
+ ///
+ /// This operation requires Windows 7 / Windows Server 2008 R2 or later.
+ ///
+ internal static string FormatPlatform_Windows7Required()
+ => GetString("Platform_Windows7Required");
+
+ ///
+ /// This operation requires Windows 8 / Windows Server 2012 or later.
+ ///
+ internal static string Platform_Windows8Required
+ {
+ get => GetString("Platform_Windows8Required");
+ }
+
+ ///
+ /// This operation requires Windows 8 / Windows Server 2012 or later.
+ ///
+ internal static string FormatPlatform_Windows8Required()
+ => GetString("Platform_Windows8Required");
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Resources.resx b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Resources.resx
new file mode 100644
index 0000000000..125f619abb
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Resources.resx
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ A provider could not be found for algorithm '{0}'.
+
+
+ The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
+
+
+ This operation requires Windows 7 / Windows Server 2008 R2 or later.
+
+
+ This operation requires Windows 8 / Windows Server 2012 or later.
+
+
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptAlgorithmHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptAlgorithmHandle.cs
new file mode 100644
index 0000000000..45f4c4e041
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptAlgorithmHandle.cs
@@ -0,0 +1,170 @@
+// 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.Diagnostics;
+using System.Globalization;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.Internal;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ ///
+ /// Represents a handle to a BCrypt algorithm provider from which keys and hashes can be created.
+ ///
+ internal unsafe sealed class BCryptAlgorithmHandle : BCryptHandle
+ {
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptAlgorithmHandle() { }
+
+ ///
+ /// Creates an unkeyed hash handle from this hash algorithm.
+ ///
+ public BCryptHashHandle CreateHash()
+ {
+ return CreateHashCore(null, 0);
+ }
+
+ private BCryptHashHandle CreateHashCore(byte* pbKey, uint cbKey)
+ {
+ BCryptHashHandle retVal;
+ int ntstatus = UnsafeNativeMethods.BCryptCreateHash(this, out retVal, IntPtr.Zero, 0, pbKey, cbKey, dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(retVal);
+
+ retVal.SetAlgorithmProviderHandle(this);
+ return retVal;
+ }
+
+ ///
+ /// Creates an HMAC hash handle from this hash algorithm.
+ ///
+ public BCryptHashHandle CreateHmac(byte* pbKey, uint cbKey)
+ {
+ Debug.Assert(pbKey != null);
+ return CreateHashCore(pbKey, cbKey);
+ }
+
+ ///
+ /// Imports a key into a symmetric encryption or KDF algorithm.
+ ///
+ public BCryptKeyHandle GenerateSymmetricKey(byte* pbSecret, uint cbSecret)
+ {
+ BCryptKeyHandle retVal;
+ int ntstatus = UnsafeNativeMethods.BCryptGenerateSymmetricKey(this, out retVal, IntPtr.Zero, 0, pbSecret, cbSecret, 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(retVal);
+
+ retVal.SetAlgorithmProviderHandle(this);
+ return retVal;
+ }
+
+ ///
+ /// Gets the name of this BCrypt algorithm.
+ ///
+ public string GetAlgorithmName()
+ {
+ // First, calculate how many characters are in the name.
+ uint byteLengthOfNameWithTerminatingNull = GetProperty(Constants.BCRYPT_ALGORITHM_NAME, null, 0);
+ CryptoUtil.Assert(byteLengthOfNameWithTerminatingNull % sizeof(char) == 0 && byteLengthOfNameWithTerminatingNull > sizeof(char), "byteLengthOfNameWithTerminatingNull % sizeof(char) == 0 && byteLengthOfNameWithTerminatingNull > sizeof(char)");
+ uint numCharsWithoutNull = (byteLengthOfNameWithTerminatingNull - 1) / sizeof(char);
+
+ if (numCharsWithoutNull == 0)
+ {
+ return String.Empty; // degenerate case
+ }
+
+ // Allocate a string object and write directly into it (CLR team approves of this mechanism).
+ string retVal = new String((char)0, checked((int)numCharsWithoutNull));
+ uint numBytesCopied;
+ fixed (char* pRetVal = retVal)
+ {
+ numBytesCopied = GetProperty(Constants.BCRYPT_ALGORITHM_NAME, pRetVal, byteLengthOfNameWithTerminatingNull);
+ }
+ CryptoUtil.Assert(numBytesCopied == byteLengthOfNameWithTerminatingNull, "numBytesCopied == byteLengthOfNameWithTerminatingNull");
+ return retVal;
+ }
+
+ ///
+ /// Gets the cipher block length (in bytes) of this block cipher algorithm.
+ ///
+ public uint GetCipherBlockLength()
+ {
+ uint cipherBlockLength;
+ uint numBytesCopied = GetProperty(Constants.BCRYPT_BLOCK_LENGTH, &cipherBlockLength, sizeof(uint));
+ CryptoUtil.Assert(numBytesCopied == sizeof(uint), "numBytesCopied == sizeof(uint)");
+ return cipherBlockLength;
+ }
+
+ ///
+ /// Gets the hash block length (in bytes) of this hash algorithm.
+ ///
+ public uint GetHashBlockLength()
+ {
+ uint hashBlockLength;
+ uint numBytesCopied = GetProperty(Constants.BCRYPT_HASH_BLOCK_LENGTH, &hashBlockLength, sizeof(uint));
+ CryptoUtil.Assert(numBytesCopied == sizeof(uint), "numBytesCopied == sizeof(uint)");
+ return hashBlockLength;
+ }
+
+ ///
+ /// Gets the key lengths (in bits) supported by this algorithm.
+ ///
+ public BCRYPT_KEY_LENGTHS_STRUCT GetSupportedKeyLengths()
+ {
+ BCRYPT_KEY_LENGTHS_STRUCT supportedKeyLengths;
+ uint numBytesCopied = GetProperty(Constants.BCRYPT_KEY_LENGTHS, &supportedKeyLengths, (uint)sizeof(BCRYPT_KEY_LENGTHS_STRUCT));
+ CryptoUtil.Assert(numBytesCopied == sizeof(BCRYPT_KEY_LENGTHS_STRUCT), "numBytesCopied == sizeof(BCRYPT_KEY_LENGTHS_STRUCT)");
+ return supportedKeyLengths;
+ }
+
+ ///
+ /// Gets the digest length (in bytes) of this hash algorithm provider.
+ ///
+ public uint GetHashDigestLength()
+ {
+ uint digestLength;
+ uint numBytesCopied = GetProperty(Constants.BCRYPT_HASH_LENGTH, &digestLength, sizeof(uint));
+ CryptoUtil.Assert(numBytesCopied == sizeof(uint), "numBytesCopied == sizeof(uint)");
+ return digestLength;
+ }
+
+ public static BCryptAlgorithmHandle OpenAlgorithmHandle(string algorithmId, string implementation = null, bool hmac = false)
+ {
+ // from bcrypt.h
+ const uint BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x00000008;
+
+ // from ntstatus.h
+ const int STATUS_NOT_FOUND = unchecked((int)0xC0000225);
+
+ BCryptAlgorithmHandle algHandle;
+ int ntstatus = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, algorithmId, implementation, dwFlags: (hmac) ? BCRYPT_ALG_HANDLE_HMAC_FLAG : 0);
+
+ // error checking
+ if (ntstatus == STATUS_NOT_FOUND)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, Resources.BCryptAlgorithmHandle_ProviderNotFound, algorithmId);
+ throw new CryptographicException(message);
+ }
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(algHandle);
+
+ return algHandle;
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return (UnsafeNativeMethods.BCryptCloseAlgorithmProvider(handle, dwFlags: 0) == 0);
+ }
+
+ public void SetChainingMode(string chainingMode)
+ {
+ fixed (char* pszChainingMode = chainingMode ?? String.Empty)
+ {
+ SetProperty(Constants.BCRYPT_CHAINING_MODE, pszChainingMode, checked((uint)(chainingMode.Length + 1 /* null terminator */) * sizeof(char)));
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHandle.cs
new file mode 100644
index 0000000000..66b2c1dbd4
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHandle.cs
@@ -0,0 +1,30 @@
+// 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.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ internal unsafe abstract class BCryptHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ protected BCryptHandle()
+ : base(ownsHandle: true)
+ {
+ }
+
+ protected uint GetProperty(string pszProperty, void* pbOutput, uint cbOutput)
+ {
+ uint retVal;
+ int ntstatus = UnsafeNativeMethods.BCryptGetProperty(this, pszProperty, pbOutput, cbOutput, out retVal, dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ return retVal;
+ }
+
+ protected void SetProperty(string pszProperty, void* pbInput, uint cbInput)
+ {
+ int ntstatus = UnsafeNativeMethods.BCryptSetProperty(this, pszProperty, pbInput, cbInput, dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHashHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHashHandle.cs
new file mode 100644
index 0000000000..dace0f23ae
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHashHandle.cs
@@ -0,0 +1,71 @@
+// 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.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ internal unsafe sealed class BCryptHashHandle : BCryptHandle
+ {
+ private BCryptAlgorithmHandle _algProviderHandle;
+
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptHashHandle() { }
+
+ ///
+ /// Duplicates this hash handle, including any existing hashed state.
+ ///
+ public BCryptHashHandle DuplicateHash()
+ {
+ BCryptHashHandle duplicateHandle;
+ int ntstatus = UnsafeNativeMethods.BCryptDuplicateHash(this, out duplicateHandle, IntPtr.Zero, 0, 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(duplicateHandle);
+
+ duplicateHandle._algProviderHandle = this._algProviderHandle;
+ return duplicateHandle;
+ }
+
+ ///
+ /// Calculates the cryptographic hash over a set of input data.
+ ///
+ public void HashData(byte* pbInput, uint cbInput, byte* pbHashDigest, uint cbHashDigest)
+ {
+ int ntstatus;
+ if (cbInput > 0)
+ {
+ ntstatus = UnsafeNativeMethods.BCryptHashData(
+ hHash: this,
+ pbInput: pbInput,
+ cbInput: cbInput,
+ dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+
+ ntstatus = UnsafeNativeMethods.BCryptFinishHash(
+ hHash: this,
+ pbOutput: pbHashDigest,
+ cbOutput: cbHashDigest,
+ dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return (UnsafeNativeMethods.BCryptDestroyHash(handle) == 0);
+ }
+
+ // We don't actually need to hold a reference to the algorithm handle, as the native CNG library
+ // already holds the reference for us. But once we create a hash from an algorithm provider, odds
+ // are good that we'll create another hash from the same algorithm provider at some point in the
+ // future. And since algorithm providers are expensive to create, we'll hold a strong reference
+ // to all known in-use providers. This way the cached algorithm provider handles utility class
+ // doesn't keep creating providers over and over.
+ internal void SetAlgorithmProviderHandle(BCryptAlgorithmHandle algProviderHandle)
+ {
+ _algProviderHandle = algProviderHandle;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptKeyHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptKeyHandle.cs
new file mode 100644
index 0000000000..cd7d05f8e3
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptKeyHandle.cs
@@ -0,0 +1,33 @@
+// 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.Cryptography.SafeHandles
+{
+ internal sealed class BCryptKeyHandle : BCryptHandle
+ {
+ private BCryptAlgorithmHandle _algProviderHandle;
+
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptKeyHandle() { }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ _algProviderHandle = null;
+ return (UnsafeNativeMethods.BCryptDestroyKey(handle) == 0);
+ }
+
+ // We don't actually need to hold a reference to the algorithm handle, as the native CNG library
+ // already holds the reference for us. But once we create a key from an algorithm provider, odds
+ // are good that we'll create another key from the same algorithm provider at some point in the
+ // future. And since algorithm providers are expensive to create, we'll hold a strong reference
+ // to all known in-use providers. This way the cached algorithm provider handles utility class
+ // doesn't keep creating providers over and over.
+ internal void SetAlgorithmProviderHandle(BCryptAlgorithmHandle algProviderHandle)
+ {
+ _algProviderHandle = algProviderHandle;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/LocalAllocHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/LocalAllocHandle.cs
new file mode 100644
index 0000000000..852c5d1594
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/LocalAllocHandle.cs
@@ -0,0 +1,26 @@
+// 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.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ ///
+ /// Represents a handle returned by LocalAlloc.
+ ///
+ internal class LocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ // Called by P/Invoke when returning SafeHandles
+ protected LocalAllocHandle()
+ : base(ownsHandle: true) { }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ Marshal.FreeHGlobal(handle); // actually calls LocalFree
+ return true;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/NCryptDescriptorHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/NCryptDescriptorHandle.cs
new file mode 100644
index 0000000000..3a181cf06b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/NCryptDescriptorHandle.cs
@@ -0,0 +1,42 @@
+// 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.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ internal unsafe sealed class NCryptDescriptorHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ private NCryptDescriptorHandle()
+ : base(ownsHandle: true)
+ {
+ }
+
+ public string GetProtectionDescriptorRuleString()
+ {
+ // from ncryptprotect.h
+ const int NCRYPT_PROTECTION_INFO_TYPE_DESCRIPTOR_STRING = 0x00000001;
+
+ LocalAllocHandle ruleStringHandle;
+ int ntstatus = UnsafeNativeMethods.NCryptGetProtectionDescriptorInfo(
+ hDescriptor: this,
+ pMemPara: IntPtr.Zero,
+ dwInfoType: NCRYPT_PROTECTION_INFO_TYPE_DESCRIPTOR_STRING,
+ ppvInfo: out ruleStringHandle);
+ UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(ruleStringHandle);
+
+ using (ruleStringHandle)
+ {
+ return new String((char*)ruleStringHandle.DangerousGetHandle());
+ }
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return (UnsafeNativeMethods.NCryptCloseProtectionDescriptor(handle) == 0);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SafeLibraryHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SafeLibraryHandle.cs
new file mode 100644
index 0000000000..ccd0b99c79
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SafeLibraryHandle.cs
@@ -0,0 +1,176 @@
+// 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.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ ///
+ /// Represents a handle to a Windows module (DLL).
+ ///
+ internal unsafe sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ // Called by P/Invoke when returning SafeHandles
+ private SafeLibraryHandle()
+ : base(ownsHandle: true)
+ { }
+
+ ///
+ /// Returns a value stating whether the library exports a given proc.
+ ///
+ public bool DoesProcExist(string lpProcName)
+ {
+ IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName);
+ return (pfnProc != IntPtr.Zero);
+ }
+
+ ///
+ /// Forbids this library from being unloaded. The library will remain loaded until process termination,
+ /// regardless of how many times FreeLibrary is called.
+ ///
+ public void ForbidUnload()
+ {
+ // from winbase.h
+ const uint GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004U;
+ const uint GET_MODULE_HANDLE_EX_FLAG_PIN = 0x00000001U;
+
+ IntPtr unused;
+ bool retVal = UnsafeNativeMethods.GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, this, out unused);
+ if (!retVal)
+ {
+ UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
+ }
+ }
+
+ ///
+ /// Formats a message string using the resource table in the specified library.
+ ///
+ public string FormatMessage(int messageId)
+ {
+ // from winbase.h
+ const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
+ const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
+ const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
+ const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
+
+ LocalAllocHandle messageHandle;
+ int numCharsOutput = UnsafeNativeMethods.FormatMessage(
+ dwFlags: FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ lpSource: this,
+ dwMessageId: (uint)messageId,
+ dwLanguageId: 0 /* ignore current culture */,
+ lpBuffer: out messageHandle,
+ nSize: 0 /* unused */,
+ Arguments: IntPtr.Zero /* unused */);
+
+ if (numCharsOutput != 0 && messageHandle != null && !messageHandle.IsInvalid)
+ {
+ // Successfully retrieved the message.
+ using (messageHandle)
+ {
+ return new String((char*)messageHandle.DangerousGetHandle(), 0, numCharsOutput).Trim();
+ }
+ }
+ else
+ {
+ // Message not found - that's fine.
+ return null;
+ }
+ }
+
+ ///
+ /// Gets a delegate pointing to a given export from this library.
+ ///
+ public TDelegate GetProcAddress(string lpProcName, bool throwIfNotFound = true) where TDelegate : class
+ {
+ IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName);
+ if (pfnProc == IntPtr.Zero)
+ {
+ if (throwIfNotFound)
+ {
+ UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ return Marshal.GetDelegateForFunctionPointer(pfnProc);
+ }
+
+ ///
+ /// Opens a library. If 'filename' is not a fully-qualified path, the default search path is used.
+ ///
+ public static SafeLibraryHandle Open(string filename)
+ {
+ const uint LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800U; // from libloaderapi.h
+
+ SafeLibraryHandle handle = UnsafeNativeMethods.LoadLibraryEx(filename, IntPtr.Zero, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (handle == null || handle.IsInvalid)
+ {
+ UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
+ }
+ return handle;
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return UnsafeNativeMethods.FreeLibrary(handle);
+ }
+
+ [SuppressUnmanagedCodeSecurity]
+ private static class UnsafeNativeMethods
+ {
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx
+ [DllImport("kernel32.dll", EntryPoint = "FormatMessageW", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern int FormatMessage(
+ [In] uint dwFlags,
+ [In] SafeLibraryHandle lpSource,
+ [In] uint dwMessageId,
+ [In] uint dwLanguageId,
+ [Out] out LocalAllocHandle lpBuffer,
+ [In] uint nSize,
+ [In] IntPtr Arguments
+ );
+
+ // http://msdn.microsoft.com/en-us/library/ms683152(v=vs.85).aspx
+ [return: MarshalAs(UnmanagedType.Bool)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
+ internal static extern bool FreeLibrary(IntPtr hModule);
+
+ // http://msdn.microsoft.com/en-us/library/ms683200(v=vs.85).aspx
+ [return: MarshalAs(UnmanagedType.Bool)]
+ [DllImport("kernel32.dll", EntryPoint = "GetModuleHandleExW", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ internal static extern bool GetModuleHandleEx(
+ [In] uint dwFlags,
+ [In] SafeLibraryHandle lpModuleName, // can point to a location within the module if GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS is set
+ [Out] out IntPtr phModule);
+
+ // http://msdn.microsoft.com/en-us/library/ms683212(v=vs.85).aspx
+ [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ internal static extern IntPtr GetProcAddress(
+ [In] SafeLibraryHandle hModule,
+ [In, MarshalAs(UnmanagedType.LPStr)] string lpProcName);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx
+ [DllImport("kernel32.dll", EntryPoint = "LoadLibraryExW", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ internal static extern SafeLibraryHandle LoadLibraryEx(
+ [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
+ [In] IntPtr hFile,
+ [In] uint dwFlags);
+
+ internal static void ThrowExceptionForLastWin32Error()
+ {
+ int hr = Marshal.GetHRForLastWin32Error();
+ Marshal.ThrowExceptionForHR(hr);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SecureLocalAllocHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SecureLocalAllocHandle.cs
new file mode 100644
index 0000000000..ac1f3c6172
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SecureLocalAllocHandle.cs
@@ -0,0 +1,61 @@
+// 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.Runtime.InteropServices;
+using System.Runtime.ConstrainedExecution;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ ///
+ /// Represents a handle returned by LocalAlloc.
+ /// The memory will be zeroed out before it's freed.
+ ///
+ internal unsafe sealed class SecureLocalAllocHandle : LocalAllocHandle
+ {
+ private readonly IntPtr _cb;
+
+ private SecureLocalAllocHandle(IntPtr cb)
+ {
+ _cb = cb;
+ }
+
+ public IntPtr Length
+ {
+ get
+ {
+ return _cb;
+ }
+ }
+
+ ///
+ /// Allocates some amount of memory using LocalAlloc.
+ ///
+ public static SecureLocalAllocHandle Allocate(IntPtr cb)
+ {
+ SecureLocalAllocHandle newHandle = new SecureLocalAllocHandle(cb);
+ newHandle.AllocateImpl(cb);
+ return newHandle;
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ private void AllocateImpl(IntPtr cb)
+ {
+ handle = Marshal.AllocHGlobal(cb); // actually calls LocalAlloc
+ }
+
+ public SecureLocalAllocHandle Duplicate()
+ {
+ SecureLocalAllocHandle duplicateHandle = Allocate(_cb);
+ UnsafeBufferUtil.BlockCopy(from: this, to: duplicateHandle, length: _cb);
+ return duplicateHandle;
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ UnsafeBufferUtil.SecureZeroMemory((byte*)handle, _cb); // compiler won't optimize this away
+ return base.ReleaseHandle();
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeBufferUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeBufferUtil.cs
new file mode 100644
index 0000000000..681adb8bc3
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeBufferUtil.cs
@@ -0,0 +1,179 @@
+// 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.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Threading;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ internal unsafe static class UnsafeBufferUtil
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void BlockCopy(void* from, void* to, int byteCount)
+ {
+ BlockCopy(from, to, checked((uint)byteCount)); // will be checked before invoking the delegate
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void BlockCopy(void* from, void* to, uint byteCount)
+ {
+ if (byteCount != 0)
+ {
+ BlockCopyCore((byte*)from, (byte*)to, byteCount);
+ }
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public static void BlockCopy(LocalAllocHandle from, void* to, uint byteCount)
+ {
+ bool refAdded = false;
+ try
+ {
+ from.DangerousAddRef(ref refAdded);
+ BlockCopy((void*)from.DangerousGetHandle(), to, byteCount);
+ }
+ finally
+ {
+ if (refAdded)
+ {
+ from.DangerousRelease();
+ }
+ }
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public static void BlockCopy(void* from, LocalAllocHandle to, uint byteCount)
+ {
+ bool refAdded = false;
+ try
+ {
+ to.DangerousAddRef(ref refAdded);
+ BlockCopy(from, (void*)to.DangerousGetHandle(), byteCount);
+ }
+ finally
+ {
+ if (refAdded)
+ {
+ to.DangerousRelease();
+ }
+ }
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public static void BlockCopy(LocalAllocHandle from, LocalAllocHandle to, IntPtr length)
+ {
+ if (length == IntPtr.Zero)
+ {
+ return;
+ }
+
+ bool fromRefAdded = false;
+ bool toRefAdded = false;
+ try
+ {
+ from.DangerousAddRef(ref fromRefAdded);
+ to.DangerousAddRef(ref toRefAdded);
+ if (sizeof(IntPtr) == 4)
+ {
+ BlockCopyCore(from: (byte*)from.DangerousGetHandle(), to: (byte*)to.DangerousGetHandle(), byteCount: (uint)length.ToInt32());
+ }
+ else
+ {
+ BlockCopyCore(from: (byte*)from.DangerousGetHandle(), to: (byte*)to.DangerousGetHandle(), byteCount: (ulong)length.ToInt64());
+ }
+ }
+ finally
+ {
+ if (fromRefAdded)
+ {
+ from.DangerousRelease();
+ }
+ if (toRefAdded)
+ {
+ to.DangerousRelease();
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void BlockCopyCore(byte* from, byte* to, uint byteCount)
+ {
+ Buffer.MemoryCopy(from, to, (ulong)byteCount, (ulong)byteCount);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void BlockCopyCore(byte* from, byte* to, ulong byteCount)
+ {
+ Buffer.MemoryCopy(from, to, byteCount, byteCount);
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void SecureZeroMemory(byte* buffer, int byteCount)
+ {
+ SecureZeroMemory(buffer, checked((uint)byteCount));
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void SecureZeroMemory(byte* buffer, uint byteCount)
+ {
+ if (byteCount != 0)
+ {
+ do
+ {
+ buffer[--byteCount] = 0;
+ } while (byteCount != 0);
+
+ // Volatile to make sure the zero-writes don't get optimized away
+ Volatile.Write(ref *buffer, 0);
+ }
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void SecureZeroMemory(byte* buffer, ulong byteCount)
+ {
+ if (byteCount != 0)
+ {
+ do
+ {
+ buffer[--byteCount] = 0;
+ } while (byteCount != 0);
+
+ // Volatile to make sure the zero-writes don't get optimized away
+ Volatile.Write(ref *buffer, 0);
+ }
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void SecureZeroMemory(byte* buffer, IntPtr length)
+ {
+ if (sizeof(IntPtr) == 4)
+ {
+ SecureZeroMemory(buffer, (uint)length.ToInt32());
+ }
+ else
+ {
+ SecureZeroMemory(buffer, (ulong)length.ToInt64());
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeNativeMethods.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeNativeMethods.cs
new file mode 100644
index 0000000000..3a5a4d8db3
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeNativeMethods.cs
@@ -0,0 +1,346 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Cryptography;
+using System.Threading;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ [SuppressUnmanagedCodeSecurity]
+ internal unsafe static class UnsafeNativeMethods
+ {
+ private const string BCRYPT_LIB = "bcrypt.dll";
+ private static readonly Lazy _lazyBCryptLibHandle = GetLazyLibraryHandle(BCRYPT_LIB);
+
+ private const string CRYPT32_LIB = "crypt32.dll";
+ private static readonly Lazy _lazyCrypt32LibHandle = GetLazyLibraryHandle(CRYPT32_LIB);
+
+ private const string NCRYPT_LIB = "ncrypt.dll";
+ private static readonly Lazy _lazyNCryptLibHandle = GetLazyLibraryHandle(NCRYPT_LIB);
+
+ private static Lazy GetLazyLibraryHandle(string libraryName)
+ {
+ // We don't need to worry about race conditions: SafeLibraryHandle will clean up after itself
+ return new Lazy(() => SafeLibraryHandle.Open(libraryName), LazyThreadSafetyMode.PublicationOnly);
+ }
+
+ /*
+ * BCRYPT.DLL
+ */
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375377(v=vs.85).aspx
+ internal static extern int BCryptCloseAlgorithmProvider(
+ [In] IntPtr hAlgorithm,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375383(v=vs.85).aspx
+ internal static extern int BCryptCreateHash(
+ [In] BCryptAlgorithmHandle hAlgorithm,
+ [Out] out BCryptHashHandle phHash,
+ [In] IntPtr pbHashObject,
+ [In] uint cbHashObject,
+ [In] byte* pbSecret,
+ [In] uint cbSecret,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375391(v=vs.85).aspx
+ internal static extern int BCryptDecrypt(
+ [In] BCryptKeyHandle hKey,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] void* pPaddingInfo,
+ [In] byte* pbIV,
+ [In] uint cbIV,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [Out] out uint pcbResult,
+ [In] BCryptEncryptFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/dd433795(v=vs.85).aspx
+ internal static extern int BCryptDeriveKeyPBKDF2(
+ [In] BCryptAlgorithmHandle hPrf,
+ [In] byte* pbPassword,
+ [In] uint cbPassword,
+ [In] byte* pbSalt,
+ [In] uint cbSalt,
+ [In] ulong cIterations,
+ [In] byte* pbDerivedKey,
+ [In] uint cbDerivedKey,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375399(v=vs.85).aspx
+ internal static extern int BCryptDestroyHash(
+ [In] IntPtr hHash);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375404(v=vs.85).aspx
+ internal static extern int BCryptDestroyKey(
+ [In] IntPtr hKey);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375413(v=vs.85).aspx
+ internal static extern int BCryptDuplicateHash(
+ [In] BCryptHashHandle hHash,
+ [Out] out BCryptHashHandle phNewHash,
+ [In] IntPtr pbHashObject,
+ [In] uint cbHashObject,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375421(v=vs.85).aspx
+ internal static extern int BCryptEncrypt(
+ [In] BCryptKeyHandle hKey,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] void* pPaddingInfo,
+ [In] byte* pbIV,
+ [In] uint cbIV,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [Out] out uint pcbResult,
+ [In] BCryptEncryptFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375443(v=vs.85).aspx
+ internal static extern int BCryptFinishHash(
+ [In] BCryptHashHandle hHash,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375453(v=vs.85).aspx
+ internal static extern int BCryptGenerateSymmetricKey(
+ [In] BCryptAlgorithmHandle hAlgorithm,
+ [Out] out BCryptKeyHandle phKey,
+ [In] IntPtr pbKeyObject,
+ [In] uint cbKeyObject,
+ [In] byte* pbSecret,
+ [In] uint cbSecret,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375458(v=vs.85).aspx
+ internal static extern int BCryptGenRandom(
+ [In] IntPtr hAlgorithm,
+ [In] byte* pbBuffer,
+ [In] uint cbBuffer,
+ [In] BCryptGenRandomFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375464(v=vs.85).aspx
+ internal static extern int BCryptGetProperty(
+ [In] BCryptHandle hObject,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszProperty,
+ [In] void* pbOutput,
+ [In] uint cbOutput,
+ [Out] out uint pcbResult,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375468(v=vs.85).aspx
+ internal static extern int BCryptHashData(
+ [In] BCryptHashHandle hHash,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh448506(v=vs.85).aspx
+ internal static extern int BCryptKeyDerivation(
+ [In] BCryptKeyHandle hKey,
+ [In] BCryptBufferDesc* pParameterList,
+ [In] byte* pbDerivedKey,
+ [In] uint cbDerivedKey,
+ [Out] out uint pcbResult,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375479(v=vs.85).aspx
+ internal static extern int BCryptOpenAlgorithmProvider(
+ [Out] out BCryptAlgorithmHandle phAlgorithm,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszAlgId,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszImplementation,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375504(v=vs.85).aspx
+ internal static extern int BCryptSetProperty(
+ [In] BCryptHandle hObject,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszProperty,
+ [In] void* pbInput,
+ [In] uint cbInput,
+ [In] uint dwFlags);
+
+ /*
+ * CRYPT32.DLL
+ */
+
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261(v=vs.85).aspx
+ internal static extern bool CryptProtectData(
+ [In] DATA_BLOB* pDataIn,
+ [In] IntPtr szDataDescr,
+ [In] DATA_BLOB* pOptionalEntropy,
+ [In] IntPtr pvReserved,
+ [In] IntPtr pPromptStruct,
+ [In] uint dwFlags,
+ [Out] out DATA_BLOB pDataOut);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380262(v=vs.85).aspx
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ public static extern bool CryptProtectMemory(
+ [In] SafeHandle pData,
+ [In] uint cbData,
+ [In] uint dwFlags);
+
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380882(v=vs.85).aspx
+ internal static extern bool CryptUnprotectData(
+ [In] DATA_BLOB* pDataIn,
+ [In] IntPtr ppszDataDescr,
+ [In] DATA_BLOB* pOptionalEntropy,
+ [In] IntPtr pvReserved,
+ [In] IntPtr pPromptStruct,
+ [In] uint dwFlags,
+ [Out] out DATA_BLOB pDataOut);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380890(v=vs.85).aspx
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ public static extern bool CryptUnprotectMemory(
+ [In] byte* pData,
+ [In] uint cbData,
+ [In] uint dwFlags);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380890(v=vs.85).aspx
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ public static extern bool CryptUnprotectMemory(
+ [In] SafeHandle pData,
+ [In] uint cbData,
+ [In] uint dwFlags);
+
+ /*
+ * NCRYPT.DLL
+ */
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706799(v=vs.85).aspx
+ internal static extern int NCryptCloseProtectionDescriptor(
+ [In] IntPtr hDescriptor);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706800(v=vs.85).aspx
+ internal static extern int NCryptCreateProtectionDescriptor(
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pwszDescriptorString,
+ [In] uint dwFlags,
+ [Out] out NCryptDescriptorHandle phDescriptor);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/hh706801(v=vs.85).aspx
+ internal static extern int NCryptGetProtectionDescriptorInfo(
+ [In] NCryptDescriptorHandle hDescriptor,
+ [In] IntPtr pMemPara,
+ [In] uint dwInfoType,
+ [Out] out LocalAllocHandle ppvInfo);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706802(v=vs.85).aspx
+ internal static extern int NCryptProtectSecret(
+ [In] NCryptDescriptorHandle hDescriptor,
+ [In] uint dwFlags,
+ [In] byte* pbData,
+ [In] uint cbData,
+ [In] IntPtr pMemPara,
+ [In] IntPtr hWnd,
+ [Out] out LocalAllocHandle ppbProtectedBlob,
+ [Out] out uint pcbProtectedBlob);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706811(v=vs.85).aspx
+ internal static extern int NCryptUnprotectSecret(
+ [In] IntPtr phDescriptor,
+ [In] uint dwFlags,
+ [In] byte* pbProtectedBlob,
+ [In] uint cbProtectedBlob,
+ [In] IntPtr pMemPara,
+ [In] IntPtr hWnd,
+ [Out] out LocalAllocHandle ppbData,
+ [Out] out uint pcbData);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706811(v=vs.85).aspx
+ internal static extern int NCryptUnprotectSecret(
+ [Out] out NCryptDescriptorHandle phDescriptor,
+ [In] uint dwFlags,
+ [In] byte* pbProtectedBlob,
+ [In] uint cbProtectedBlob,
+ [In] IntPtr pMemPara,
+ [In] IntPtr hWnd,
+ [Out] out LocalAllocHandle ppbData,
+ [Out] out uint pcbData);
+
+ /*
+ * HELPER FUNCTIONS
+ */
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void ThrowExceptionForBCryptStatus(int ntstatus)
+ {
+ // This wrapper method exists because 'throw' statements won't always be inlined.
+ if (ntstatus != 0)
+ {
+ ThrowExceptionForBCryptStatusImpl(ntstatus);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowExceptionForBCryptStatusImpl(int ntstatus)
+ {
+ string message = _lazyBCryptLibHandle.Value.FormatMessage(ntstatus);
+ throw new CryptographicException(message);
+ }
+
+ public static void ThrowExceptionForLastCrypt32Error()
+ {
+ int lastError = Marshal.GetLastWin32Error();
+ Debug.Assert(lastError != 0, "This method should only be called if there was an error.");
+
+ string message = _lazyCrypt32LibHandle.Value.FormatMessage(lastError);
+ throw new CryptographicException(message);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void ThrowExceptionForNCryptStatus(int ntstatus)
+ {
+ // This wrapper method exists because 'throw' statements won't always be inlined.
+ if (ntstatus != 0)
+ {
+ ThrowExceptionForNCryptStatusImpl(ntstatus);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowExceptionForNCryptStatusImpl(int ntstatus)
+ {
+ string message = _lazyNCryptLibHandle.Value.FormatMessage(ntstatus);
+ throw new CryptographicException(message);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/WeakReferenceHelpers.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/WeakReferenceHelpers.cs
new file mode 100644
index 0000000000..71b77a58e5
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/WeakReferenceHelpers.cs
@@ -0,0 +1,59 @@
+// 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.Diagnostics;
+using System.Threading;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ internal static class WeakReferenceHelpers
+ {
+ public static T GetSharedInstance(ref WeakReference weakReference, Func factory)
+ where T : class, IDisposable
+ {
+ // First, see if the WR already exists and points to a live object.
+ WeakReference existingWeakRef = Volatile.Read(ref weakReference);
+ T newTarget = null;
+ WeakReference newWeakRef = null;
+
+ while (true)
+ {
+ if (existingWeakRef != null)
+ {
+ T existingTarget;
+ if (weakReference.TryGetTarget(out existingTarget))
+ {
+ // If we created a new target on a previous iteration of the loop but we
+ // weren't able to store the target into the desired location, dispose of it now.
+ newTarget?.Dispose();
+ return existingTarget;
+ }
+ }
+
+ // If the existing WR didn't point anywhere useful and this is our
+ // first iteration through the loop, create the new target and WR now.
+ if (newTarget == null)
+ {
+ newTarget = factory();
+ Debug.Assert(newTarget != null);
+ newWeakRef = new WeakReference(newTarget);
+ }
+ Debug.Assert(newWeakRef != null);
+
+ // Try replacing the existing WR with our newly-created one.
+ WeakReference currentWeakRef = Interlocked.CompareExchange(ref weakReference, newWeakRef, existingWeakRef);
+ if (ReferenceEquals(currentWeakRef, existingWeakRef))
+ {
+ // success, 'weakReference' now points to our newly-created WR
+ return newTarget;
+ }
+
+ // If we got to this point, somebody beat us to creating a new WR.
+ // We'll loop around and check it for validity.
+ Debug.Assert(currentWeakRef != null);
+ existingWeakRef = currentWeakRef;
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/baseline.netcore.json
new file mode 100644
index 0000000000..01daa339ee
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/baseline.netcore.json
@@ -0,0 +1,4 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Cryptography.Internal, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": []
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivation.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivation.cs
new file mode 100644
index 0000000000..67ff1ca420
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivation.cs
@@ -0,0 +1,56 @@
+// 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.KeyDerivation.PBKDF2;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation
+{
+ ///
+ /// Provides algorithms for performing key derivation.
+ ///
+ public static class KeyDerivation
+ {
+ ///
+ /// Performs key derivation using the PBKDF2 algorithm.
+ ///
+ /// The password from which to derive the key.
+ /// The salt to be used during the key derivation process.
+ /// The pseudo-random function to be used in the key derivation process.
+ /// The number of iterations of the pseudo-random function to apply
+ /// during the key derivation process.
+ /// The desired length (in bytes) of the derived key.
+ /// The derived key.
+ ///
+ /// The PBKDF2 algorithm is specified in RFC 2898.
+ ///
+ public static byte[] Pbkdf2(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ if (password == null)
+ {
+ throw new ArgumentNullException(nameof(password));
+ }
+
+ if (salt == null)
+ {
+ throw new ArgumentNullException(nameof(salt));
+ }
+
+ // parameter checking
+ if (prf < KeyDerivationPrf.HMACSHA1 || prf > KeyDerivationPrf.HMACSHA512)
+ {
+ throw new ArgumentOutOfRangeException(nameof(prf));
+ }
+ if (iterationCount <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(iterationCount));
+ }
+ if (numBytesRequested <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(numBytesRequested));
+ }
+
+ return Pbkdf2Util.Pbkdf2Provider.DeriveKey(password, salt, prf, iterationCount, numBytesRequested);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivationPrf.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivationPrf.cs
new file mode 100644
index 0000000000..57e740f04b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivationPrf.cs
@@ -0,0 +1,26 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation
+{
+ ///
+ /// Specifies the PRF which should be used for the key derivation algorithm.
+ ///
+ public enum KeyDerivationPrf
+ {
+ ///
+ /// The HMAC algorithm (RFC 2104) using the SHA-1 hash function (FIPS 180-4).
+ ///
+ HMACSHA1,
+
+ ///
+ /// The HMAC algorithm (RFC 2104) using the SHA-256 hash function (FIPS 180-4).
+ ///
+ HMACSHA256,
+
+ ///
+ /// The HMAC algorithm (RFC 2104) using the SHA-512 hash function (FIPS 180-4).
+ ///
+ HMACSHA512,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj
new file mode 100644
index 0000000000..70205f1754
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj
@@ -0,0 +1,15 @@
+
+
+
+ ASP.NET Core utilities for key derivation.
+ netstandard2.0;netcoreapp2.0
+ true
+ true
+ aspnetcore;dataprotection
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/IPbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/IPbkdf2Provider.cs
new file mode 100644
index 0000000000..8be8a5e809
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/IPbkdf2Provider.cs
@@ -0,0 +1,15 @@
+// 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.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// Internal interface used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
+ ///
+ internal interface IPbkdf2Provider
+ {
+ byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/ManagedPbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/ManagedPbkdf2Provider.cs
new file mode 100644
index 0000000000..bf81ae65c5
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/ManagedPbkdf2Provider.cs
@@ -0,0 +1,103 @@
+// 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.Diagnostics;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// A PBKDF2 provider which utilizes the managed hash algorithm classes as PRFs.
+ /// This isn't the preferred provider since the implementation is slow, but it is provided as a fallback.
+ ///
+ internal sealed class ManagedPbkdf2Provider : IPbkdf2Provider
+ {
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ // PBKDF2 is defined in NIST SP800-132, Sec. 5.3.
+ // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
+
+ byte[] retVal = new byte[numBytesRequested];
+ int numBytesWritten = 0;
+ int numBytesRemaining = numBytesRequested;
+
+ // For each block index, U_0 := Salt || block_index
+ byte[] saltWithBlockIndex = new byte[checked(salt.Length + sizeof(uint))];
+ Buffer.BlockCopy(salt, 0, saltWithBlockIndex, 0, salt.Length);
+
+ using (var hashAlgorithm = PrfToManagedHmacAlgorithm(prf, password))
+ {
+ for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++)
+ {
+ // write the block index out as big-endian
+ saltWithBlockIndex[saltWithBlockIndex.Length - 4] = (byte)(blockIndex >> 24);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 3] = (byte)(blockIndex >> 16);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 2] = (byte)(blockIndex >> 8);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 1] = (byte)blockIndex;
+
+ // U_1 = PRF(U_0) = PRF(Salt || block_index)
+ // T_blockIndex = U_1
+ byte[] U_iter = hashAlgorithm.ComputeHash(saltWithBlockIndex); // this is U_1
+ byte[] T_blockIndex = U_iter;
+
+ for (int iter = 1; iter < iterationCount; iter++)
+ {
+ U_iter = hashAlgorithm.ComputeHash(U_iter);
+ XorBuffers(src: U_iter, dest: T_blockIndex);
+ // At this point, the 'U_iter' variable actually contains U_{iter+1} (due to indexing differences).
+ }
+
+ // At this point, we're done iterating on this block, so copy the transformed block into retVal.
+ int numBytesToCopy = Math.Min(numBytesRemaining, T_blockIndex.Length);
+ Buffer.BlockCopy(T_blockIndex, 0, retVal, numBytesWritten, numBytesToCopy);
+ numBytesWritten += numBytesToCopy;
+ numBytesRemaining -= numBytesToCopy;
+ }
+ }
+
+ // retVal := T_1 || T_2 || ... || T_n, where T_n may be truncated to meet the desired output length
+ return retVal;
+ }
+
+ private static KeyedHashAlgorithm PrfToManagedHmacAlgorithm(KeyDerivationPrf prf, string password)
+ {
+ byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
+ try
+ {
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ return new HMACSHA1(passwordBytes);
+ case KeyDerivationPrf.HMACSHA256:
+ return new HMACSHA256(passwordBytes);
+ case KeyDerivationPrf.HMACSHA512:
+ return new HMACSHA512(passwordBytes);
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+ }
+ finally
+ {
+ // The HMAC ctor makes a duplicate of this key; we clear original buffer to limit exposure to the GC.
+ Array.Clear(passwordBytes, 0, passwordBytes.Length);
+ }
+ }
+
+ private static void XorBuffers(byte[] src, byte[] dest)
+ {
+ // Note: dest buffer is mutated.
+ Debug.Assert(src.Length == dest.Length);
+ for (int i = 0; i < src.Length; i++)
+ {
+ dest[i] ^= src[i];
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/NetCorePbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/NetCorePbkdf2Provider.cs
new file mode 100644
index 0000000000..2aaf445dda
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/NetCorePbkdf2Provider.cs
@@ -0,0 +1,71 @@
+// 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 NETCOREAPP2_0
+// Rfc2898DeriveBytes in .NET Standard 2.0 only supports SHA1
+
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// Implements Pbkdf2 using .
+ ///
+ internal sealed class NetCorePbkdf2Provider : IPbkdf2Provider
+ {
+ private static readonly ManagedPbkdf2Provider _fallbackProvider = new ManagedPbkdf2Provider();
+
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ if (salt.Length < 8)
+ {
+ // Rfc2898DeriveBytes enforces the 8 byte recommendation.
+ // To maintain compatibility, we call into ManagedPbkdf2Provider for salts shorter than 8 bytes
+ // because we can't use Rfc2898DeriveBytes with this salt.
+ return _fallbackProvider.DeriveKey(password, salt, prf, iterationCount, numBytesRequested);
+ }
+ else
+ {
+ return DeriveKeyImpl(password, salt, prf, iterationCount, numBytesRequested);
+ }
+ }
+
+ private static byte[] DeriveKeyImpl(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ HashAlgorithmName algorithmName;
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ algorithmName = HashAlgorithmName.SHA1;
+ break;
+ case KeyDerivationPrf.HMACSHA256:
+ algorithmName = HashAlgorithmName.SHA256;
+ break;
+ case KeyDerivationPrf.HMACSHA512:
+ algorithmName = HashAlgorithmName.SHA512;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ var passwordBytes = Encoding.UTF8.GetBytes(password);
+ using (var rfc = new Rfc2898DeriveBytes(passwordBytes, salt, iterationCount, algorithmName))
+ {
+ return rfc.GetBytes(numBytesRequested);
+ }
+ }
+ }
+}
+
+#elif NETSTANDARD2_0
+#else
+#error Update target frameworks
+#endif
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Pbkdf2Util.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Pbkdf2Util.cs
new file mode 100644
index 0000000000..f7c99c4bcb
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Pbkdf2Util.cs
@@ -0,0 +1,46 @@
+// 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.Cryptography.Cng;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// Internal base class used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
+ ///
+ internal static class Pbkdf2Util
+ {
+ public static readonly IPbkdf2Provider Pbkdf2Provider = GetPbkdf2Provider();
+
+ private static IPbkdf2Provider GetPbkdf2Provider()
+ {
+ // In priority order, our three implementations are Win8, Win7, and "other".
+ if (OSVersionUtil.IsWindows8OrLater())
+ {
+ // fastest implementation
+ return new Win8Pbkdf2Provider();
+ }
+ else if (OSVersionUtil.IsWindows())
+ {
+ // acceptable implementation
+ return new Win7Pbkdf2Provider();
+ }
+#if NETCOREAPP2_0
+ else
+ {
+ // fastest implementation on .NET Core for Linux/macOS.
+ // Not supported on .NET Framework
+ return new NetCorePbkdf2Provider();
+ }
+#elif NETSTANDARD2_0
+ else
+ {
+ // slowest implementation
+ return new ManagedPbkdf2Provider();
+ }
+#else
+#error Update target frameworks
+#endif
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win7Pbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win7Pbkdf2Provider.cs
new file mode 100644
index 0000000000..4c359b80f4
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win7Pbkdf2Provider.cs
@@ -0,0 +1,100 @@
+// 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.Diagnostics;
+using System.Text;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// A PBKDF2 provider which utilizes the Win7 API BCryptDeriveKeyPBKDF2.
+ ///
+ internal unsafe sealed class Win7Pbkdf2Provider : IPbkdf2Provider
+ {
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers
+
+ // Don't dispose of this algorithm instance; it is cached and reused!
+ var algHandle = PrfToCachedCngAlgorithmInstance(prf);
+
+ // Convert password string to bytes.
+ // Allocate on the stack whenever we can to save allocations.
+ int cbPasswordBuffer = Encoding.UTF8.GetMaxByteCount(password.Length);
+ fixed (byte* pbHeapAllocatedPasswordBuffer = (cbPasswordBuffer > Constants.MAX_STACKALLOC_BYTES) ? new byte[cbPasswordBuffer] : null)
+ {
+ byte* pbPasswordBuffer = pbHeapAllocatedPasswordBuffer;
+ if (pbPasswordBuffer == null)
+ {
+ if (cbPasswordBuffer == 0)
+ {
+ pbPasswordBuffer = &dummy;
+ }
+ else
+ {
+ byte* pbStackAllocPasswordBuffer = stackalloc byte[cbPasswordBuffer]; // will be released when the frame unwinds
+ pbPasswordBuffer = pbStackAllocPasswordBuffer;
+ }
+ }
+
+ try
+ {
+ int cbPasswordBufferUsed; // we're not filling the entire buffer, just a partial buffer
+ fixed (char* pszPassword = password)
+ {
+ cbPasswordBufferUsed = Encoding.UTF8.GetBytes(pszPassword, password.Length, pbPasswordBuffer, cbPasswordBuffer);
+ }
+
+ fixed (byte* pbHeapAllocatedSalt = salt)
+ {
+ byte* pbSalt = (pbHeapAllocatedSalt != null) ? pbHeapAllocatedSalt : &dummy;
+
+ byte[] retVal = new byte[numBytesRequested];
+ fixed (byte* pbRetVal = retVal)
+ {
+ int ntstatus = UnsafeNativeMethods.BCryptDeriveKeyPBKDF2(
+ hPrf: algHandle,
+ pbPassword: pbPasswordBuffer,
+ cbPassword: (uint)cbPasswordBufferUsed,
+ pbSalt: pbSalt,
+ cbSalt: (uint)salt.Length,
+ cIterations: (ulong)iterationCount,
+ pbDerivedKey: pbRetVal,
+ cbDerivedKey: (uint)retVal.Length,
+ dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+ return retVal;
+ }
+ }
+ finally
+ {
+ UnsafeBufferUtil.SecureZeroMemory(pbPasswordBuffer, cbPasswordBuffer);
+ }
+ }
+ }
+
+ private static BCryptAlgorithmHandle PrfToCachedCngAlgorithmInstance(KeyDerivationPrf prf)
+ {
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ return CachedAlgorithmHandles.HMAC_SHA1;
+ case KeyDerivationPrf.HMACSHA256:
+ return CachedAlgorithmHandles.HMAC_SHA256;
+ case KeyDerivationPrf.HMACSHA512:
+ return CachedAlgorithmHandles.HMAC_SHA512;
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win8Pbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win8Pbkdf2Provider.cs
new file mode 100644
index 0000000000..296e85b7dd
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win8Pbkdf2Provider.cs
@@ -0,0 +1,211 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// A PBKDF2 provider which utilizes the Win8 API BCryptKeyDerivation.
+ ///
+ internal unsafe sealed class Win8Pbkdf2Provider : IPbkdf2Provider
+ {
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ string algorithmName = PrfToCngAlgorithmId(prf);
+ fixed (byte* pbHeapAllocatedSalt = salt)
+ {
+ byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers
+ byte* pbSalt = (pbHeapAllocatedSalt != null) ? pbHeapAllocatedSalt : &dummy;
+
+ byte[] retVal = new byte[numBytesRequested];
+ using (BCryptKeyHandle keyHandle = PasswordToPbkdfKeyHandle(password, CachedAlgorithmHandles.PBKDF2, prf))
+ {
+ fixed (byte* pbRetVal = retVal)
+ {
+ DeriveKeyCore(keyHandle, algorithmName, pbSalt, (uint)salt.Length, (ulong)iterationCount, pbRetVal, (uint)retVal.Length);
+ }
+ return retVal;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint GetTotalByteLengthIncludingNullTerminator(string input)
+ {
+ if (input == null)
+ {
+ // degenerate case
+ return 0;
+ }
+ else
+ {
+ uint numChars = (uint)input.Length + 1U; // no overflow check necessary since Length is signed
+ return checked(numChars * sizeof(char));
+ }
+ }
+
+ private static BCryptKeyHandle PasswordToPbkdfKeyHandle(string password, BCryptAlgorithmHandle pbkdf2AlgHandle, KeyDerivationPrf prf)
+ {
+ byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers
+
+ // Convert password string to bytes.
+ // Allocate on the stack whenever we can to save allocations.
+ int cbPasswordBuffer = Encoding.UTF8.GetMaxByteCount(password.Length);
+ fixed (byte* pbHeapAllocatedPasswordBuffer = (cbPasswordBuffer > Constants.MAX_STACKALLOC_BYTES) ? new byte[cbPasswordBuffer] : null)
+ {
+ byte* pbPasswordBuffer = pbHeapAllocatedPasswordBuffer;
+ if (pbPasswordBuffer == null)
+ {
+ if (cbPasswordBuffer == 0)
+ {
+ pbPasswordBuffer = &dummy;
+ }
+ else
+ {
+ byte* pbStackAllocPasswordBuffer = stackalloc byte[cbPasswordBuffer]; // will be released when the frame unwinds
+ pbPasswordBuffer = pbStackAllocPasswordBuffer;
+ }
+ }
+
+ try
+ {
+ int cbPasswordBufferUsed; // we're not filling the entire buffer, just a partial buffer
+ fixed (char* pszPassword = password)
+ {
+ cbPasswordBufferUsed = Encoding.UTF8.GetBytes(pszPassword, password.Length, pbPasswordBuffer, cbPasswordBuffer);
+ }
+
+ return PasswordToPbkdfKeyHandleStep2(pbkdf2AlgHandle, pbPasswordBuffer, (uint)cbPasswordBufferUsed, prf);
+ }
+ finally
+ {
+ UnsafeBufferUtil.SecureZeroMemory(pbPasswordBuffer, cbPasswordBuffer);
+ }
+ }
+ }
+
+ private static BCryptKeyHandle PasswordToPbkdfKeyHandleStep2(BCryptAlgorithmHandle pbkdf2AlgHandle, byte* pbPassword, uint cbPassword, KeyDerivationPrf prf)
+ {
+ const uint PBKDF2_MAX_KEYLENGTH_IN_BYTES = 2048; // GetSupportedKeyLengths() on a Win8 box; value should never be lowered in any future version of Windows
+ if (cbPassword <= PBKDF2_MAX_KEYLENGTH_IN_BYTES)
+ {
+ // Common case: the password is small enough to be consumed directly by the PBKDF2 algorithm.
+ return pbkdf2AlgHandle.GenerateSymmetricKey(pbPassword, cbPassword);
+ }
+ else
+ {
+ // Rare case: password is very long; we must hash manually.
+ // PBKDF2 uses the PRFs in HMAC mode, and when the HMAC input key exceeds the hash function's
+ // block length the key is hashed and run back through the key initialization function.
+
+ BCryptAlgorithmHandle prfAlgorithmHandle; // cached; don't dispose
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ prfAlgorithmHandle = CachedAlgorithmHandles.SHA1;
+ break;
+ case KeyDerivationPrf.HMACSHA256:
+ prfAlgorithmHandle = CachedAlgorithmHandles.SHA256;
+ break;
+ case KeyDerivationPrf.HMACSHA512:
+ prfAlgorithmHandle = CachedAlgorithmHandles.SHA512;
+ break;
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+
+ // Final sanity check: don't hash the password if the HMAC key initialization function wouldn't have done it for us.
+ if (cbPassword <= prfAlgorithmHandle.GetHashBlockLength() /* in bytes */)
+ {
+ return pbkdf2AlgHandle.GenerateSymmetricKey(pbPassword, cbPassword);
+ }
+
+ // Hash the password and use the hash as input to PBKDF2.
+ uint cbPasswordDigest = prfAlgorithmHandle.GetHashDigestLength();
+ CryptoUtil.Assert(cbPasswordDigest > 0, "cbPasswordDigest > 0");
+ fixed (byte* pbPasswordDigest = new byte[cbPasswordDigest])
+ {
+ try
+ {
+ using (var hashHandle = prfAlgorithmHandle.CreateHash())
+ {
+ hashHandle.HashData(pbPassword, cbPassword, pbPasswordDigest, cbPasswordDigest);
+ }
+ return pbkdf2AlgHandle.GenerateSymmetricKey(pbPasswordDigest, cbPasswordDigest);
+ }
+ finally
+ {
+ UnsafeBufferUtil.SecureZeroMemory(pbPasswordDigest, cbPasswordDigest);
+ }
+ }
+ }
+ }
+
+ private static void DeriveKeyCore(BCryptKeyHandle pbkdf2KeyHandle, string hashAlgorithm, byte* pbSalt, uint cbSalt, ulong iterCount, byte* pbDerivedBytes, uint cbDerivedBytes)
+ {
+ // First, build the buffers necessary to pass (hash alg, salt, iter count) into the KDF
+ BCryptBuffer* pBuffers = stackalloc BCryptBuffer[3];
+
+ pBuffers[0].BufferType = BCryptKeyDerivationBufferType.KDF_ITERATION_COUNT;
+ pBuffers[0].pvBuffer = (IntPtr)(&iterCount);
+ pBuffers[0].cbBuffer = sizeof(ulong);
+
+ pBuffers[1].BufferType = BCryptKeyDerivationBufferType.KDF_SALT;
+ pBuffers[1].pvBuffer = (IntPtr)pbSalt;
+ pBuffers[1].cbBuffer = cbSalt;
+
+ fixed (char* pszHashAlgorithm = hashAlgorithm)
+ {
+ pBuffers[2].BufferType = BCryptKeyDerivationBufferType.KDF_HASH_ALGORITHM;
+ pBuffers[2].pvBuffer = (IntPtr)pszHashAlgorithm;
+ pBuffers[2].cbBuffer = GetTotalByteLengthIncludingNullTerminator(hashAlgorithm);
+
+ // Add the header which points to the buffers
+ BCryptBufferDesc bufferDesc = default(BCryptBufferDesc);
+ BCryptBufferDesc.Initialize(ref bufferDesc);
+ bufferDesc.cBuffers = 3;
+ bufferDesc.pBuffers = pBuffers;
+
+ // Finally, import the KDK into the KDF algorithm, then invoke the KDF
+ uint numBytesDerived;
+ int ntstatus = UnsafeNativeMethods.BCryptKeyDerivation(
+ hKey: pbkdf2KeyHandle,
+ pParameterList: &bufferDesc,
+ pbDerivedKey: pbDerivedBytes,
+ cbDerivedKey: cbDerivedBytes,
+ pcbResult: out numBytesDerived,
+ dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+
+ // Final sanity checks before returning control to caller.
+ CryptoUtil.Assert(numBytesDerived == cbDerivedBytes, "numBytesDerived == cbDerivedBytes");
+ }
+ }
+
+ private static string PrfToCngAlgorithmId(KeyDerivationPrf prf)
+ {
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ return Constants.BCRYPT_SHA1_ALGORITHM;
+ case KeyDerivationPrf.HMACSHA256:
+ return Constants.BCRYPT_SHA256_ALGORITHM;
+ case KeyDerivationPrf.HMACSHA512:
+ return Constants.BCRYPT_SHA512_ALGORITHM;
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2ca6553c5d
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Properties/AssemblyInfo.cs
@@ -0,0 +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.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cryptography.KeyDerivation.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/baseline.netcore.json
new file mode 100644
index 0000000000..ceddb40cc2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/baseline.netcore.json
@@ -0,0 +1,78 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Cryptography.KeyDerivation, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Pbkdf2",
+ "Parameters": [
+ {
+ "Name": "password",
+ "Type": "System.String"
+ },
+ {
+ "Name": "salt",
+ "Type": "System.Byte[]"
+ },
+ {
+ "Name": "prf",
+ "Type": "Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivationPrf"
+ },
+ {
+ "Name": "iterationCount",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "numBytesRequested",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivationPrf",
+ "Visibility": "Public",
+ "Kind": "Enumeration",
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "HMACSHA1",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "0"
+ },
+ {
+ "Kind": "Field",
+ "Name": "HMACSHA256",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "1"
+ },
+ {
+ "Kind": "Field",
+ "Name": "HMACSHA512",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "2"
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/CryptoUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/CryptoUtil.cs
new file mode 100644
index 0000000000..e3e361a3a8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/CryptoUtil.cs
@@ -0,0 +1,33 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class CryptoUtil
+ {
+ // This isn't a typical Debug.Fail; an error always occurs, even in retail builds.
+ // This method doesn't return, but since the CLR doesn't allow specifying a 'never'
+ // return type, we mimic it by specifying our return type as Exception. That way
+ // callers can write 'throw Fail(...);' to make the C# compiler happy, as the
+ // throw keyword is implicitly of type O.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static Exception Fail(string message)
+ {
+ Debug.Fail(message);
+ throw new CryptographicException("Assertion failed: " + message);
+ }
+
+ // Allows callers to write "var x = Method() ?? Fail(message);" as a convenience to guard
+ // against a method returning null unexpectedly.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static T Fail(string message) where T : class
+ {
+ throw Fail(message);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/DataProtectionCommonExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/DataProtectionCommonExtensions.cs
new file mode 100644
index 0000000000..f4fd8801ae
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/DataProtectionCommonExtensions.cs
@@ -0,0 +1,244 @@
+// 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.Diagnostics;
+using Microsoft.AspNetCore.DataProtection.Abstractions;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Helpful extension methods for data protection APIs.
+ ///
+ public static class DataProtectionCommonExtensions
+ {
+ ///
+ /// Creates an given a list of purposes.
+ ///
+ /// The from which to generate the purpose chain.
+ /// The list of purposes which contribute to the purpose chain. This list must
+ /// contain at least one element, and it may not contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which chains together several calls to
+ /// . See that method's
+ /// documentation for more information.
+ ///
+ public static IDataProtector CreateProtector(this IDataProtectionProvider provider, IEnumerable purposes)
+ {
+ if (provider == null)
+ {
+ throw new ArgumentNullException(nameof(provider));
+ }
+
+ if (purposes == null)
+ {
+ throw new ArgumentNullException(nameof(purposes));
+ }
+
+ bool collectionIsEmpty = true;
+ IDataProtectionProvider retVal = provider;
+ foreach (string purpose in purposes)
+ {
+ if (purpose == null)
+ {
+ throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesCollection, nameof(purposes));
+ }
+ retVal = retVal.CreateProtector(purpose) ?? CryptoUtil.Fail("CreateProtector returned null.");
+ collectionIsEmpty = false;
+ }
+
+ if (collectionIsEmpty)
+ {
+ throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesCollection, nameof(purposes));
+ }
+
+ Debug.Assert(retVal is IDataProtector); // CreateProtector is supposed to return an instance of this interface
+ return (IDataProtector)retVal;
+ }
+
+ ///
+ /// Creates an given a list of purposes.
+ ///
+ /// The from which to generate the purpose chain.
+ /// The primary purpose used to create the .
+ /// An optional list of secondary purposes which contribute to the purpose chain.
+ /// If this list is provided it cannot contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which chains together several calls to
+ /// . See that method's
+ /// documentation for more information.
+ ///
+ public static IDataProtector CreateProtector(this IDataProtectionProvider provider, string purpose, params string[] subPurposes)
+ {
+ if (provider == null)
+ {
+ throw new ArgumentNullException(nameof(provider));
+ }
+
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ // The method signature isn't simply CreateProtector(this IDataProtectionProvider, params string[] purposes)
+ // because we don't want the code provider.CreateProtector() [parameterless] to inadvertently compile.
+ // The actual signature for this method forces at least one purpose to be provided at the call site.
+
+ IDataProtector protector = provider.CreateProtector(purpose);
+ if (subPurposes != null && subPurposes.Length > 0)
+ {
+ protector = protector?.CreateProtector((IEnumerable)subPurposes);
+ }
+ return protector ?? CryptoUtil.Fail("CreateProtector returned null.");
+ }
+
+ ///
+ /// Retrieves an from an .
+ ///
+ /// The service provider from which to retrieve the .
+ /// An . This method is guaranteed never to return null.
+ /// If no service exists in .
+ public static IDataProtectionProvider GetDataProtectionProvider(this IServiceProvider services)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ // We have our own implementation of GetRequiredService since we don't want to
+ // take a dependency on DependencyInjection.Interfaces.
+ IDataProtectionProvider provider = (IDataProtectionProvider)services.GetService(typeof(IDataProtectionProvider));
+ if (provider == null)
+ {
+ throw new InvalidOperationException(Resources.FormatDataProtectionExtensions_NoService(typeof(IDataProtectionProvider).FullName));
+ }
+ return provider;
+ }
+
+ ///
+ /// Retrieves an from an given a list of purposes.
+ ///
+ /// An which contains the
+ /// from which to generate the purpose chain.
+ /// The list of purposes which contribute to the purpose chain. This list must
+ /// contain at least one element, and it may not contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which calls
+ /// then . See those methods'
+ /// documentation for more information.
+ ///
+ public static IDataProtector GetDataProtector(this IServiceProvider services, IEnumerable purposes)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (purposes == null)
+ {
+ throw new ArgumentNullException(nameof(purposes));
+ }
+
+ return services.GetDataProtectionProvider().CreateProtector(purposes);
+ }
+
+ ///
+ /// Retrieves an from an given a list of purposes.
+ ///
+ /// An which contains the
+ /// from which to generate the purpose chain.
+ /// The primary purpose used to create the .
+ /// An optional list of secondary purposes which contribute to the purpose chain.
+ /// If this list is provided it cannot contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which calls
+ /// then . See those methods'
+ /// documentation for more information.
+ ///
+ public static IDataProtector GetDataProtector(this IServiceProvider services, string purpose, params string[] subPurposes)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ return services.GetDataProtectionProvider().CreateProtector(purpose, subPurposes);
+ }
+
+ ///
+ /// Cryptographically protects a piece of plaintext data.
+ ///
+ /// The data protector to use for this operation.
+ /// The plaintext data to protect.
+ /// The protected form of the plaintext data.
+ public static string Protect(this IDataProtector protector, string plaintext)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ try
+ {
+ byte[] plaintextAsBytes = EncodingUtil.SecureUtf8Encoding.GetBytes(plaintext);
+ byte[] protectedDataAsBytes = protector.Protect(plaintextAsBytes);
+ return WebEncoders.Base64UrlEncode(protectedDataAsBytes);
+ }
+ catch (Exception ex) when (ex.RequiresHomogenization())
+ {
+ // Homogenize exceptions to CryptographicException
+ throw Error.CryptCommon_GenericError(ex);
+ }
+ }
+
+ ///
+ /// Cryptographically unprotects a piece of protected data.
+ ///
+ /// The data protector to use for this operation.
+ /// The protected data to unprotect.
+ /// The plaintext form of the protected data.
+ ///
+ /// Thrown if is invalid or malformed.
+ ///
+ public static string Unprotect(this IDataProtector protector, string protectedData)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ try
+ {
+ byte[] protectedDataAsBytes = WebEncoders.Base64UrlDecode(protectedData);
+ byte[] plaintextAsBytes = protector.Unprotect(protectedDataAsBytes);
+ return EncodingUtil.SecureUtf8Encoding.GetString(plaintextAsBytes);
+ }
+ catch (Exception ex) when (ex.RequiresHomogenization())
+ {
+ // Homogenize exceptions to CryptographicException
+ throw Error.CryptCommon_GenericError(ex);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Error.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Error.cs
new file mode 100644
index 0000000000..18b93c0ac7
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Error.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 System.Security.Cryptography;
+using Microsoft.AspNetCore.DataProtection.Abstractions;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class Error
+ {
+ public static CryptographicException CryptCommon_GenericError(Exception inner = null)
+ {
+ return new CryptographicException(Resources.CryptCommon_GenericError, inner);
+ }
+
+ public static CryptographicException CryptCommon_PayloadInvalid()
+ {
+ string message = Resources.CryptCommon_PayloadInvalid;
+ return new CryptographicException(message);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtectionProvider.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtectionProvider.cs
new file mode 100644
index 0000000000..02f772724b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtectionProvider.cs
@@ -0,0 +1,26 @@
+// 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.
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// An interface that can be used to create instances.
+ ///
+ public interface IDataProtectionProvider
+ {
+ ///
+ /// Creates an given a purpose.
+ ///
+ ///
+ /// The purpose to be assigned to the newly-created .
+ ///
+ /// An IDataProtector tied to the provided purpose.
+ ///
+ /// The parameter must be unique for the intended use case; two
+ /// different instances created with two different
+ /// values will not be able to decipher each other's payloads. The parameter
+ /// value is not intended to be kept secret.
+ ///
+ IDataProtector CreateProtector(string purpose);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtector.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtector.cs
new file mode 100644
index 0000000000..1d9c8c3946
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtector.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.
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// An interface that can provide data protection services.
+ ///
+ public interface IDataProtector : IDataProtectionProvider
+ {
+ ///
+ /// Cryptographically protects a piece of plaintext data.
+ ///
+ /// The plaintext data to protect.
+ /// The protected form of the plaintext data.
+ byte[] Protect(byte[] plaintext);
+
+ ///
+ /// Cryptographically unprotects a piece of protected data.
+ ///
+ /// The protected data to unprotect.
+ /// The plaintext form of the protected data.
+ ///
+ /// Thrown if the protected data is invalid or malformed.
+ ///
+ byte[] Unprotect(byte[] protectedData);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Infrastructure/IApplicationDiscriminator.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Infrastructure/IApplicationDiscriminator.cs
new file mode 100644
index 0000000000..d8c3af376f
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Infrastructure/IApplicationDiscriminator.cs
@@ -0,0 +1,25 @@
+// 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.ComponentModel;
+
+namespace Microsoft.AspNetCore.DataProtection.Infrastructure
+{
+ ///
+ /// Provides information used to discriminate applications.
+ ///
+ ///
+ /// This type supports the data protection system and is not intended to be used
+ /// by consumers.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IApplicationDiscriminator
+ {
+ ///
+ /// An identifier that uniquely discriminates this application from all other
+ /// applications on the machine.
+ ///
+ string Discriminator { get; }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Microsoft.AspNetCore.DataProtection.Abstractions.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
new file mode 100644
index 0000000000..24bd9f5fb6
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
@@ -0,0 +1,21 @@
+
+
+
+ ASP.NET Core data protection abstractions.
+Commonly used types:
+Microsoft.AspNetCore.DataProtection.IDataProtectionProvider
+Microsoft.AspNetCore.DataProtection.IDataProtector
+ netstandard2.0
+ true
+ aspnetcore;dataprotection
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..838462a81d
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/AssemblyInfo.cs
@@ -0,0 +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.Runtime.CompilerServices;
+
+// for unit testing
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..7f8422cf6b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs
@@ -0,0 +1,86 @@
+//
+namespace Microsoft.AspNetCore.DataProtection.Abstractions
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.DataProtection.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The payload was invalid.
+ ///
+ internal static string CryptCommon_PayloadInvalid
+ {
+ get => GetString("CryptCommon_PayloadInvalid");
+ }
+
+ ///
+ /// The payload was invalid.
+ ///
+ internal static string FormatCryptCommon_PayloadInvalid()
+ => GetString("CryptCommon_PayloadInvalid");
+
+ ///
+ /// The purposes collection cannot be null or empty and cannot contain null elements.
+ ///
+ internal static string DataProtectionExtensions_NullPurposesCollection
+ {
+ get => GetString("DataProtectionExtensions_NullPurposesCollection");
+ }
+
+ ///
+ /// The purposes collection cannot be null or empty and cannot contain null elements.
+ ///
+ internal static string FormatDataProtectionExtensions_NullPurposesCollection()
+ => GetString("DataProtectionExtensions_NullPurposesCollection");
+
+ ///
+ /// An error occurred during a cryptographic operation.
+ ///
+ internal static string CryptCommon_GenericError
+ {
+ get => GetString("CryptCommon_GenericError");
+ }
+
+ ///
+ /// An error occurred during a cryptographic operation.
+ ///
+ internal static string FormatCryptCommon_GenericError()
+ => GetString("CryptCommon_GenericError");
+
+ ///
+ /// No service for type '{0}' has been registered.
+ ///
+ internal static string DataProtectionExtensions_NoService
+ {
+ get => GetString("DataProtectionExtensions_NoService");
+ }
+
+ ///
+ /// No service for type '{0}' has been registered.
+ ///
+ internal static string FormatDataProtectionExtensions_NoService(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("DataProtectionExtensions_NoService"), p0);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Resources.resx b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Resources.resx
new file mode 100644
index 0000000000..daa9e2cbd9
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Resources.resx
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The payload was invalid.
+
+
+ The purposes collection cannot be null or empty and cannot contain null elements.
+
+
+ An error occurred during a cryptographic operation.
+
+
+ No service for type '{0}' has been registered.
+
+
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/baseline.netcore.json
new file mode 100644
index 0000000000..eb6e5030fe
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/baseline.netcore.json
@@ -0,0 +1,231 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.DataProtection.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CreateProtector",
+ "Parameters": [
+ {
+ "Name": "provider",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider"
+ },
+ {
+ "Name": "purposes",
+ "Type": "System.Collections.Generic.IEnumerable"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "CreateProtector",
+ "Parameters": [
+ {
+ "Name": "provider",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider"
+ },
+ {
+ "Name": "purpose",
+ "Type": "System.String"
+ },
+ {
+ "Name": "subPurposes",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDataProtectionProvider",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "System.IServiceProvider"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDataProtector",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "System.IServiceProvider"
+ },
+ {
+ "Name": "purposes",
+ "Type": "System.Collections.Generic.IEnumerable"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDataProtector",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "System.IServiceProvider"
+ },
+ {
+ "Name": "purpose",
+ "Type": "System.String"
+ },
+ {
+ "Name": "subPurposes",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtector"
+ },
+ {
+ "Name": "plaintext",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Unprotect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtector"
+ },
+ {
+ "Name": "protectedData",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CreateProtector",
+ "Parameters": [
+ {
+ "Name": "purpose",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "plaintext",
+ "Type": "System.Byte[]"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Unprotect",
+ "Parameters": [
+ {
+ "Name": "protectedData",
+ "Type": "System.Byte[]"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Discriminator",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs
new file mode 100644
index 0000000000..0701220b4b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs
@@ -0,0 +1,118 @@
+// 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.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.DataProtection.AzureKeyVault;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains Azure KeyVault-specific extension methods for modifying a .
+ ///
+ public static class AzureDataProtectionBuilderExtensions
+ {
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The application client id.
+ ///
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, string keyIdentifier, string clientId, X509Certificate2 certificate)
+ {
+ if (string.IsNullOrEmpty(clientId))
+ {
+ throw new ArgumentException(nameof(clientId));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ KeyVaultClient.AuthenticationCallback callback =
+ (authority, resource, scope) => GetTokenFromClientCertificate(authority, resource, clientId, certificate);
+
+ return ProtectKeysWithAzureKeyVault(builder, new KeyVaultClient(callback), keyIdentifier);
+ }
+
+ private static async Task GetTokenFromClientCertificate(string authority, string resource, string clientId, X509Certificate2 certificate)
+ {
+ var authContext = new AuthenticationContext(authority);
+ var result = await authContext.AcquireTokenAsync(resource, new ClientAssertionCertificate(clientId, certificate));
+ return result.AccessToken;
+ }
+
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The application client id.
+ /// The client secret to use for authentication.
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, string keyIdentifier, string clientId, string clientSecret)
+ {
+ if (string.IsNullOrEmpty(clientId))
+ {
+ throw new ArgumentNullException(nameof(clientId));
+ }
+ if (string.IsNullOrEmpty(clientSecret))
+ {
+ throw new ArgumentNullException(nameof(clientSecret));
+ }
+
+ KeyVaultClient.AuthenticationCallback callback =
+ (authority, resource, scope) => GetTokenFromClientSecret(authority, resource, clientId, clientSecret);
+
+ return ProtectKeysWithAzureKeyVault(builder, new KeyVaultClient(callback), keyIdentifier);
+ }
+
+ private static async Task GetTokenFromClientSecret(string authority, string resource, string clientId, string clientSecret)
+ {
+ var authContext = new AuthenticationContext(authority);
+ var clientCred = new ClientCredential(clientId, clientSecret);
+ var result = await authContext.AcquireTokenAsync(resource, clientCred);
+ return result.AccessToken;
+ }
+
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The to use for KeyVault access.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, KeyVaultClient client, string keyIdentifier)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+ if (string.IsNullOrEmpty(keyIdentifier))
+ {
+ throw new ArgumentException(nameof(keyIdentifier));
+ }
+
+ var vaultClientWrapper = new KeyVaultClientWrapper(client);
+
+ builder.Services.AddSingleton(vaultClientWrapper);
+ builder.Services.Configure(options =>
+ {
+ options.XmlEncryptor = new AzureKeyVaultXmlEncryptor(vaultClientWrapper, keyIdentifier);
+ });
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs
new file mode 100644
index 0000000000..b9942fa84f
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.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 System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class AzureKeyVaultXmlDecryptor: IXmlDecryptor
+ {
+ private readonly IKeyVaultWrappingClient _client;
+
+ public AzureKeyVaultXmlDecryptor(IServiceProvider serviceProvider)
+ {
+ _client = serviceProvider.GetService();
+ }
+
+ public XElement Decrypt(XElement encryptedElement)
+ {
+ return DecryptAsync(encryptedElement).GetAwaiter().GetResult();
+ }
+
+ private async Task DecryptAsync(XElement encryptedElement)
+ {
+ var kid = (string)encryptedElement.Element("kid");
+ var symmetricKey = Convert.FromBase64String((string)encryptedElement.Element("key"));
+ var symmetricIV = Convert.FromBase64String((string)encryptedElement.Element("iv"));
+
+ var encryptedValue = Convert.FromBase64String((string)encryptedElement.Element("value"));
+
+ var result = await _client.UnwrapKeyAsync(kid, AzureKeyVaultXmlEncryptor.DefaultKeyEncryption, symmetricKey);
+
+ byte[] decryptedValue;
+ using (var symmetricAlgorithm = AzureKeyVaultXmlEncryptor.DefaultSymmetricAlgorithmFactory())
+ {
+ using (var decryptor = symmetricAlgorithm.CreateDecryptor(result.Result, symmetricIV))
+ {
+ decryptedValue = decryptor.TransformFinalBlock(encryptedValue, 0, encryptedValue.Length);
+ }
+ }
+
+ using (var memoryStream = new MemoryStream(decryptedValue))
+ {
+ return XElement.Load(memoryStream);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs
new file mode 100644
index 0000000000..3451c3ded2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs
@@ -0,0 +1,77 @@
+// 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.Security.Cryptography;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Azure.KeyVault.WebKey;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class AzureKeyVaultXmlEncryptor : IXmlEncryptor
+ {
+ internal static string DefaultKeyEncryption = JsonWebKeyEncryptionAlgorithm.RSAOAEP;
+ internal static Func DefaultSymmetricAlgorithmFactory = Aes.Create;
+
+ private readonly RandomNumberGenerator _randomNumberGenerator;
+ private readonly IKeyVaultWrappingClient _client;
+ private readonly string _keyId;
+
+ public AzureKeyVaultXmlEncryptor(IKeyVaultWrappingClient client, string keyId)
+ : this(client, keyId, RandomNumberGenerator.Create())
+ {
+ }
+
+ internal AzureKeyVaultXmlEncryptor(IKeyVaultWrappingClient client, string keyId, RandomNumberGenerator randomNumberGenerator)
+ {
+ _client = client;
+ _keyId = keyId;
+ _randomNumberGenerator = randomNumberGenerator;
+ }
+
+ public EncryptedXmlInfo Encrypt(XElement plaintextElement)
+ {
+ return EncryptAsync(plaintextElement).GetAwaiter().GetResult();
+ }
+
+ private async Task EncryptAsync(XElement plaintextElement)
+ {
+ byte[] value;
+ using (var memoryStream = new MemoryStream())
+ {
+ plaintextElement.Save(memoryStream, SaveOptions.DisableFormatting);
+ value = memoryStream.ToArray();
+ }
+
+ using (var symmetricAlgorithm = DefaultSymmetricAlgorithmFactory())
+ {
+ var symmetricBlockSize = symmetricAlgorithm.BlockSize / 8;
+ var symmetricKey = new byte[symmetricBlockSize];
+ var symmetricIV = new byte[symmetricBlockSize];
+ _randomNumberGenerator.GetBytes(symmetricKey);
+ _randomNumberGenerator.GetBytes(symmetricIV);
+
+ byte[] encryptedValue;
+ using (var encryptor = symmetricAlgorithm.CreateEncryptor(symmetricKey, symmetricIV))
+ {
+ encryptedValue = encryptor.TransformFinalBlock(value, 0, value.Length);
+ }
+
+ var wrappedKey = await _client.WrapKeyAsync(_keyId, DefaultKeyEncryption, symmetricKey);
+
+ var element = new XElement("encryptedKey",
+ new XComment(" This key is encrypted with Azure KeyVault. "),
+ new XElement("kid", wrappedKey.Kid),
+ new XElement("key", Convert.ToBase64String(wrappedKey.Result)),
+ new XElement("iv", Convert.ToBase64String(symmetricIV)),
+ new XElement("value", Convert.ToBase64String(encryptedValue)));
+
+ return new EncryptedXmlInfo(element, typeof(AzureKeyVaultXmlDecryptor));
+ }
+
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs
new file mode 100644
index 0000000000..2347460dc3
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs
@@ -0,0 +1,14 @@
+// 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.Threading.Tasks;
+using Microsoft.Azure.KeyVault.Models;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal interface IKeyVaultWrappingClient
+ {
+ Task UnwrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText);
+ Task WrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText);
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs
new file mode 100644
index 0000000000..82fe0649e2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs
@@ -0,0 +1,29 @@
+// 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.Threading.Tasks;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Azure.KeyVault.Models;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class KeyVaultClientWrapper : IKeyVaultWrappingClient
+ {
+ private readonly KeyVaultClient _client;
+
+ public KeyVaultClientWrapper(KeyVaultClient client)
+ {
+ _client = client;
+ }
+
+ public Task UnwrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText)
+ {
+ return _client.UnwrapKeyAsync(keyIdentifier, algorithm, cipherText);
+ }
+
+ public Task WrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText)
+ {
+ return _client.WrapKeyAsync(keyIdentifier, algorithm, cipherText);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
new file mode 100644
index 0000000000..0c7b084a2a
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Microsoft Azure KeyVault key encryption support.
+ netstandard2.0
+ true
+ aspnetcore;dataprotection;azure;keyvault
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..c23a3410b7
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs
@@ -0,0 +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 System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureBlobXmlRepository.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureBlobXmlRepository.cs
new file mode 100644
index 0000000000..e39babaa31
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureBlobXmlRepository.cs
@@ -0,0 +1,297 @@
+// 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.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.Repositories;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Blob;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureStorage
+{
+ ///
+ /// An which is backed by Azure Blob Storage.
+ ///
+ ///
+ /// Instances of this type are thread-safe.
+ ///
+ public sealed class AzureBlobXmlRepository : IXmlRepository
+ {
+ private const int ConflictMaxRetries = 5;
+ private static readonly TimeSpan ConflictBackoffPeriod = TimeSpan.FromMilliseconds(200);
+
+ private static readonly XName RepositoryElementName = "repository";
+
+ private readonly Func _blobRefFactory;
+ private readonly Random _random;
+ private BlobData _cachedBlobData;
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// A factory which can create
+ /// instances. The factory must be thread-safe for invocation by multiple
+ /// concurrent threads, and each invocation must return a new object.
+ public AzureBlobXmlRepository(Func blobRefFactory)
+ {
+ if (blobRefFactory == null)
+ {
+ throw new ArgumentNullException(nameof(blobRefFactory));
+ }
+
+ _blobRefFactory = blobRefFactory;
+ _random = new Random();
+ }
+
+ ///
+ public IReadOnlyCollection GetAllElements()
+ {
+ var blobRef = CreateFreshBlobRef();
+
+ // Shunt the work onto a ThreadPool thread so that it's independent of any
+ // existing sync context or other potentially deadlock-causing items.
+
+ var elements = Task.Run(() => GetAllElementsAsync(blobRef)).GetAwaiter().GetResult();
+ return new ReadOnlyCollection(elements);
+ }
+
+ ///
+ public void StoreElement(XElement element, string friendlyName)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException(nameof(element));
+ }
+
+ var blobRef = CreateFreshBlobRef();
+
+ // Shunt the work onto a ThreadPool thread so that it's independent of any
+ // existing sync context or other potentially deadlock-causing items.
+
+ Task.Run(() => StoreElementAsync(blobRef, element)).GetAwaiter().GetResult();
+ }
+
+ private XDocument CreateDocumentFromBlob(byte[] blob)
+ {
+ using (var memoryStream = new MemoryStream(blob))
+ {
+ var xmlReaderSettings = new XmlReaderSettings()
+ {
+ DtdProcessing = DtdProcessing.Prohibit, IgnoreProcessingInstructions = true
+ };
+
+ using (var xmlReader = XmlReader.Create(memoryStream, xmlReaderSettings))
+ {
+ return XDocument.Load(xmlReader);
+ }
+ }
+ }
+
+ private ICloudBlob CreateFreshBlobRef()
+ {
+ // ICloudBlob instances aren't thread-safe, so we need to make sure we're working
+ // with a fresh instance that won't be mutated by another thread.
+
+ var blobRef = _blobRefFactory();
+ if (blobRef == null)
+ {
+ throw new InvalidOperationException("The ICloudBlob factory method returned null.");
+ }
+
+ return blobRef;
+ }
+
+ private async Task> GetAllElementsAsync(ICloudBlob blobRef)
+ {
+ var data = await GetLatestDataAsync(blobRef);
+
+ if (data == null)
+ {
+ // no data in blob storage
+ return new XElement[0];
+ }
+
+ // The document will look like this:
+ //
+ //
+ //
+ //
+ // ...
+ //
+ //
+ // We want to return the first-level child elements to our caller.
+
+ var doc = CreateDocumentFromBlob(data.BlobContents);
+ return doc.Root.Elements().ToList();
+ }
+
+ private async Task GetLatestDataAsync(ICloudBlob blobRef)
+ {
+ // Set the appropriate AccessCondition based on what we believe the latest
+ // file contents to be, then make the request.
+
+ var latestCachedData = Volatile.Read(ref _cachedBlobData); // local ref so field isn't mutated under our feet
+ var accessCondition = (latestCachedData != null)
+ ? AccessCondition.GenerateIfNoneMatchCondition(latestCachedData.ETag)
+ : null;
+
+ try
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ await blobRef.DownloadToStreamAsync(
+ target: memoryStream,
+ accessCondition: accessCondition,
+ options: null,
+ operationContext: null);
+
+ // At this point, our original cache either didn't exist or was outdated.
+ // We'll update it now and return the updated value;
+
+ latestCachedData = new BlobData()
+ {
+ BlobContents = memoryStream.ToArray(),
+ ETag = blobRef.Properties.ETag
+ };
+
+ }
+ Volatile.Write(ref _cachedBlobData, latestCachedData);
+ }
+ catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 304)
+ {
+ // 304 Not Modified
+ // Thrown when we already have the latest cached data.
+ // This isn't an error; we'll return our cached copy of the data.
+ }
+ catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404)
+ {
+ // 404 Not Found
+ // Thrown when no file exists in storage.
+ // This isn't an error; we'll delete our cached copy of data.
+
+ latestCachedData = null;
+ Volatile.Write(ref _cachedBlobData, latestCachedData);
+ }
+
+ return latestCachedData;
+ }
+
+ private int GetRandomizedBackoffPeriod()
+ {
+ // returns a TimeSpan in the range [0.8, 1.0) * ConflictBackoffPeriod
+ // not used for crypto purposes
+ var multiplier = 0.8 + (_random.NextDouble() * 0.2);
+ return (int) (multiplier * ConflictBackoffPeriod.Ticks);
+ }
+
+ private async Task StoreElementAsync(ICloudBlob blobRef, XElement element)
+ {
+ // holds the last error in case we need to rethrow it
+ ExceptionDispatchInfo lastError = null;
+
+ for (var i = 0; i < ConflictMaxRetries; i++)
+ {
+ if (i > 1)
+ {
+ // If multiple conflicts occurred, wait a small period of time before retrying
+ // the operation so that other writers can make forward progress.
+ await Task.Delay(GetRandomizedBackoffPeriod());
+ }
+
+ if (i > 0)
+ {
+ // If at least one conflict occurred, make sure we have an up-to-date
+ // view of the blob contents.
+ await GetLatestDataAsync(blobRef);
+ }
+
+ // Merge the new element into the document. If no document exists,
+ // create a new default document and inject this element into it.
+
+ var latestData = Volatile.Read(ref _cachedBlobData);
+ var doc = (latestData != null)
+ ? CreateDocumentFromBlob(latestData.BlobContents)
+ : new XDocument(new XElement(RepositoryElementName));
+ doc.Root.Add(element);
+
+ // Turn this document back into a byte[].
+
+ var serializedDoc = new MemoryStream();
+ doc.Save(serializedDoc, SaveOptions.DisableFormatting);
+
+ // Generate the appropriate precondition header based on whether or not
+ // we believe data already exists in storage.
+
+ AccessCondition accessCondition;
+ if (latestData != null)
+ {
+ accessCondition = AccessCondition.GenerateIfMatchCondition(blobRef.Properties.ETag);
+ }
+ else
+ {
+ accessCondition = AccessCondition.GenerateIfNotExistsCondition();
+ blobRef.Properties.ContentType = "application/xml; charset=utf-8"; // set content type on first write
+ }
+
+ try
+ {
+ // Send the request up to the server.
+
+ var serializedDocAsByteArray = serializedDoc.ToArray();
+
+ await blobRef.UploadFromByteArrayAsync(
+ buffer: serializedDocAsByteArray,
+ index: 0,
+ count: serializedDocAsByteArray.Length,
+ accessCondition: accessCondition,
+ options: null,
+ operationContext: null);
+
+ // If we got this far, success!
+ // We can update the cached view of the remote contents.
+
+ Volatile.Write(ref _cachedBlobData, new BlobData()
+ {
+ BlobContents = serializedDocAsByteArray,
+ ETag = blobRef.Properties.ETag // was updated by Upload routine
+ });
+
+ return;
+ }
+ catch (StorageException ex)
+ when (ex.RequestInformation.HttpStatusCode == 409 || ex.RequestInformation.HttpStatusCode == 412)
+ {
+ // 409 Conflict
+ // This error is rare but can be thrown in very special circumstances,
+ // such as if the blob in the process of being created. We treat it
+ // as equivalent to 412 for the purposes of retry logic.
+
+ // 412 Precondition Failed
+ // We'll get this error if another writer updated the repository and we
+ // have an outdated view of its contents. If this occurs, we'll just
+ // refresh our view of the remote contents and try again up to the max
+ // retry limit.
+
+ lastError = ExceptionDispatchInfo.Capture(ex);
+ }
+ }
+
+ // if we got this far, something went awry
+ lastError.Throw();
+ }
+
+ private sealed class BlobData
+ {
+ internal byte[] BlobContents;
+ internal string ETag;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureDataProtectionBuilderExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureDataProtectionBuilderExtensions.cs
new file mode 100644
index 0000000000..8ff62929e2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureDataProtectionBuilderExtensions.cs
@@ -0,0 +1,175 @@
+// 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.AzureStorage;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Auth;
+using Microsoft.WindowsAzure.Storage.Blob;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains Azure-specific extension methods for modifying a
+ /// .
+ ///
+ public static class AzureDataProtectionBuilderExtensions
+ {
+ ///
+ /// Configures the data protection system to persist keys to the specified path
+ /// in Azure Blob Storage.
+ ///
+ /// The builder instance to modify.
+ /// The which
+ /// should be utilized.
+ /// A relative path where the key file should be
+ /// stored, generally specified as "/containerName/[subDir/]keys.xml".
+ /// The value .
+ ///
+ /// The container referenced by must already exist.
+ ///
+ public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, CloudStorageAccount storageAccount, string relativePath)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (storageAccount == null)
+ {
+ throw new ArgumentNullException(nameof(storageAccount));
+ }
+ if (relativePath == null)
+ {
+ throw new ArgumentNullException(nameof(relativePath));
+ }
+
+ // Simply concatenate the root storage endpoint with the relative path,
+ // which includes the container name and blob name.
+
+ var uriBuilder = new UriBuilder(storageAccount.BlobEndpoint);
+ uriBuilder.Path = uriBuilder.Path.TrimEnd('/') + "/" + relativePath.TrimStart('/');
+
+ // We can create a CloudBlockBlob from the storage URI and the creds.
+
+ var blobAbsoluteUri = uriBuilder.Uri;
+ var credentials = storageAccount.Credentials;
+
+ return PersistKeystoAzureBlobStorageInternal(builder, () => new CloudBlockBlob(blobAbsoluteUri, credentials));
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the specified path
+ /// in Azure Blob Storage.
+ ///
+ /// The builder instance to modify.
+ /// The full URI where the key file should be stored.
+ /// The URI must contain the SAS token as a query string parameter.
+ /// The value .
+ ///
+ /// The container referenced by must already exist.
+ ///
+ public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, Uri blobUri)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (blobUri == null)
+ {
+ throw new ArgumentNullException(nameof(blobUri));
+ }
+
+ var uriBuilder = new UriBuilder(blobUri);
+
+ // The SAS token is present in the query string.
+
+ if (string.IsNullOrEmpty(uriBuilder.Query))
+ {
+ throw new ArgumentException(
+ message: "URI does not have a SAS token in the query string.",
+ paramName: nameof(blobUri));
+ }
+
+ var credentials = new StorageCredentials(uriBuilder.Query);
+ uriBuilder.Query = null; // no longer needed
+ var blobAbsoluteUri = uriBuilder.Uri;
+
+ return PersistKeystoAzureBlobStorageInternal(builder, () => new CloudBlockBlob(blobAbsoluteUri, credentials));
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the specified path
+ /// in Azure Blob Storage.
+ ///
+ /// The builder instance to modify.
+ /// The where the
+ /// key file should be stored.
+ /// The value .
+ ///
+ /// The container referenced by must already exist.
+ ///
+ public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, CloudBlockBlob blobReference)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (blobReference == null)
+ {
+ throw new ArgumentNullException(nameof(blobReference));
+ }
+
+ // We're basically just going to make a copy of this blob.
+ // Use (container, blobName) instead of (storageuri, creds) since the container
+ // is tied to an existing service client, which contains user-settable defaults
+ // like retry policy and secondary connection URIs.
+
+ var container = blobReference.Container;
+ var blobName = blobReference.Name;
+
+ return PersistKeystoAzureBlobStorageInternal(builder, () => container.GetBlockBlobReference(blobName));
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the specified path
+ /// in Azure Blob Storage.
+ ///
+ /// The builder instance to modify.
+ /// The in which the
+ /// key file should be stored.
+ /// The name of the key file, generally specified
+ /// as "[subdir/]keys.xml"
+ /// The value .
+ ///
+ /// The container referenced by must already exist.
+ ///
+ public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, CloudBlobContainer container, string blobName)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (container == null)
+ {
+ throw new ArgumentNullException(nameof(container));
+ }
+ if (blobName == null)
+ {
+ throw new ArgumentNullException(nameof(blobName));
+ }
+ return PersistKeystoAzureBlobStorageInternal(builder, () => container.GetBlockBlobReference(blobName));
+ }
+
+ // important: the Func passed into this method must return a new instance with each call
+ private static IDataProtectionBuilder PersistKeystoAzureBlobStorageInternal(IDataProtectionBuilder builder, Func blobRefFactory)
+ {
+ builder.Services.Configure(options =>
+ {
+ options.XmlRepository = new AzureBlobXmlRepository(blobRefFactory);
+ });
+ return builder;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj
new file mode 100644
index 0000000000..ceb83f3925
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Microsoft Azure Blob storrage support as key store.
+ netstandard2.0
+ true
+ true
+ aspnetcore;dataprotection;azure;blob
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/baseline.netcore.json
new file mode 100644
index 0000000000..09e208bfef
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/baseline.netcore.json
@@ -0,0 +1,156 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.DataProtection.AzureStorage, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.AzureDataProtectionBuilderExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToAzureBlobStorage",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ },
+ {
+ "Name": "storageAccount",
+ "Type": "Microsoft.WindowsAzure.Storage.CloudStorageAccount"
+ },
+ {
+ "Name": "relativePath",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToAzureBlobStorage",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ },
+ {
+ "Name": "blobUri",
+ "Type": "System.Uri"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToAzureBlobStorage",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ },
+ {
+ "Name": "blobReference",
+ "Type": "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToAzureBlobStorage",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ },
+ {
+ "Name": "container",
+ "Type": "Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer"
+ },
+ {
+ "Name": "blobName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.AzureStorage.AzureBlobXmlRepository",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Sealed": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetAllElements",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IReadOnlyCollection",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "StoreElement",
+ "Parameters": [
+ {
+ "Name": "element",
+ "Type": "System.Xml.Linq.XElement"
+ },
+ {
+ "Name": "friendlyName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "blobRefFactory",
+ "Type": "System.Func"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/BitHelpers.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/BitHelpers.cs
new file mode 100644
index 0000000000..eb2063fbd8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/BitHelpers.cs
@@ -0,0 +1,42 @@
+// 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
+{
+ internal static class BitHelpers
+ {
+ ///
+ /// Reads an unsigned 64-bit integer from
+ /// starting at offset . Data is read big-endian.
+ ///
+ public static ulong ReadUInt64(byte[] buffer, int offset)
+ {
+ return (((ulong)buffer[offset + 0]) << 56)
+ | (((ulong)buffer[offset + 1]) << 48)
+ | (((ulong)buffer[offset + 2]) << 40)
+ | (((ulong)buffer[offset + 3]) << 32)
+ | (((ulong)buffer[offset + 4]) << 24)
+ | (((ulong)buffer[offset + 5]) << 16)
+ | (((ulong)buffer[offset + 6]) << 8)
+ | (ulong)buffer[offset + 7];
+ }
+
+ ///
+ /// Writes an unsigned 64-bit integer to starting at
+ /// offset . Data is written big-endian.
+ ///
+ public static void WriteUInt64(byte[] buffer, int offset, ulong value)
+ {
+ buffer[offset + 0] = (byte)(value >> 56);
+ buffer[offset + 1] = (byte)(value >> 48);
+ buffer[offset + 2] = (byte)(value >> 40);
+ buffer[offset + 3] = (byte)(value >> 32);
+ buffer[offset + 4] = (byte)(value >> 24);
+ buffer[offset + 5] = (byte)(value >> 16);
+ buffer[offset + 6] = (byte)(value >> 8);
+ buffer[offset + 7] = (byte)(value);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionAdvancedExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionAdvancedExtensions.cs
new file mode 100644
index 0000000000..6e4c2aabac
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionAdvancedExtensions.cs
@@ -0,0 +1,169 @@
+// 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
+{
+ ///
+ /// Helpful extension methods for data protection APIs.
+ ///
+ public static class DataProtectionAdvancedExtensions
+ {
+ ///
+ /// Cryptographically protects a piece of plaintext data, expiring the data after
+ /// the specified amount of time has elapsed.
+ ///
+ /// The protector to use.
+ /// The plaintext data to protect.
+ /// The amount of time after which the payload should no longer be unprotectable.
+ /// The protected form of the plaintext data.
+ public static byte[] Protect(this ITimeLimitedDataProtector protector, byte[] plaintext, TimeSpan lifetime)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ return protector.Protect(plaintext, DateTimeOffset.UtcNow + lifetime);
+ }
+
+ ///
+ /// Cryptographically protects a piece of plaintext data, expiring the data at
+ /// the chosen time.
+ ///
+ /// The protector to use.
+ /// The plaintext data to protect.
+ /// The time when this payload should expire.
+ /// The protected form of the plaintext data.
+ public static string Protect(this ITimeLimitedDataProtector protector, string plaintext, DateTimeOffset expiration)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ var wrappingProtector = new TimeLimitedWrappingProtector(protector) { Expiration = expiration };
+ return wrappingProtector.Protect(plaintext);
+ }
+
+ ///
+ /// Cryptographically protects a piece of plaintext data, expiring the data after
+ /// the specified amount of time has elapsed.
+ ///
+ /// The protector to use.
+ /// The plaintext data to protect.
+ /// The amount of time after which the payload should no longer be unprotectable.
+ /// The protected form of the plaintext data.
+ public static string Protect(this ITimeLimitedDataProtector protector, string plaintext, TimeSpan lifetime)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ return Protect(protector, plaintext, DateTimeOffset.Now + lifetime);
+ }
+
+ ///
+ /// Converts an into an
+ /// so that payloads can be protected with a finite lifetime.
+ ///
+ /// The to convert to a time-limited protector.
+ /// An .
+ public static ITimeLimitedDataProtector ToTimeLimitedDataProtector(this IDataProtector protector)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ return (protector as ITimeLimitedDataProtector) ?? new TimeLimitedDataProtector(protector);
+ }
+
+ ///
+ /// Cryptographically unprotects a piece of protected data.
+ ///
+ /// The protector to use.
+ /// The protected data to unprotect.
+ /// An 'out' parameter which upon a successful unprotect
+ /// operation receives the expiration date of the payload.
+ /// The plaintext form of the protected data.
+ ///
+ /// Thrown if is invalid, malformed, or expired.
+ ///
+ public static string Unprotect(this ITimeLimitedDataProtector protector, string protectedData, out DateTimeOffset expiration)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ var wrappingProtector = new TimeLimitedWrappingProtector(protector);
+ string retVal = wrappingProtector.Unprotect(protectedData);
+ expiration = wrappingProtector.Expiration;
+ return retVal;
+ }
+
+ private sealed class TimeLimitedWrappingProtector : IDataProtector
+ {
+ public DateTimeOffset Expiration;
+ private readonly ITimeLimitedDataProtector _innerProtector;
+
+ public TimeLimitedWrappingProtector(ITimeLimitedDataProtector innerProtector)
+ {
+ _innerProtector = innerProtector;
+ }
+
+ public IDataProtector CreateProtector(string purpose)
+ {
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ throw new NotImplementedException();
+ }
+
+ public byte[] Protect(byte[] plaintext)
+ {
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ return _innerProtector.Protect(plaintext, Expiration);
+ }
+
+ public byte[] Unprotect(byte[] protectedData)
+ {
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ return _innerProtector.Unprotect(protectedData, out Expiration);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionProvider.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionProvider.cs
new file mode 100644
index 0000000000..7b080a9a87
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionProvider.cs
@@ -0,0 +1,178 @@
+// 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.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains factory methods for creating an where keys are stored
+ /// at a particular location on the file system.
+ ///
+ /// Use these methods when not using dependency injection to provide the service to the application.
+ public static class DataProtectionProvider
+ {
+ ///
+ /// Creates a that store keys in a location based on
+ /// the platform and operating system.
+ ///
+ /// An identifier that uniquely discriminates this application from all other
+ /// applications on the machine.
+ public static IDataProtectionProvider Create(string applicationName)
+ {
+ if (string.IsNullOrEmpty(applicationName))
+ {
+ throw new ArgumentNullException(nameof(applicationName));
+ }
+
+ return CreateProvider(
+ keyDirectory: null,
+ setupAction: builder => { builder.SetApplicationName(applicationName); },
+ certificate: null);
+ }
+
+ ///
+ /// Creates an given a location at which to store keys.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ public static IDataProtectionProvider Create(DirectoryInfo keyDirectory)
+ {
+ if (keyDirectory == null)
+ {
+ throw new ArgumentNullException(nameof(keyDirectory));
+ }
+
+ return CreateProvider(keyDirectory, setupAction: builder => { }, certificate: null);
+ }
+
+ ///
+ /// Creates an given a location at which to store keys and an
+ /// optional configuration callback.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ /// An optional callback which provides further configuration of the data protection
+ /// system. See for more information.
+ public static IDataProtectionProvider Create(
+ DirectoryInfo keyDirectory,
+ Action setupAction)
+ {
+ if (keyDirectory == null)
+ {
+ throw new ArgumentNullException(nameof(keyDirectory));
+ }
+ if (setupAction == null)
+ {
+ throw new ArgumentNullException(nameof(setupAction));
+ }
+
+ return CreateProvider(keyDirectory, setupAction, certificate: null);
+ }
+
+ ///
+ /// Creates a that store keys in a location based on
+ /// the platform and operating system and uses the given to encrypt the keys.
+ ///
+ /// An identifier that uniquely discriminates this application from all other
+ /// applications on the machine.
+ /// The to be used for encryption.
+ public static IDataProtectionProvider Create(string applicationName, X509Certificate2 certificate)
+ {
+ if (string.IsNullOrEmpty(applicationName))
+ {
+ throw new ArgumentNullException(nameof(applicationName));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ return CreateProvider(
+ keyDirectory: null,
+ setupAction: builder => { builder.SetApplicationName(applicationName); },
+ certificate: certificate);
+ }
+
+ ///
+ /// Creates an given a location at which to store keys
+ /// and a used to encrypt the keys.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ /// The to be used for encryption.
+ public static IDataProtectionProvider Create(
+ DirectoryInfo keyDirectory,
+ X509Certificate2 certificate)
+ {
+ if (keyDirectory == null)
+ {
+ throw new ArgumentNullException(nameof(keyDirectory));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ return CreateProvider(keyDirectory, setupAction: builder => { }, certificate: certificate);
+ }
+
+ ///
+ /// Creates an given a location at which to store keys, an
+ /// optional configuration callback and a used to encrypt the keys.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ /// An optional callback which provides further configuration of the data protection
+ /// system. See for more information.
+ /// The to be used for encryption.
+ public static IDataProtectionProvider Create(
+ DirectoryInfo keyDirectory,
+ Action setupAction,
+ X509Certificate2 certificate)
+ {
+ if (keyDirectory == null)
+ {
+ throw new ArgumentNullException(nameof(keyDirectory));
+ }
+ if (setupAction == null)
+ {
+ throw new ArgumentNullException(nameof(setupAction));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ return CreateProvider(keyDirectory, setupAction, certificate);
+ }
+
+ private static IDataProtectionProvider CreateProvider(
+ DirectoryInfo keyDirectory,
+ Action setupAction,
+ X509Certificate2 certificate)
+ {
+ // build the service collection
+ var serviceCollection = new ServiceCollection();
+ var builder = serviceCollection.AddDataProtection();
+
+ if (keyDirectory != null)
+ {
+ builder.PersistKeysToFileSystem(keyDirectory);
+ }
+
+ if (certificate != null)
+ {
+ builder.ProtectKeysWithCertificate(certificate);
+ }
+
+ setupAction(builder);
+
+ // extract the provider instance from the service collection
+ return serviceCollection.BuildServiceProvider().GetRequiredService();
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/ITimeLimitedDataProtector.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/ITimeLimitedDataProtector.cs
new file mode 100644
index 0000000000..71fa609f21
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/ITimeLimitedDataProtector.cs
@@ -0,0 +1,55 @@
+// 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
+{
+ ///
+ /// An interface that can provide data protection services where payloads have
+ /// a finite lifetime.
+ ///
+ ///
+ /// It is intended that payload lifetimes be somewhat short. Payloads protected
+ /// via this mechanism are not intended for long-term persistence (e.g., longer
+ /// than a few weeks).
+ ///
+ public interface ITimeLimitedDataProtector : IDataProtector
+ {
+ ///
+ /// Creates an given a purpose.
+ ///
+ ///
+ /// The purpose to be assigned to the newly-created .
+ ///
+ /// An tied to the provided purpose.
+ ///
+ /// The parameter must be unique for the intended use case; two
+ /// different instances created with two different
+ /// values will not be able to decipher each other's payloads. The parameter
+ /// value is not intended to be kept secret.
+ ///
+ new ITimeLimitedDataProtector CreateProtector(string purpose);
+
+ ///
+ /// Cryptographically protects a piece of plaintext data, expiring the data at
+ /// the chosen time.
+ ///
+ /// The plaintext data to protect.
+ /// The time when this payload should expire.
+ /// The protected form of the plaintext data.
+ byte[] Protect(byte[] plaintext, DateTimeOffset expiration);
+
+ ///
+ /// Cryptographically unprotects a piece of protected data.
+ ///
+ /// The protected data to unprotect.
+ /// An 'out' parameter which upon a successful unprotect
+ /// operation receives the expiration date of the payload.
+ /// The plaintext form of the protected data.
+ ///
+ /// Thrown if is invalid, malformed, or expired.
+ ///
+ byte[] Unprotect(byte[] protectedData, out DateTimeOffset expiration);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Microsoft.AspNetCore.DataProtection.Extensions.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Microsoft.AspNetCore.DataProtection.Extensions.csproj
new file mode 100644
index 0000000000..44885e5711
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Microsoft.AspNetCore.DataProtection.Extensions.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Additional APIs for ASP.NET Core data protection.
+ netstandard2.0
+ true
+ aspnetcore;dataprotection
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..022a5a3e6c
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/AssemblyInfo.cs
@@ -0,0 +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.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..8fba5cd9f2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs
@@ -0,0 +1,72 @@
+//
+namespace Microsoft.AspNetCore.DataProtection.Extensions
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.DataProtection.Extensions.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// An error occurred during a cryptographic operation.
+ ///
+ internal static string CryptCommon_GenericError
+ {
+ get => GetString("CryptCommon_GenericError");
+ }
+
+ ///
+ /// An error occurred during a cryptographic operation.
+ ///
+ internal static string FormatCryptCommon_GenericError()
+ => GetString("CryptCommon_GenericError");
+
+ ///
+ /// The payload expired at {0}.
+ ///
+ internal static string TimeLimitedDataProtector_PayloadExpired
+ {
+ get => GetString("TimeLimitedDataProtector_PayloadExpired");
+ }
+
+ ///
+ /// The payload expired at {0}.
+ ///
+ internal static string FormatTimeLimitedDataProtector_PayloadExpired(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0);
+
+ ///
+ /// The payload is invalid.
+ ///
+ internal static string TimeLimitedDataProtector_PayloadInvalid
+ {
+ get => GetString("TimeLimitedDataProtector_PayloadInvalid");
+ }
+
+ ///
+ /// The payload is invalid.
+ ///
+ internal static string FormatTimeLimitedDataProtector_PayloadInvalid()
+ => GetString("TimeLimitedDataProtector_PayloadInvalid");
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Resources.resx b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Resources.resx
new file mode 100644
index 0000000000..b53d26e321
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Resources.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ An error occurred during a cryptographic operation.
+
+
+ The payload expired at {0}.
+
+
+ The payload is invalid.
+
+
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/TimeLimitedDataProtector.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/TimeLimitedDataProtector.cs
new file mode 100644
index 0000000000..71e9c3c553
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/TimeLimitedDataProtector.cs
@@ -0,0 +1,149 @@
+// 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 System.Threading;
+using Microsoft.AspNetCore.DataProtection.Extensions;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Wraps an existing and appends a purpose that allows
+ /// protecting data with a finite lifetime.
+ ///
+ internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
+ {
+ private const string MyPurposeString = "Microsoft.AspNetCore.DataProtection.TimeLimitedDataProtector.v1";
+
+ private readonly IDataProtector _innerProtector;
+ private IDataProtector _innerProtectorWithTimeLimitedPurpose; // created on-demand
+
+ public TimeLimitedDataProtector(IDataProtector innerProtector)
+ {
+ _innerProtector = innerProtector;
+ }
+
+ public ITimeLimitedDataProtector CreateProtector(string purpose)
+ {
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ return new TimeLimitedDataProtector(_innerProtector.CreateProtector(purpose));
+ }
+
+ private IDataProtector GetInnerProtectorWithTimeLimitedPurpose()
+ {
+ // thread-safe lazy init pattern with multi-execution and single publication
+ var retVal = Volatile.Read(ref _innerProtectorWithTimeLimitedPurpose);
+ if (retVal == null)
+ {
+ var newValue = _innerProtector.CreateProtector(MyPurposeString); // we always append our purpose to the end of the chain
+ retVal = Interlocked.CompareExchange(ref _innerProtectorWithTimeLimitedPurpose, newValue, null) ?? newValue;
+ }
+ return retVal;
+ }
+
+ public byte[] Protect(byte[] plaintext, DateTimeOffset expiration)
+ {
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ // We prepend the expiration time (as a 64-bit UTC tick count) to the unprotected data.
+ byte[] plaintextWithHeader = new byte[checked(8 + plaintext.Length)];
+ BitHelpers.WriteUInt64(plaintextWithHeader, 0, (ulong)expiration.UtcTicks);
+ Buffer.BlockCopy(plaintext, 0, plaintextWithHeader, 8, plaintext.Length);
+
+ return GetInnerProtectorWithTimeLimitedPurpose().Protect(plaintextWithHeader);
+ }
+
+ public byte[] Unprotect(byte[] protectedData, out DateTimeOffset expiration)
+ {
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ return UnprotectCore(protectedData, DateTimeOffset.UtcNow, out expiration);
+ }
+
+ internal byte[] UnprotectCore(byte[] protectedData, DateTimeOffset now, out DateTimeOffset expiration)
+ {
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ try
+ {
+ byte[] plaintextWithHeader = GetInnerProtectorWithTimeLimitedPurpose().Unprotect(protectedData);
+ if (plaintextWithHeader.Length < 8)
+ {
+ // header isn't present
+ throw new CryptographicException(Resources.TimeLimitedDataProtector_PayloadInvalid);
+ }
+
+ // Read expiration time back out of the payload
+ ulong utcTicksExpiration = BitHelpers.ReadUInt64(plaintextWithHeader, 0);
+ DateTimeOffset embeddedExpiration = new DateTimeOffset(checked((long)utcTicksExpiration), TimeSpan.Zero /* UTC */);
+
+ // Are we expired?
+ if (now > embeddedExpiration)
+ {
+ throw new CryptographicException(Resources.FormatTimeLimitedDataProtector_PayloadExpired(embeddedExpiration));
+ }
+
+ // Not expired - split and return payload
+ byte[] retVal = new byte[plaintextWithHeader.Length - 8];
+ Buffer.BlockCopy(plaintextWithHeader, 8, retVal, 0, retVal.Length);
+ expiration = embeddedExpiration;
+ return retVal;
+ }
+ catch (Exception ex) when (ex.RequiresHomogenization())
+ {
+ // Homogenize all failures to CryptographicException
+ throw new CryptographicException(Resources.CryptCommon_GenericError, ex);
+ }
+ }
+
+ /*
+ * EXPLICIT INTERFACE IMPLEMENTATIONS
+ */
+
+ IDataProtector IDataProtectionProvider.CreateProtector(string purpose)
+ {
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ return CreateProtector(purpose);
+ }
+
+ byte[] IDataProtector.Protect(byte[] plaintext)
+ {
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ // MaxValue essentially means 'no expiration'
+ return Protect(plaintext, DateTimeOffset.MaxValue);
+ }
+
+ byte[] IDataProtector.Unprotect(byte[] protectedData)
+ {
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ DateTimeOffset expiration; // unused
+ return Unprotect(protectedData, out expiration);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/baseline.netcore.json
new file mode 100644
index 0000000000..5bb3088d07
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/baseline.netcore.json
@@ -0,0 +1,298 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.DataProtection.Extensions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.DataProtectionAdvancedExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector"
+ },
+ {
+ "Name": "plaintext",
+ "Type": "System.Byte[]"
+ },
+ {
+ "Name": "lifetime",
+ "Type": "System.TimeSpan"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector"
+ },
+ {
+ "Name": "plaintext",
+ "Type": "System.String"
+ },
+ {
+ "Name": "expiration",
+ "Type": "System.DateTimeOffset"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector"
+ },
+ {
+ "Name": "plaintext",
+ "Type": "System.String"
+ },
+ {
+ "Name": "lifetime",
+ "Type": "System.TimeSpan"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ToTimeLimitedDataProtector",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtector"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Unprotect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector"
+ },
+ {
+ "Name": "protectedData",
+ "Type": "System.String"
+ },
+ {
+ "Name": "expiration",
+ "Type": "System.DateTimeOffset",
+ "Direction": "Out"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.DataProtectionProvider",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "applicationName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "keyDirectory",
+ "Type": "System.IO.DirectoryInfo"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "keyDirectory",
+ "Type": "System.IO.DirectoryInfo"
+ },
+ {
+ "Name": "setupAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "applicationName",
+ "Type": "System.String"
+ },
+ {
+ "Name": "certificate",
+ "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "keyDirectory",
+ "Type": "System.IO.DirectoryInfo"
+ },
+ {
+ "Name": "certificate",
+ "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "keyDirectory",
+ "Type": "System.IO.DirectoryInfo"
+ },
+ {
+ "Name": "setupAction",
+ "Type": "System.Action"
+ },
+ {
+ "Name": "certificate",
+ "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.DataProtection.IDataProtector"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CreateProtector",
+ "Parameters": [
+ {
+ "Name": "purpose",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "plaintext",
+ "Type": "System.Byte[]"
+ },
+ {
+ "Name": "expiration",
+ "Type": "System.DateTimeOffset"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Unprotect",
+ "Parameters": [
+ {
+ "Name": "protectedData",
+ "Type": "System.Byte[]"
+ },
+ {
+ "Name": "expiration",
+ "Type": "System.DateTimeOffset",
+ "Direction": "Out"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/Microsoft.AspNetCore.DataProtection.Redis.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/Microsoft.AspNetCore.DataProtection.Redis.csproj
new file mode 100644
index 0000000000..3cad440e37
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/Microsoft.AspNetCore.DataProtection.Redis.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Redis storage support as key store.
+ $(ExperimentalVersionPrefix)
+ $(ExperimentalVersionSuffix)
+ false
+ $(ExperimentalPackageVersion)
+ netstandard2.0
+ true
+ true
+ aspnetcore;dataprotection;redis
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/RedisDataProtectionBuilderExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/RedisDataProtectionBuilderExtensions.cs
new file mode 100644
index 0000000000..97593cbb03
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/RedisDataProtectionBuilderExtensions.cs
@@ -0,0 +1,78 @@
+// 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 StackExchange.Redis;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains Redis-specific extension methods for modifying a .
+ ///
+ public static class RedisDataProtectionBuilderExtensions
+ {
+ private const string DataProtectionKeysName = "DataProtection-Keys";
+
+ ///
+ /// Configures the data protection system to persist keys to specified key in Redis database
+ ///
+ /// The builder instance to modify.
+ /// The delegate used to create instances.
+ /// The used to store key list.
+ /// A reference to the after this operation has completed.
+ public static IDataProtectionBuilder PersistKeysToRedis(this IDataProtectionBuilder builder, Func databaseFactory, RedisKey key)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (databaseFactory == null)
+ {
+ throw new ArgumentNullException(nameof(databaseFactory));
+ }
+ return PersistKeysToRedisInternal(builder, databaseFactory, key);
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the default key ('DataProtection-Keys') in Redis database
+ ///
+ /// The builder instance to modify.
+ /// The for database access.
+ /// A reference to the after this operation has completed.
+ public static IDataProtectionBuilder PersistKeysToRedis(this IDataProtectionBuilder builder, IConnectionMultiplexer connectionMultiplexer)
+ {
+ return PersistKeysToRedis(builder, connectionMultiplexer, DataProtectionKeysName);
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the specified key in Redis database
+ ///
+ /// The builder instance to modify.
+ /// The for database access.
+ /// The used to store key list.
+ /// A reference to the after this operation has completed.
+ public static IDataProtectionBuilder PersistKeysToRedis(this IDataProtectionBuilder builder, IConnectionMultiplexer connectionMultiplexer, RedisKey key)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (connectionMultiplexer == null)
+ {
+ throw new ArgumentNullException(nameof(connectionMultiplexer));
+ }
+ return PersistKeysToRedisInternal(builder, () => connectionMultiplexer.GetDatabase(), key);
+ }
+
+ private static IDataProtectionBuilder PersistKeysToRedisInternal(IDataProtectionBuilder builder, Func databaseFactory, RedisKey key)
+ {
+ builder.Services.Configure(options =>
+ {
+ options.XmlRepository = new RedisXmlRepository(databaseFactory, key);
+ });
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/RedisXmlRepository.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/RedisXmlRepository.cs
new file mode 100644
index 0000000000..87a9338f64
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/RedisXmlRepository.cs
@@ -0,0 +1,59 @@
+// 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.Xml.Linq;
+using StackExchange.Redis;
+using Microsoft.AspNetCore.DataProtection.Repositories;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// An XML repository backed by a Redis list entry.
+ ///
+ public class RedisXmlRepository: IXmlRepository
+ {
+ private readonly Func _databaseFactory;
+ private readonly RedisKey _key;
+
+ ///
+ /// Creates a with keys stored at the given directory.
+ ///
+ /// The delegate used to create instances.
+ /// The used to store key list.
+ public RedisXmlRepository(Func databaseFactory, RedisKey key)
+ {
+ _databaseFactory = databaseFactory;
+ _key = key;
+ }
+
+ ///
+ public IReadOnlyCollection GetAllElements()
+ {
+ return GetAllElementsCore().ToList().AsReadOnly();
+ }
+
+ private IEnumerable GetAllElementsCore()
+ {
+ // Note: Inability to read any value is considered a fatal error (since the file may contain
+ // revocation information), and we'll fail the entire operation rather than return a partial
+ // set of elements. If a value contains well-formed XML but its contents are meaningless, we
+ // won't fail that operation here. The caller is responsible for failing as appropriate given
+ // that scenario.
+ var database = _databaseFactory();
+ foreach (var value in database.ListRange(_key))
+ {
+ yield return XElement.Parse(value);
+ }
+ }
+
+ ///
+ public void StoreElement(XElement element, string friendlyName)
+ {
+ var database = _databaseFactory();
+ database.ListRightPush(_key, element.ToString(SaveOptions.DisableFormatting));
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.SystemWeb/CompatibilityDataProtector.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.SystemWeb/CompatibilityDataProtector.cs
new file mode 100644
index 0000000000..739afe83bd
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.SystemWeb/CompatibilityDataProtector.cs
@@ -0,0 +1,133 @@
+// 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.ComponentModel;
+using System.Configuration;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNetCore.DataProtection.SystemWeb
+{
+ ///
+ /// A that can be used by ASP.NET 4.x to interact with ASP.NET Core's
+ /// DataProtection stack. This type is for internal use only and shouldn't be directly used by
+ /// developers.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class CompatibilityDataProtector : DataProtector
+ {
+ private static readonly Lazy _lazyProtectionProvider = new Lazy(CreateProtectionProvider);
+
+ [ThreadStatic]
+ private static bool _suppressPrimaryPurpose;
+
+ private readonly Lazy _lazyProtector;
+ private readonly Lazy _lazyProtectorSuppressedPrimaryPurpose;
+
+ public CompatibilityDataProtector(string applicationName, string primaryPurpose, string[] specificPurposes)
+ : base("application-name", "primary-purpose", null) // we feed dummy values to the base ctor
+ {
+ // We don't want to evaluate the IDataProtectionProvider factory quite yet,
+ // as we'd rather defer failures to the call to Protect so that we can bubble
+ // up a good error message to the developer.
+
+ _lazyProtector = new Lazy(() => _lazyProtectionProvider.Value.CreateProtector(primaryPurpose, specificPurposes));
+
+ // System.Web always provides "User.MachineKey.Protect" as the primary purpose for calls
+ // to MachineKey.Protect. Only in this case should we allow suppressing the primary
+ // purpose, as then we can easily map calls to MachineKey.Protect(userData, purposes)
+ // into calls to provider.GetProtector(purposes).Protect(userData).
+ if (primaryPurpose == "User.MachineKey.Protect")
+ {
+ _lazyProtectorSuppressedPrimaryPurpose = new Lazy(() => _lazyProtectionProvider.Value.CreateProtector(specificPurposes));
+ }
+ else
+ {
+ _lazyProtectorSuppressedPrimaryPurpose = _lazyProtector;
+ }
+ }
+
+ // We take care of flowing purposes ourselves.
+ protected override bool PrependHashedPurposeToPlaintext { get; } = false;
+
+ // Retrieves the appropriate protector (potentially with a suppressed primary purpose) for this operation.
+ private IDataProtector Protector => ((_suppressPrimaryPurpose) ? _lazyProtectorSuppressedPrimaryPurpose : _lazyProtector).Value;
+
+ private static IDataProtectionProvider CreateProtectionProvider()
+ {
+ // Read from the startup type we need to use, then create it
+ const string APPSETTINGS_KEY = "aspnet:dataProtectionStartupType";
+ string startupTypeName = ConfigurationManager.AppSettings[APPSETTINGS_KEY];
+ if (String.IsNullOrEmpty(startupTypeName))
+ {
+ // fall back to default startup type if one hasn't been specified in config
+ startupTypeName = typeof(DataProtectionStartup).AssemblyQualifiedName;
+ }
+ Type startupType = Type.GetType(startupTypeName, throwOnError: true);
+ var startupInstance = (DataProtectionStartup)Activator.CreateInstance(startupType);
+
+ // Use it to initialize the system.
+ return startupInstance.InternalConfigureServicesAndCreateProtectionProvider();
+ }
+
+ public override bool IsReprotectRequired(byte[] encryptedData)
+ {
+ // Nobody ever calls this.
+ return false;
+ }
+
+ protected override byte[] ProviderProtect(byte[] userData)
+ {
+ try
+ {
+ return Protector.Protect(userData);
+ }
+ catch (Exception ex)
+ {
+ // System.Web special-cases ConfigurationException errors and allows them to bubble
+ // up to the developer without being homogenized. Since a call to Protect should
+ // never fail, any exceptions here really do imply a misconfiguration.
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ throw new ConfigurationException(Resources.DataProtector_ProtectFailed, ex);
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+ }
+
+ protected override byte[] ProviderUnprotect(byte[] encryptedData)
+ {
+ return Protector.Unprotect(encryptedData);
+ }
+
+ ///
+ /// Invokes a delegate where calls to
+ /// and will ignore the primary
+ /// purpose and instead use only the sub-purposes.
+ ///
+ public static byte[] RunWithSuppressedPrimaryPurpose(Func