2
0
Эх сурвалжийг харах

Merge source code from aspnet/DataProtection

Nate McMaster 7 жил өмнө
parent
commit
c355f10074
100 өөрчлөгдсөн 6838 нэмэгдсэн , 0 устгасан
  1. 300 0
      src/DataProtection/DataProtection.sln
  2. 8 0
      src/DataProtection/Directory.Build.props
  3. 117 0
      src/DataProtection/Provision-AutoGenKeys.ps1
  4. 8 0
      src/DataProtection/README.md
  5. 29 0
      src/DataProtection/dependencies.props
  6. 19 0
      src/DataProtection/samples/AzureBlob/AzureBlob.csproj
  7. 43 0
      src/DataProtection/samples/AzureBlob/Program.cs
  8. 20 0
      src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj
  9. 44 0
      src/DataProtection/samples/AzureKeyVault/Program.cs
  10. 5 0
      src/DataProtection/samples/AzureKeyVault/settings.json
  11. 31 0
      src/DataProtection/samples/CustomEncryptorSample/CustomBuilderExtensions.cs
  12. 18 0
      src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj
  13. 32 0
      src/DataProtection/samples/CustomEncryptorSample/CustomXmlDecryptor.cs
  14. 38 0
      src/DataProtection/samples/CustomEncryptorSample/CustomXmlEncryptor.cs
  15. 36 0
      src/DataProtection/samples/CustomEncryptorSample/Program.cs
  16. 13 0
      src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj
  17. 66 0
      src/DataProtection/samples/KeyManagementSample/Program.cs
  18. 12 0
      src/DataProtection/samples/NonDISample/NonDISample.csproj
  19. 41 0
      src/DataProtection/samples/NonDISample/Program.cs
  20. 34 0
      src/DataProtection/samples/Redis/Program.cs
  21. 18 0
      src/DataProtection/samples/Redis/Redis.csproj
  22. 14 0
      src/DataProtection/shared/EncodingUtil.cs
  23. 20 0
      src/DataProtection/shared/ExceptionExtensions.cs
  24. 7 0
      src/DataProtection/src/Directory.Build.props
  25. 38 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.cs
  26. 46 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_KEY_LENGTHS_STRUCT.cs
  27. 17 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBuffer.cs
  28. 26 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBufferDesc.cs
  29. 13 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptEncryptFlags.cs
  30. 15 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptGenRandomFlags.cs
  31. 29 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptKeyDerivationBufferType.cs
  32. 29 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptUtil.cs
  33. 93 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/CachedAlgorithmHandles.cs
  34. 17 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/NCryptEncryptFlags.cs
  35. 66 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/OSVersionUtil.cs
  36. 88 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Constants.cs
  37. 99 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/CryptoUtil.cs
  38. 16 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/DATA_BLOB.cs
  39. 12 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Microsoft.AspNetCore.Cryptography.Internal.csproj
  40. 16 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/AssemblyInfo.cs
  41. 86 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs
  42. 132 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Resources.resx
  43. 170 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptAlgorithmHandle.cs
  44. 30 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHandle.cs
  45. 71 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHashHandle.cs
  46. 33 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptKeyHandle.cs
  47. 26 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/LocalAllocHandle.cs
  48. 42 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/NCryptDescriptorHandle.cs
  49. 176 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SafeLibraryHandle.cs
  50. 61 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SecureLocalAllocHandle.cs
  51. 179 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeBufferUtil.cs
  52. 346 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeNativeMethods.cs
  53. 59 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/WeakReferenceHelpers.cs
  54. 4 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/baseline.netcore.json
  55. 56 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivation.cs
  56. 26 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivationPrf.cs
  57. 15 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj
  58. 15 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/IPbkdf2Provider.cs
  59. 103 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/ManagedPbkdf2Provider.cs
  60. 71 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/NetCorePbkdf2Provider.cs
  61. 46 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Pbkdf2Util.cs
  62. 100 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win7Pbkdf2Provider.cs
  63. 211 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win8Pbkdf2Provider.cs
  64. 6 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Properties/AssemblyInfo.cs
  65. 78 0
      src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/baseline.netcore.json
  66. 33 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/CryptoUtil.cs
  67. 244 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/DataProtectionCommonExtensions.cs
  68. 23 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Error.cs
  69. 26 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtectionProvider.cs
  70. 28 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtector.cs
  71. 25 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Infrastructure/IApplicationDiscriminator.cs
  72. 21 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
  73. 7 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/AssemblyInfo.cs
  74. 86 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs
  75. 132 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Resources.resx
  76. 231 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/baseline.netcore.json
  77. 118 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs
  78. 52 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs
  79. 77 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs
  80. 14 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs
  81. 29 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs
  82. 20 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
  83. 9 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs
  84. 297 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureBlobXmlRepository.cs
  85. 175 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureDataProtectionBuilderExtensions.cs
  86. 19 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj
  87. 156 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/baseline.netcore.json
  88. 42 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/BitHelpers.cs
  89. 169 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionAdvancedExtensions.cs
  90. 178 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionProvider.cs
  91. 55 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/ITimeLimitedDataProtector.cs
  92. 22 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Microsoft.AspNetCore.DataProtection.Extensions.csproj
  93. 6 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/AssemblyInfo.cs
  94. 72 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs
  95. 129 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Resources.resx
  96. 149 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/TimeLimitedDataProtector.cs
  97. 298 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/baseline.netcore.json
  98. 24 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/Microsoft.AspNetCore.DataProtection.Redis.csproj
  99. 78 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/RedisDataProtectionBuilderExtensions.cs
  100. 59 0
      src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/RedisXmlRepository.cs

+ 300 - 0
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

+ 8 - 0
src/DataProtection/Directory.Build.props

@@ -0,0 +1,8 @@
+<Project>
+
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
+
+  <Import Project="version.props" />
+  <Import Project="dependencies.props" />
+
+</Project>

+ 117 - 0
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

+ 8 - 0
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)

+ 29 - 0
src/DataProtection/dependencies.props

@@ -0,0 +1,29 @@
+<Project>
+  <PropertyGroup>
+    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!-- Fallback when not building from command line. -->
+    <InternalAspNetCoreSdkPackageVersion Condition="'$(InternalAspNetCoreSdkPackageVersion)' == ''">2.1.3-rtm-15822</InternalAspNetCoreSdkPackageVersion>
+  </PropertyGroup>
+
+  <!--
+    The versions are present to maintain compatibility with the 2.1.1 release of DataProtection,
+    even though source code and infrastructure has changed.
+   -->
+  <PropertyGroup Label="Package Versions: Overrides">
+    <MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.1</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
+    <MicrosoftAspNetCoreHostingPackageVersion>2.1.1</MicrosoftAspNetCoreHostingPackageVersion>
+    <MicrosoftAspNetCoreTestingPackageVersion>2.1.0</MicrosoftAspNetCoreTestingPackageVersion>
+    <MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.1</MicrosoftExtensionsConfigurationJsonPackageVersion>
+    <MicrosoftExtensionsConfigurationPackageVersion>2.1.1</MicrosoftExtensionsConfigurationPackageVersion>
+    <MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
+    <MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionPackageVersion>
+    <MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
+    <MicrosoftExtensionsLoggingConsolePackageVersion>2.1.1</MicrosoftExtensionsLoggingConsolePackageVersion>
+    <MicrosoftExtensionsLoggingPackageVersion>2.1.1</MicrosoftExtensionsLoggingPackageVersion>
+    <MicrosoftExtensionsOptionsPackageVersion>2.1.1</MicrosoftExtensionsOptionsPackageVersion>
+    <MicrosoftExtensionsWebEncodersSourcesPackageVersion>2.1.1</MicrosoftExtensionsWebEncodersSourcesPackageVersion>
+  </PropertyGroup>
+</Project>

+ 19 - 0
src/DataProtection/samples/AzureBlob/AzureBlob.csproj

@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <OutputType>exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection.AzureStorage\Microsoft.AspNetCore.DataProtection.AzureStorage.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 43 - 0
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);
+            }
+        }
+    }
+}

+ 20 - 0
src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <OutputType>exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection.AzureKeyVault\Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsConfigurationPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 44 - 0
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<X509Certificate2>().Single());
+
+            var serviceProvider = serviceCollection.BuildServiceProvider();
+
+            var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
+            loggerFactory.AddConsole();
+
+            var protector = serviceProvider.GetDataProtector("Test");
+
+            Console.WriteLine(protector.Protect("Hello world"));
+        }
+    }
+}

+ 5 - 0
src/DataProtection/samples/AzureKeyVault/settings.json

@@ -0,0 +1,5 @@
+{
+  "CertificateThumbprint": "",
+  "KeyId": "",
+  "ClientId": ""
+}

+ 31 - 0
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<IServiceProvider, IXmlEncryptor> factory)
+        {
+            builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(serviceProvider =>
+            {
+                var instance = factory(serviceProvider);
+                return new ConfigureOptions<KeyManagementOptions>(options =>
+                {
+                    options.XmlEncryptor = instance;
+                });
+            });
+
+            return builder;
+        }
+    }
+}

+ 18 - 0
src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks>
+    <OutputType>exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection.Extensions\Microsoft.AspNetCore.DataProtection.Extensions.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 32 - 0
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<ILoggerFactory>().CreateLogger<CustomXmlDecryptor>();
+        }
+
+        public XElement Decrypt(XElement encryptedElement)
+        {
+            if (encryptedElement == null)
+            {
+                throw new ArgumentNullException(nameof(encryptedElement));
+            }
+
+            return new XElement(encryptedElement.Elements().Single());
+        }
+    }
+}

+ 38 - 0
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<ILoggerFactory>().CreateLogger<CustomXmlEncryptor>();
+        }
+
+        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;
+        }
+    }
+}

+ 36 - 0
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}");
+            }
+        }
+    }
+}

+ 13 - 0
src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks>
+    <OutputType>exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection.Extensions\Microsoft.AspNetCore.DataProtection.Extensions.csproj" />
+  </ItemGroup>
+
+</Project>

+ 66 - 0
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<IKeyManager>();
+
+                // 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}");
+                }
+            }
+        }
+    }
+}

+ 12 - 0
src/DataProtection/samples/NonDISample/NonDISample.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks>
+    <OutputType>exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection.Extensions\Microsoft.AspNetCore.DataProtection.Extensions.csproj" />
+  </ItemGroup>
+
+</Project>

+ 41 - 0
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}");
+        }
+    }
+}

+ 34 - 0
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);
+            }
+        }
+    }
+}

+ 18 - 0
src/DataProtection/samples/Redis/Redis.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks>
+    <OutputType>exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection.Redis\Microsoft.AspNetCore.DataProtection.Redis.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 14 - 0
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);
+    }
+}

+ 20 - 0
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
+    {
+        /// <summary>
+        /// Determines whether an exception must be homogenized by being wrapped inside a
+        /// CryptographicException before being rethrown.
+        /// </summary>
+        public static bool RequiresHomogenization(this Exception ex)
+        {
+            return !(ex is CryptographicException);
+        }
+    }
+}

+ 7 - 0
src/DataProtection/src/Directory.Build.props

@@ -0,0 +1,7 @@
+<Project>
+  <Import Project="..\Directory.Build.props" />
+
+  <ItemGroup>
+    <PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
+  </ItemGroup>
+</Project>

+ 38 - 0
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
+            };
+        }
+    }
+}

+ 46 - 0
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);
+            }
+        }
+    }
+}

+ 17 - 0
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
+    }
+}

+ 26 - 0
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;
+        }
+    }
+}

+ 13 - 0
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,
+    }
+}

+ 15 - 0
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,
+    }
+}

+ 29 - 0
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,
+    }
+}

+ 29 - 0
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
+{
+    /// <summary>
+    /// Wraps utility BCRYPT APIs that don't work directly with handles.
+    /// </summary>
+    internal unsafe static class BCryptUtil
+    {
+        /// <summary>
+        /// Fills a buffer with cryptographically secure random data.
+        /// </summary>
+        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);
+            }
+        }
+    }
+}

+ 93 - 0
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
+{
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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<BCryptAlgorithmHandle> _algorithmHandle;
+            private readonly Func<BCryptAlgorithmHandle> _factory;
+
+            public CachedAlgorithmInfo(Func<BCryptAlgorithmHandle> factory)
+            {
+                _algorithmHandle = null;
+                _factory = factory;
+            }
+
+            public static BCryptAlgorithmHandle GetAlgorithmHandle(ref CachedAlgorithmInfo cachedAlgorithmInfo)
+            {
+                return WeakReferenceHelpers.GetSharedInstance(ref cachedAlgorithmInfo._algorithmHandle, cachedAlgorithmInfo._factory);
+            }
+        }
+    }
+}

+ 17 - 0
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,
+    }
+}

+ 66 - 0
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
+        }
+    }
+}

+ 88 - 0
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";
+    }
+}

+ 99 - 0
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<T>(message);" as a convenience to guard
+        // against a method returning null unexpectedly.
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static T Fail<T>(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;
+        }
+    }
+}

+ 16 - 0
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;
+    }
+}

+ 12 - 0
src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Microsoft.AspNetCore.Cryptography.Internal.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>Infrastructure for ASP.NET Core cryptographic packages. Applications and libraries should not reference this package directly.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;dataprotection</PackageTags>
+  </PropertyGroup>
+
+</Project>

+ 16 - 0
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")]

+ 86 - 0
src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs

@@ -0,0 +1,86 @@
+// <auto-generated />
+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);
+
+        /// <summary>
+        /// A provider could not be found for algorithm '{0}'.
+        /// </summary>
+        internal static string BCryptAlgorithmHandle_ProviderNotFound
+        {
+            get => GetString("BCryptAlgorithmHandle_ProviderNotFound");
+        }
+
+        /// <summary>
+        /// A provider could not be found for algorithm '{0}'.
+        /// </summary>
+        internal static string FormatBCryptAlgorithmHandle_ProviderNotFound(object p0)
+            => string.Format(CultureInfo.CurrentCulture, GetString("BCryptAlgorithmHandle_ProviderNotFound"), p0);
+
+        /// <summary>
+        /// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
+        /// </summary>
+        internal static string BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength
+        {
+            get => GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength");
+        }
+
+        /// <summary>
+        /// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
+        /// </summary>
+        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);
+
+        /// <summary>
+        /// This operation requires Windows 7 / Windows Server 2008 R2 or later.
+        /// </summary>
+        internal static string Platform_Windows7Required
+        {
+            get => GetString("Platform_Windows7Required");
+        }
+
+        /// <summary>
+        /// This operation requires Windows 7 / Windows Server 2008 R2 or later.
+        /// </summary>
+        internal static string FormatPlatform_Windows7Required()
+            => GetString("Platform_Windows7Required");
+
+        /// <summary>
+        /// This operation requires Windows 8 / Windows Server 2012 or later.
+        /// </summary>
+        internal static string Platform_Windows8Required
+        {
+            get => GetString("Platform_Windows8Required");
+        }
+
+        /// <summary>
+        /// This operation requires Windows 8 / Windows Server 2012 or later.
+        /// </summary>
+        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;
+        }
+    }
+}

+ 132 - 0
src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Resources.resx

@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="BCryptAlgorithmHandle_ProviderNotFound" xml:space="preserve">
+    <value>A provider could not be found for algorithm '{0}'.</value>
+  </data>
+  <data name="BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength" xml:space="preserve">
+    <value>The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).</value>
+  </data>
+  <data name="Platform_Windows7Required" xml:space="preserve">
+    <value>This operation requires Windows 7 / Windows Server 2008 R2 or later.</value>
+  </data>
+  <data name="Platform_Windows8Required" xml:space="preserve">
+    <value>This operation requires Windows 8 / Windows Server 2012 or later.</value>
+  </data>
+</root>

+ 170 - 0
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
+{
+    /// <summary>
+    /// Represents a handle to a BCrypt algorithm provider from which keys and hashes can be created.
+    /// </summary>
+    internal unsafe sealed class BCryptAlgorithmHandle : BCryptHandle
+    {
+        // Called by P/Invoke when returning SafeHandles
+        private BCryptAlgorithmHandle() { }
+
+        /// <summary>
+        /// Creates an unkeyed hash handle from this hash algorithm.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Creates an HMAC hash handle from this hash algorithm.
+        /// </summary>
+        public BCryptHashHandle CreateHmac(byte* pbKey, uint cbKey)
+        {
+            Debug.Assert(pbKey != null);
+            return CreateHashCore(pbKey, cbKey);
+        }
+
+        /// <summary>
+        /// Imports a key into a symmetric encryption or KDF algorithm.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Gets the name of this BCrypt algorithm.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Gets the cipher block length (in bytes) of this block cipher algorithm.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Gets the hash block length (in bytes) of this hash algorithm.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Gets the key lengths (in bits) supported by this algorithm.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Gets the digest length (in bytes) of this hash algorithm provider.
+        /// </summary>
+        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)));
+            }
+        }
+    }
+}

+ 30 - 0
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);
+        }
+    }
+}

+ 71 - 0
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() { }
+
+        /// <summary>
+        /// Duplicates this hash handle, including any existing hashed state.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Calculates the cryptographic hash over a set of input data.
+        /// </summary>
+        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;
+        }
+    }
+}

+ 33 - 0
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;
+        }
+    }
+}

+ 26 - 0
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
+{
+    /// <summary>
+    /// Represents a handle returned by LocalAlloc.
+    /// </summary>
+    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;
+        }
+    }
+}

+ 42 - 0
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);
+        }
+    }
+}

+ 176 - 0
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
+{
+    /// <summary>
+    /// Represents a handle to a Windows module (DLL).
+    /// </summary>
+    internal unsafe sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
+    {
+        // Called by P/Invoke when returning SafeHandles
+        private SafeLibraryHandle()
+            : base(ownsHandle: true)
+        { }
+
+        /// <summary>
+        /// Returns a value stating whether the library exports a given proc.
+        /// </summary>
+        public bool DoesProcExist(string lpProcName)
+        {
+            IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName);
+            return (pfnProc != IntPtr.Zero);
+        }
+
+        /// <summary>
+        /// Forbids this library from being unloaded. The library will remain loaded until process termination,
+        /// regardless of how many times FreeLibrary is called.
+        /// </summary>
+        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();
+            }
+        }
+
+        /// <summary>
+        /// Formats a message string using the resource table in the specified library.
+        /// </summary>
+        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;
+            }
+        }
+
+        /// <summary>
+        /// Gets a delegate pointing to a given export from this library.
+        /// </summary>
+        public TDelegate GetProcAddress<TDelegate>(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<TDelegate>(pfnProc);
+        }
+
+        /// <summary>
+        /// Opens a library. If 'filename' is not a fully-qualified path, the default search path is used.
+        /// </summary>
+        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);
+            }
+        }
+    }
+}

+ 61 - 0
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
+{
+    /// <summary>
+    /// Represents a handle returned by LocalAlloc.
+    /// The memory will be zeroed out before it's freed.
+    /// </summary>
+    internal unsafe sealed class SecureLocalAllocHandle : LocalAllocHandle
+    {
+        private readonly IntPtr _cb;
+
+        private SecureLocalAllocHandle(IntPtr cb)
+        {
+            _cb = cb;
+        }
+
+        public IntPtr Length
+        {
+            get
+            {
+                return _cb;
+            }
+        }
+
+        /// <summary>
+        /// Allocates some amount of memory using LocalAlloc.
+        /// </summary>
+        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();
+        }
+    }
+}

+ 179 - 0
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);
+        }
+
+        /// <summary>
+        /// Securely clears a memory buffer.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+        public static void SecureZeroMemory(byte* buffer, int byteCount)
+        {
+            SecureZeroMemory(buffer, checked((uint)byteCount));
+        }
+
+        /// <summary>
+        /// Securely clears a memory buffer.
+        /// </summary>
+        [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);
+            }
+        }
+
+        /// <summary>
+        /// Securely clears a memory buffer.
+        /// </summary>
+        [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);
+            }
+        }
+
+        /// <summary>
+        /// Securely clears a memory buffer.
+        /// </summary>
+        [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());
+            }
+        }
+    }
+}

+ 346 - 0
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<SafeLibraryHandle> _lazyBCryptLibHandle = GetLazyLibraryHandle(BCRYPT_LIB);
+
+        private const string CRYPT32_LIB = "crypt32.dll";
+        private static readonly Lazy<SafeLibraryHandle> _lazyCrypt32LibHandle = GetLazyLibraryHandle(CRYPT32_LIB);
+
+        private const string NCRYPT_LIB = "ncrypt.dll";
+        private static readonly Lazy<SafeLibraryHandle> _lazyNCryptLibHandle = GetLazyLibraryHandle(NCRYPT_LIB);
+
+        private static Lazy<SafeLibraryHandle> GetLazyLibraryHandle(string libraryName)
+        {
+            // We don't need to worry about race conditions: SafeLibraryHandle will clean up after itself
+            return new Lazy<SafeLibraryHandle>(() => 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);
+        }
+    }
+}

+ 59 - 0
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<T>(ref WeakReference<T> weakReference, Func<T> factory)
+            where T : class, IDisposable
+        {
+            // First, see if the WR already exists and points to a live object.
+            WeakReference<T> existingWeakRef = Volatile.Read(ref weakReference);
+            T newTarget = null;
+            WeakReference<T> 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<T>(newTarget);
+                }
+                Debug.Assert(newWeakRef != null);
+
+                // Try replacing the existing WR with our newly-created one.
+                WeakReference<T> 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;
+            }
+        }
+    }
+}

+ 4 - 0
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": []
+}

+ 56 - 0
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
+{
+    /// <summary>
+    /// Provides algorithms for performing key derivation.
+    /// </summary>
+    public static class KeyDerivation
+    {
+        /// <summary>
+        /// Performs key derivation using the PBKDF2 algorithm.
+        /// </summary>
+        /// <param name="password">The password from which to derive the key.</param>
+        /// <param name="salt">The salt to be used during the key derivation process.</param>
+        /// <param name="prf">The pseudo-random function to be used in the key derivation process.</param>
+        /// <param name="iterationCount">The number of iterations of the pseudo-random function to apply
+        /// during the key derivation process.</param>
+        /// <param name="numBytesRequested">The desired length (in bytes) of the derived key.</param>
+        /// <returns>The derived key.</returns>
+        /// <remarks>
+        /// The PBKDF2 algorithm is specified in RFC 2898.
+        /// </remarks>
+        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);
+        }
+    }
+}

+ 26 - 0
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
+{
+    /// <summary>
+    /// Specifies the PRF which should be used for the key derivation algorithm.
+    /// </summary>
+    public enum KeyDerivationPrf
+    {
+        /// <summary>
+        /// The HMAC algorithm (RFC 2104) using the SHA-1 hash function (FIPS 180-4).
+        /// </summary>
+        HMACSHA1,
+
+        /// <summary>
+        /// The HMAC algorithm (RFC 2104) using the SHA-256 hash function (FIPS 180-4).
+        /// </summary>
+        HMACSHA256,
+
+        /// <summary>
+        /// The HMAC algorithm (RFC 2104) using the SHA-512 hash function (FIPS 180-4).
+        /// </summary>
+        HMACSHA512,
+    }
+}

+ 15 - 0
src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core utilities for key derivation.</Description>
+    <TargetFrameworks>netstandard2.0;netcoreapp2.0</TargetFrameworks>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;dataprotection</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.AspNetCore.Cryptography.Internal\Microsoft.AspNetCore.Cryptography.Internal.csproj" />
+  </ItemGroup>
+
+</Project>

+ 15 - 0
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
+{
+    /// <summary>
+    /// Internal interface used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
+    /// </summary>
+    internal interface IPbkdf2Provider
+    {
+        byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested);
+    }
+}

+ 103 - 0
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
+{
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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];
+            }
+        }
+    }
+}

+ 71 - 0
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
+{
+    /// <summary>
+    /// Implements Pbkdf2 using <see cref="Rfc2898DeriveBytes"/>.
+    /// </summary>
+    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

+ 46 - 0
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
+{
+    /// <summary>
+    /// Internal base class used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
+    /// </summary>
+    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
+        }
+    }
+}

+ 100 - 0
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
+{
+    /// <summary>
+    /// A PBKDF2 provider which utilizes the Win7 API BCryptDeriveKeyPBKDF2.
+    /// </summary>
+    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.");
+            }
+        }
+    }
+}

+ 211 - 0
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
+{
+    /// <summary>
+    /// A PBKDF2 provider which utilizes the Win8 API BCryptKeyDerivation.
+    /// </summary>
+    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.");
+            }
+        }
+    }
+}

+ 6 - 0
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")]

+ 78 - 0
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": []
+    }
+  ]
+}

+ 33 - 0
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<T>(message);" as a convenience to guard
+        // against a method returning null unexpectedly.
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static T Fail<T>(string message) where T : class
+        {
+            throw Fail(message);
+        }
+    }
+}

+ 244 - 0
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
+{
+    /// <summary>
+    /// Helpful extension methods for data protection APIs.
+    /// </summary>
+    public static class DataProtectionCommonExtensions
+    {
+        /// <summary>
+        /// Creates an <see cref="IDataProtector"/> given a list of purposes.
+        /// </summary>
+        /// <param name="provider">The <see cref="IDataProtectionProvider"/> from which to generate the purpose chain.</param>
+        /// <param name="purposes">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.</param>
+        /// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
+        /// <remarks>
+        /// This is a convenience method which chains together several calls to
+        /// <see cref="IDataProtectionProvider.CreateProtector(string)"/>. See that method's
+        /// documentation for more information.
+        /// </remarks>
+        public static IDataProtector CreateProtector(this IDataProtectionProvider provider, IEnumerable<string> 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<IDataProtector>("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;
+        }
+
+        /// <summary>
+        /// Creates an <see cref="IDataProtector"/> given a list of purposes.
+        /// </summary>
+        /// <param name="provider">The <see cref="IDataProtectionProvider"/> from which to generate the purpose chain.</param>
+        /// <param name="purpose">The primary purpose used to create the <see cref="IDataProtector"/>.</param>
+        /// <param name="subPurposes">An optional list of secondary purposes which contribute to the purpose chain.
+        /// If this list is provided it cannot contain null elements.</param>
+        /// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
+        /// <remarks>
+        /// This is a convenience method which chains together several calls to
+        /// <see cref="IDataProtectionProvider.CreateProtector(string)"/>. See that method's
+        /// documentation for more information.
+        /// </remarks>
+        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<string>)subPurposes);
+            }
+            return protector ?? CryptoUtil.Fail<IDataProtector>("CreateProtector returned null.");
+        }
+
+        /// <summary>
+        /// Retrieves an <see cref="IDataProtectionProvider"/> from an <see cref="IServiceProvider"/>.
+        /// </summary>
+        /// <param name="services">The service provider from which to retrieve the <see cref="IDataProtectionProvider"/>.</param>
+        /// <returns>An <see cref="IDataProtectionProvider"/>. This method is guaranteed never to return null.</returns>
+        /// <exception cref="InvalidOperationException">If no <see cref="IDataProtectionProvider"/> service exists in <paramref name="services"/>.</exception>
+        public static IDataProtectionProvider GetDataProtectionProvider(this IServiceProvider services)
+        {
+            if (services == null)
+            {
+                throw new ArgumentNullException(nameof(services));
+            }
+
+            // We have our own implementation of GetRequiredService<T> 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;
+        }
+
+        /// <summary>
+        /// Retrieves an <see cref="IDataProtector"/> from an <see cref="IServiceProvider"/> given a list of purposes.
+        /// </summary>
+        /// <param name="services">An <see cref="IServiceProvider"/> which contains the <see cref="IDataProtectionProvider"/>
+        /// from which to generate the purpose chain.</param>
+        /// <param name="purposes">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.</param>
+        /// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
+        /// <remarks>
+        /// This is a convenience method which calls <see cref="GetDataProtectionProvider(IServiceProvider)"/>
+        /// then <see cref="CreateProtector(IDataProtectionProvider, IEnumerable{string})"/>. See those methods'
+        /// documentation for more information.
+        /// </remarks>
+        public static IDataProtector GetDataProtector(this IServiceProvider services, IEnumerable<string> purposes)
+        {
+            if (services == null)
+            {
+                throw new ArgumentNullException(nameof(services));
+            }
+
+            if (purposes == null)
+            {
+                throw new ArgumentNullException(nameof(purposes));
+            }
+
+            return services.GetDataProtectionProvider().CreateProtector(purposes);
+        }
+
+        /// <summary>
+        /// Retrieves an <see cref="IDataProtector"/> from an <see cref="IServiceProvider"/> given a list of purposes.
+        /// </summary>
+        /// <param name="services">An <see cref="IServiceProvider"/> which contains the <see cref="IDataProtectionProvider"/>
+        /// from which to generate the purpose chain.</param>
+        /// <param name="purpose">The primary purpose used to create the <see cref="IDataProtector"/>.</param>
+        /// <param name="subPurposes">An optional list of secondary purposes which contribute to the purpose chain.
+        /// If this list is provided it cannot contain null elements.</param>
+        /// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
+        /// <remarks>
+        /// This is a convenience method which calls <see cref="GetDataProtectionProvider(IServiceProvider)"/>
+        /// then <see cref="CreateProtector(IDataProtectionProvider, string, string[])"/>. See those methods'
+        /// documentation for more information.
+        /// </remarks>
+        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);
+        }
+
+        /// <summary>
+        /// Cryptographically protects a piece of plaintext data.
+        /// </summary>
+        /// <param name="protector">The data protector to use for this operation.</param>
+        /// <param name="plaintext">The plaintext data to protect.</param>
+        /// <returns>The protected form of the plaintext data.</returns>
+        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);
+            }
+        }
+
+        /// <summary>
+        /// Cryptographically unprotects a piece of protected data.
+        /// </summary>
+        /// <param name="protector">The data protector to use for this operation.</param>
+        /// <param name="protectedData">The protected data to unprotect.</param>
+        /// <returns>The plaintext form of the protected data.</returns>
+        /// <exception cref="System.Security.Cryptography.CryptographicException">
+        /// Thrown if <paramref name="protectedData"/> is invalid or malformed.
+        /// </exception>
+        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);
+            }
+        }
+    }
+}

+ 23 - 0
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);
+        }
+    }
+}

+ 26 - 0
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
+{
+    /// <summary>
+    /// An interface that can be used to create <see cref="IDataProtector"/> instances.
+    /// </summary>
+    public interface IDataProtectionProvider
+    {
+        /// <summary>
+        /// Creates an <see cref="IDataProtector"/> given a purpose.
+        /// </summary>
+        /// <param name="purpose">
+        /// The purpose to be assigned to the newly-created <see cref="IDataProtector"/>.
+        /// </param>
+        /// <returns>An IDataProtector tied to the provided purpose.</returns>
+        /// <remarks>
+        /// The <paramref name="purpose"/> parameter must be unique for the intended use case; two
+        /// different <see cref="IDataProtector"/> instances created with two different <paramref name="purpose"/>
+        /// values will not be able to decipher each other's payloads. The <paramref name="purpose"/> parameter
+        /// value is not intended to be kept secret.
+        /// </remarks>
+        IDataProtector CreateProtector(string purpose);
+    }
+}

+ 28 - 0
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
+{
+    /// <summary>
+    /// An interface that can provide data protection services.
+    /// </summary>
+    public interface IDataProtector : IDataProtectionProvider
+    {
+        /// <summary>
+        /// Cryptographically protects a piece of plaintext data.
+        /// </summary>
+        /// <param name="plaintext">The plaintext data to protect.</param>
+        /// <returns>The protected form of the plaintext data.</returns>
+        byte[] Protect(byte[] plaintext);
+
+        /// <summary>
+        /// Cryptographically unprotects a piece of protected data.
+        /// </summary>
+        /// <param name="protectedData">The protected data to unprotect.</param>
+        /// <returns>The plaintext form of the protected data.</returns>
+        /// <exception cref="System.Security.Cryptography.CryptographicException">
+        /// Thrown if the protected data is invalid or malformed.
+        /// </exception>
+        byte[] Unprotect(byte[] protectedData);
+    }
+}

+ 25 - 0
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
+{
+    /// <summary>
+    /// Provides information used to discriminate applications.
+    /// </summary>
+    /// <remarks>
+    /// This type supports the data protection system and is not intended to be used
+    /// by consumers.
+    /// </remarks>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public interface IApplicationDiscriminator
+    {
+        /// <summary>
+        /// An identifier that uniquely discriminates this application from all other
+        /// applications on the machine.
+        /// </summary>
+        string Discriminator { get; }
+    }
+}

+ 21 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Microsoft.AspNetCore.DataProtection.Abstractions.csproj

@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core data protection abstractions.
+Commonly used types:
+Microsoft.AspNetCore.DataProtection.IDataProtectionProvider
+Microsoft.AspNetCore.DataProtection.IDataProtector</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;dataprotection</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="..\..\shared\*.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.WebEncoders.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsWebEncodersSourcesPackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 7 - 0
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")]

+ 86 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs

@@ -0,0 +1,86 @@
+// <auto-generated />
+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);
+
+        /// <summary>
+        /// The payload was invalid.
+        /// </summary>
+        internal static string CryptCommon_PayloadInvalid
+        {
+            get => GetString("CryptCommon_PayloadInvalid");
+        }
+
+        /// <summary>
+        /// The payload was invalid.
+        /// </summary>
+        internal static string FormatCryptCommon_PayloadInvalid()
+            => GetString("CryptCommon_PayloadInvalid");
+
+        /// <summary>
+        /// The purposes collection cannot be null or empty and cannot contain null elements.
+        /// </summary>
+        internal static string DataProtectionExtensions_NullPurposesCollection
+        {
+            get => GetString("DataProtectionExtensions_NullPurposesCollection");
+        }
+
+        /// <summary>
+        /// The purposes collection cannot be null or empty and cannot contain null elements.
+        /// </summary>
+        internal static string FormatDataProtectionExtensions_NullPurposesCollection()
+            => GetString("DataProtectionExtensions_NullPurposesCollection");
+
+        /// <summary>
+        /// An error occurred during a cryptographic operation.
+        /// </summary>
+        internal static string CryptCommon_GenericError
+        {
+            get => GetString("CryptCommon_GenericError");
+        }
+
+        /// <summary>
+        /// An error occurred during a cryptographic operation.
+        /// </summary>
+        internal static string FormatCryptCommon_GenericError()
+            => GetString("CryptCommon_GenericError");
+
+        /// <summary>
+        /// No service for type '{0}' has been registered.
+        /// </summary>
+        internal static string DataProtectionExtensions_NoService
+        {
+            get => GetString("DataProtectionExtensions_NoService");
+        }
+
+        /// <summary>
+        /// No service for type '{0}' has been registered.
+        /// </summary>
+        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;
+        }
+    }
+}

+ 132 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Resources.resx

@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="CryptCommon_PayloadInvalid" xml:space="preserve">
+    <value>The payload was invalid.</value>
+  </data>
+  <data name="DataProtectionExtensions_NullPurposesCollection" xml:space="preserve">
+    <value>The purposes collection cannot be null or empty and cannot contain null elements.</value>
+  </data>
+  <data name="CryptCommon_GenericError" xml:space="preserve">
+    <value>An error occurred during a cryptographic operation.</value>
+  </data>
+  <data name="DataProtectionExtensions_NoService" xml:space="preserve">
+    <value>No service for type '{0}' has been registered.</value>
+  </data>
+</root>

+ 231 - 0
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<System.String>"
+            }
+          ],
+          "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<System.String>"
+            }
+          ],
+          "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": []
+    }
+  ]
+}

+ 118 - 0
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
+{
+    /// <summary>
+    /// Contains Azure KeyVault-specific extension methods for modifying a <see cref="IDataProtectionBuilder"/>.
+    /// </summary>
+    public static class AzureDataProtectionBuilderExtensions
+    {
+        /// <summary>
+        /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="keyIdentifier">The Azure KeyVault key identifier used for key encryption.</param>
+        /// <param name="clientId">The application client id.</param>
+        /// <param name="certificate"></param>
+        /// <returns>The value <paramref name="builder"/>.</returns>
+        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<string> 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;
+        }
+
+        /// <summary>
+        /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="keyIdentifier">The Azure KeyVault key identifier used for key encryption.</param>
+        /// <param name="clientId">The application client id.</param>
+        /// <param name="clientSecret">The client secret to use for authentication.</param>
+        /// <returns>The value <paramref name="builder"/>.</returns>
+        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<string> 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;
+        }
+
+        /// <summary>
+        /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="client">The <see cref="KeyVaultClient"/> to use for KeyVault access.</param>
+        /// <param name="keyIdentifier">The Azure KeyVault key identifier used for key encryption.</param>
+        /// <returns>The value <paramref name="builder"/>.</returns>
+        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<IKeyVaultWrappingClient>(vaultClientWrapper);
+            builder.Services.Configure<KeyManagementOptions>(options =>
+            {
+                options.XmlEncryptor = new AzureKeyVaultXmlEncryptor(vaultClientWrapper, keyIdentifier);
+            });
+
+            return builder;
+        }
+    }
+}

+ 52 - 0
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<IKeyVaultWrappingClient>();
+        }
+
+        public XElement Decrypt(XElement encryptedElement)
+        {
+            return DecryptAsync(encryptedElement).GetAwaiter().GetResult();
+        }
+
+        private async Task<XElement> 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);
+            }
+        }
+    }
+}

+ 77 - 0
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<SymmetricAlgorithm> 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<EncryptedXmlInfo> 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));
+            }
+
+        }
+    }
+}

+ 14 - 0
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<KeyOperationResult> UnwrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText);
+        Task<KeyOperationResult> WrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText);
+    }
+}

+ 29 - 0
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<KeyOperationResult> UnwrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText)
+        {
+            return _client.UnwrapKeyAsync(keyIdentifier, algorithm, cipherText);
+        }
+
+        public Task<KeyOperationResult> WrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText)
+        {
+            return _client.WrapKeyAsync(keyIdentifier, algorithm, cipherText);
+        }
+    }
+}

+ 20 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>Microsoft Azure KeyVault key encryption support.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;dataprotection;azure;keyvault</PackageTags>
+    <EnableApiCheck>false</EnableApiCheck>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="$(MicrosoftIdentityModelClientsActiveDirectoryPackageVersion)" />
+    <PackageReference Include="Microsoft.Azure.KeyVault" Version="$(MicrosoftAzureKeyVaultPackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 9 - 0
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")]

+ 297 - 0
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
+{
+    /// <summary>
+    /// An <see cref="IXmlRepository"/> which is backed by Azure Blob Storage.
+    /// </summary>
+    /// <remarks>
+    /// Instances of this type are thread-safe.
+    /// </remarks>
+    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<ICloudBlob> _blobRefFactory;
+        private readonly Random _random;
+        private BlobData _cachedBlobData;
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="AzureBlobXmlRepository"/>.
+        /// </summary>
+        /// <param name="blobRefFactory">A factory which can create <see cref="ICloudBlob"/>
+        /// instances. The factory must be thread-safe for invocation by multiple
+        /// concurrent threads, and each invocation must return a new object.</param>
+        public AzureBlobXmlRepository(Func<ICloudBlob> blobRefFactory)
+        {
+            if (blobRefFactory == null)
+            {
+                throw new ArgumentNullException(nameof(blobRefFactory));
+            }
+
+            _blobRefFactory = blobRefFactory;
+            _random = new Random();
+        }
+
+        /// <inheritdoc />
+        public IReadOnlyCollection<XElement> 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<XElement>(elements);
+        }
+
+        /// <inheritdoc />
+        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<IList<XElement>> 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:
+            //
+            // <root>
+            //   <child />
+            //   <child />
+            //   ...
+            // </root>
+            //
+            // 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<BlobData> 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;
+        }
+    }
+}

+ 175 - 0
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
+{
+    /// <summary>
+    /// Contains Azure-specific extension methods for modifying a
+    /// <see cref="IDataProtectionBuilder"/>.
+    /// </summary>
+    public static class AzureDataProtectionBuilderExtensions
+    {
+        /// <summary>
+        /// Configures the data protection system to persist keys to the specified path
+        /// in Azure Blob Storage.
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="storageAccount">The <see cref="CloudStorageAccount"/> which
+        /// should be utilized.</param>
+        /// <param name="relativePath">A relative path where the key file should be
+        /// stored, generally specified as "/containerName/[subDir/]keys.xml".</param>
+        /// <returns>The value <paramref name="builder"/>.</returns>
+        /// <remarks>
+        /// The container referenced by <paramref name="relativePath"/> must already exist.
+        /// </remarks>
+        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));
+        }
+
+        /// <summary>
+        /// Configures the data protection system to persist keys to the specified path
+        /// in Azure Blob Storage.
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="blobUri">The full URI where the key file should be stored.
+        /// The URI must contain the SAS token as a query string parameter.</param>
+        /// <returns>The value <paramref name="builder"/>.</returns>
+        /// <remarks>
+        /// The container referenced by <paramref name="blobUri"/> must already exist.
+        /// </remarks>
+        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));
+        }
+
+        /// <summary>
+        /// Configures the data protection system to persist keys to the specified path
+        /// in Azure Blob Storage.
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="blobReference">The <see cref="CloudBlockBlob"/> where the
+        /// key file should be stored.</param>
+        /// <returns>The value <paramref name="builder"/>.</returns>
+        /// <remarks>
+        /// The container referenced by <paramref name="blobReference"/> must already exist.
+        /// </remarks>
+        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));
+        }
+
+        /// <summary>
+        /// Configures the data protection system to persist keys to the specified path
+        /// in Azure Blob Storage.
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="container">The <see cref="CloudBlobContainer"/> in which the
+        /// key file should be stored.</param>
+        /// <param name="blobName">The name of the key file, generally specified
+        /// as "[subdir/]keys.xml"</param>
+        /// <returns>The value <paramref name="builder"/>.</returns>
+        /// <remarks>
+        /// The container referenced by <paramref name="container"/> must already exist.
+        /// </remarks>
+        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<CloudBlockBlob> blobRefFactory)
+        {
+            builder.Services.Configure<KeyManagementOptions>(options =>
+            {
+                options.XmlRepository = new AzureBlobXmlRepository(blobRefFactory);
+            });
+            return builder;
+        }
+    }
+}

+ 19 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj

@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>Microsoft Azure Blob storrage support as key store.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;dataprotection;azure;blob</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="WindowsAzure.Storage" Version="$(WindowsAzureStoragePackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 156 - 0
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<System.Xml.Linq.XElement>",
+          "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<Microsoft.WindowsAzure.Storage.Blob.ICloudBlob>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}

+ 42 - 0
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
+    {
+        /// <summary>
+        /// Reads an unsigned 64-bit integer from <paramref name="buffer"/>
+        /// starting at offset <paramref name="offset"/>. Data is read big-endian.
+        /// </summary>
+        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];
+        }
+
+        /// <summary>
+        /// Writes an unsigned 64-bit integer to <paramref name="buffer"/> starting at
+        /// offset <paramref name="offset"/>. Data is written big-endian.
+        /// </summary>
+        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);
+        }
+    }
+}

+ 169 - 0
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
+{
+    /// <summary>
+    /// Helpful extension methods for data protection APIs.
+    /// </summary>
+    public static class DataProtectionAdvancedExtensions
+    {
+        /// <summary>
+        /// Cryptographically protects a piece of plaintext data, expiring the data after
+        /// the specified amount of time has elapsed.
+        /// </summary>
+        /// <param name="protector">The protector to use.</param>
+        /// <param name="plaintext">The plaintext data to protect.</param>
+        /// <param name="lifetime">The amount of time after which the payload should no longer be unprotectable.</param>
+        /// <returns>The protected form of the plaintext data.</returns>
+        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);
+        }
+
+        /// <summary>
+        /// Cryptographically protects a piece of plaintext data, expiring the data at
+        /// the chosen time.
+        /// </summary>
+        /// <param name="protector">The protector to use.</param>
+        /// <param name="plaintext">The plaintext data to protect.</param>
+        /// <param name="expiration">The time when this payload should expire.</param>
+        /// <returns>The protected form of the plaintext data.</returns>
+        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);
+        }
+
+        /// <summary>
+        /// Cryptographically protects a piece of plaintext data, expiring the data after
+        /// the specified amount of time has elapsed.
+        /// </summary>
+        /// <param name="protector">The protector to use.</param>
+        /// <param name="plaintext">The plaintext data to protect.</param>
+        /// <param name="lifetime">The amount of time after which the payload should no longer be unprotectable.</param>
+        /// <returns>The protected form of the plaintext data.</returns>
+        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);
+        }
+
+        /// <summary>
+        /// Converts an <see cref="IDataProtector"/> into an <see cref="ITimeLimitedDataProtector"/>
+        /// so that payloads can be protected with a finite lifetime.
+        /// </summary>
+        /// <param name="protector">The <see cref="IDataProtector"/> to convert to a time-limited protector.</param>
+        /// <returns>An <see cref="ITimeLimitedDataProtector"/>.</returns>
+        public static ITimeLimitedDataProtector ToTimeLimitedDataProtector(this IDataProtector protector)
+        {
+            if (protector == null)
+            {
+                throw new ArgumentNullException(nameof(protector));
+            }
+
+            return (protector as ITimeLimitedDataProtector) ?? new TimeLimitedDataProtector(protector);
+        }
+
+        /// <summary>
+        /// Cryptographically unprotects a piece of protected data.
+        /// </summary>
+        /// <param name="protector">The protector to use.</param>
+        /// <param name="protectedData">The protected data to unprotect.</param>
+        /// <param name="expiration">An 'out' parameter which upon a successful unprotect
+        /// operation receives the expiration date of the payload.</param>
+        /// <returns>The plaintext form of the protected data.</returns>
+        /// <exception cref="System.Security.Cryptography.CryptographicException">
+        /// Thrown if <paramref name="protectedData"/> is invalid, malformed, or expired.
+        /// </exception>
+        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);
+            }
+        }
+    }
+}

+ 178 - 0
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
+{
+    /// <summary>
+    /// Contains factory methods for creating an <see cref="IDataProtectionProvider"/> where keys are stored
+    /// at a particular location on the file system.
+    /// </summary>
+    /// <remarks>Use these methods when not using dependency injection to provide the service to the application.</remarks>
+    public static class DataProtectionProvider
+    {
+        /// <summary>
+        /// Creates a <see cref="DataProtectionProvider"/> that store keys in a location based on
+        /// the platform and operating system.
+        /// </summary>
+        /// <param name="applicationName">An identifier that uniquely discriminates this application from all other
+        /// applications on the machine.</param>
+        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);
+        }
+
+        /// <summary>
+        /// Creates an <see cref="DataProtectionProvider"/> given a location at which to store keys.
+        /// </summary>
+        /// <param name="keyDirectory">The <see cref="DirectoryInfo"/> in which keys should be stored. This may
+        /// represent a directory on a local disk or a UNC share.</param>
+        public static IDataProtectionProvider Create(DirectoryInfo keyDirectory)
+        {
+            if (keyDirectory == null)
+            {
+                throw new ArgumentNullException(nameof(keyDirectory));
+            }
+
+            return CreateProvider(keyDirectory, setupAction: builder => { }, certificate: null);
+        }
+
+        /// <summary>
+        /// Creates an <see cref="DataProtectionProvider"/> given a location at which to store keys and an
+        /// optional configuration callback.
+        /// </summary>
+        /// <param name="keyDirectory">The <see cref="DirectoryInfo"/> in which keys should be stored. This may
+        /// represent a directory on a local disk or a UNC share.</param>
+        /// <param name="setupAction">An optional callback which provides further configuration of the data protection
+        /// system. See <see cref="IDataProtectionBuilder"/> for more information.</param>
+        public static IDataProtectionProvider Create(
+            DirectoryInfo keyDirectory,
+            Action<IDataProtectionBuilder> setupAction)
+        {
+            if (keyDirectory == null)
+            {
+                throw new ArgumentNullException(nameof(keyDirectory));
+            }
+            if (setupAction == null)
+            {
+                throw new ArgumentNullException(nameof(setupAction));
+            }
+
+            return CreateProvider(keyDirectory, setupAction, certificate: null);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="DataProtectionProvider"/> that store keys in a location based on
+        /// the platform and operating system and uses the given <see cref="X509Certificate2"/> to encrypt the keys.
+        /// </summary>
+        /// <param name="applicationName">An identifier that uniquely discriminates this application from all other
+        /// applications on the machine.</param>
+        /// <param name="certificate">The <see cref="X509Certificate2"/> to be used for encryption.</param>
+        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);
+        }
+
+        /// <summary>
+        /// Creates an <see cref="DataProtectionProvider"/> given a location at which to store keys
+        /// and a <see cref="X509Certificate2"/> used to encrypt the keys.
+        /// </summary>
+        /// <param name="keyDirectory">The <see cref="DirectoryInfo"/> in which keys should be stored. This may
+        /// represent a directory on a local disk or a UNC share.</param>
+        /// <param name="certificate">The <see cref="X509Certificate2"/> to be used for encryption.</param>
+        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);
+        }
+
+        /// <summary>
+        /// Creates an <see cref="DataProtectionProvider"/> given a location at which to store keys, an
+        /// optional configuration callback and a <see cref="X509Certificate2"/> used to encrypt the keys.
+        /// </summary>
+        /// <param name="keyDirectory">The <see cref="DirectoryInfo"/> in which keys should be stored. This may
+        /// represent a directory on a local disk or a UNC share.</param>
+        /// <param name="setupAction">An optional callback which provides further configuration of the data protection
+        /// system. See <see cref="IDataProtectionBuilder"/> for more information.</param>
+        /// <param name="certificate">The <see cref="X509Certificate2"/> to be used for encryption.</param>
+        public static IDataProtectionProvider Create(
+            DirectoryInfo keyDirectory,
+            Action<IDataProtectionBuilder> 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<IDataProtectionBuilder> 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<IDataProtectionProvider>();
+        }
+    }
+}

+ 55 - 0
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
+{
+    /// <summary>
+    /// An interface that can provide data protection services where payloads have
+    /// a finite lifetime.
+    /// </summary>
+    /// <remarks>
+    /// 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).
+    /// </remarks>
+    public interface ITimeLimitedDataProtector : IDataProtector
+    {
+        /// <summary>
+        /// Creates an <see cref="ITimeLimitedDataProtector"/> given a purpose.
+        /// </summary>
+        /// <param name="purpose">
+        /// The purpose to be assigned to the newly-created <see cref="ITimeLimitedDataProtector"/>.
+        /// </param>
+        /// <returns>An <see cref="ITimeLimitedDataProtector"/> tied to the provided purpose.</returns>
+        /// <remarks>
+        /// The <paramref name="purpose"/> parameter must be unique for the intended use case; two
+        /// different <see cref="ITimeLimitedDataProtector"/> instances created with two different <paramref name="purpose"/>
+        /// values will not be able to decipher each other's payloads. The <paramref name="purpose"/> parameter
+        /// value is not intended to be kept secret.
+        /// </remarks>
+        new ITimeLimitedDataProtector CreateProtector(string purpose);
+
+        /// <summary>
+        /// Cryptographically protects a piece of plaintext data, expiring the data at
+        /// the chosen time.
+        /// </summary>
+        /// <param name="plaintext">The plaintext data to protect.</param>
+        /// <param name="expiration">The time when this payload should expire.</param>
+        /// <returns>The protected form of the plaintext data.</returns>
+        byte[] Protect(byte[] plaintext, DateTimeOffset expiration);
+
+        /// <summary>
+        /// Cryptographically unprotects a piece of protected data.
+        /// </summary>
+        /// <param name="protectedData">The protected data to unprotect.</param>
+        /// <param name="expiration">An 'out' parameter which upon a successful unprotect
+        /// operation receives the expiration date of the payload.</param>
+        /// <returns>The plaintext form of the protected data.</returns>
+        /// <exception cref="System.Security.Cryptography.CryptographicException">
+        /// Thrown if <paramref name="protectedData"/> is invalid, malformed, or expired.
+        /// </exception>
+        byte[] Unprotect(byte[] protectedData, out DateTimeOffset expiration);
+    }
+}

+ 22 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Microsoft.AspNetCore.DataProtection.Extensions.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>Additional APIs for ASP.NET Core data protection.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;dataprotection</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="..\..\shared\*.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 6 - 0
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")]

+ 72 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs

@@ -0,0 +1,72 @@
+// <auto-generated />
+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);
+
+        /// <summary>
+        /// An error occurred during a cryptographic operation.
+        /// </summary>
+        internal static string CryptCommon_GenericError
+        {
+            get => GetString("CryptCommon_GenericError");
+        }
+
+        /// <summary>
+        /// An error occurred during a cryptographic operation.
+        /// </summary>
+        internal static string FormatCryptCommon_GenericError()
+            => GetString("CryptCommon_GenericError");
+
+        /// <summary>
+        /// The payload expired at {0}.
+        /// </summary>
+        internal static string TimeLimitedDataProtector_PayloadExpired
+        {
+            get => GetString("TimeLimitedDataProtector_PayloadExpired");
+        }
+
+        /// <summary>
+        /// The payload expired at {0}.
+        /// </summary>
+        internal static string FormatTimeLimitedDataProtector_PayloadExpired(object p0)
+            => string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0);
+
+        /// <summary>
+        /// The payload is invalid.
+        /// </summary>
+        internal static string TimeLimitedDataProtector_PayloadInvalid
+        {
+            get => GetString("TimeLimitedDataProtector_PayloadInvalid");
+        }
+
+        /// <summary>
+        /// The payload is invalid.
+        /// </summary>
+        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;
+        }
+    }
+}

+ 129 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Resources.resx

@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="CryptCommon_GenericError" xml:space="preserve">
+    <value>An error occurred during a cryptographic operation.</value>
+  </data>
+  <data name="TimeLimitedDataProtector_PayloadExpired" xml:space="preserve">
+    <value>The payload expired at {0}.</value>
+  </data>
+  <data name="TimeLimitedDataProtector_PayloadInvalid" xml:space="preserve">
+    <value>The payload is invalid.</value>
+  </data>
+</root>

+ 149 - 0
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
+{
+    /// <summary>
+    /// Wraps an existing <see cref="IDataProtector"/> and appends a purpose that allows
+    /// protecting data with a finite lifetime.
+    /// </summary>
+    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);
+        }
+    }
+}

+ 298 - 0
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<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>"
+            }
+          ],
+          "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<Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder>"
+            },
+            {
+              "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": []
+    }
+  ]
+}

+ 24 - 0
src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Redis/Microsoft.AspNetCore.DataProtection.Redis.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>Redis storage support as key store.</Description>
+    <VersionPrefix Condition="'$(ExperimentalVersionPrefix)' != ''">$(ExperimentalVersionPrefix)</VersionPrefix>
+    <VersionSuffix Condition="'$(ExperimentalVersionSuffix)' != ''">$(ExperimentalVersionSuffix)</VersionSuffix>
+    <VerifyVersion Condition="'$(ExperimentalVersionPrefix)' != ''">false</VerifyVersion>
+    <PackageVersion>$(ExperimentalPackageVersion)</PackageVersion>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;dataprotection;redis</PackageTags>
+    <EnableApiCheck>false</EnableApiCheck>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="StackExchange.Redis.StrongName" Version="$(StackExchangeRedisStrongNamePackageVersion)" />
+  </ItemGroup>
+
+</Project>

+ 78 - 0
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
+{
+    /// <summary>
+    /// Contains Redis-specific extension methods for modifying a <see cref="IDataProtectionBuilder"/>.
+    /// </summary>
+    public static class RedisDataProtectionBuilderExtensions
+    {
+        private const string DataProtectionKeysName = "DataProtection-Keys";
+
+        /// <summary>
+        /// Configures the data protection system to persist keys to specified key in Redis database
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="databaseFactory">The delegate used to create <see cref="IDatabase"/> instances.</param>
+        /// <param name="key">The <see cref="RedisKey"/> used to store key list.</param>
+        /// <returns>A reference to the <see cref="IDataProtectionBuilder" /> after this operation has completed.</returns>
+        public static IDataProtectionBuilder PersistKeysToRedis(this IDataProtectionBuilder builder, Func<IDatabase> databaseFactory, RedisKey key)
+        {
+            if (builder == null)
+            {
+                throw new ArgumentNullException(nameof(builder));
+            }
+            if (databaseFactory == null)
+            {
+                throw new ArgumentNullException(nameof(databaseFactory));
+            }
+            return PersistKeysToRedisInternal(builder, databaseFactory, key);
+        }
+
+        /// <summary>
+        /// Configures the data protection system to persist keys to the default key ('DataProtection-Keys') in Redis database
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="connectionMultiplexer">The <see cref="IConnectionMultiplexer"/> for database access.</param>
+        /// <returns>A reference to the <see cref="IDataProtectionBuilder" /> after this operation has completed.</returns>
+        public static IDataProtectionBuilder PersistKeysToRedis(this IDataProtectionBuilder builder, IConnectionMultiplexer connectionMultiplexer)
+        {
+            return PersistKeysToRedis(builder, connectionMultiplexer, DataProtectionKeysName);
+        }
+
+        /// <summary>
+        /// Configures the data protection system to persist keys to the specified key in Redis database
+        /// </summary>
+        /// <param name="builder">The builder instance to modify.</param>
+        /// <param name="connectionMultiplexer">The <see cref="IConnectionMultiplexer"/> for database access.</param>
+        /// <param name="key">The <see cref="RedisKey"/> used to store key list.</param>
+        /// <returns>A reference to the <see cref="IDataProtectionBuilder" /> after this operation has completed.</returns>
+        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<IDatabase> databaseFactory, RedisKey key)
+        {
+            builder.Services.Configure<KeyManagementOptions>(options =>
+            {
+                options.XmlRepository = new RedisXmlRepository(databaseFactory, key);
+            });
+            return builder;
+        }
+    }
+}

+ 59 - 0
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
+{
+    /// <summary>
+    /// An XML repository backed by a Redis list entry.
+    /// </summary>
+    public class RedisXmlRepository: IXmlRepository
+    {
+        private readonly Func<IDatabase> _databaseFactory;
+        private readonly RedisKey _key;
+
+        /// <summary>
+        /// Creates a <see cref="RedisXmlRepository"/> with keys stored at the given directory.
+        /// </summary>
+        /// <param name="databaseFactory">The delegate used to create <see cref="IDatabase"/> instances.</param>
+        /// <param name="key">The <see cref="RedisKey"/> used to store key list.</param>
+        public RedisXmlRepository(Func<IDatabase> databaseFactory, RedisKey key)
+        {
+            _databaseFactory = databaseFactory;
+            _key = key;
+        }
+
+        /// <inheritdoc />
+        public IReadOnlyCollection<XElement> GetAllElements()
+        {
+            return GetAllElementsCore().ToList().AsReadOnly();
+        }
+
+        private IEnumerable<XElement> 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);
+            }
+        }
+
+        /// <inheritdoc />
+        public void StoreElement(XElement element, string friendlyName)
+        {
+            var database = _databaseFactory();
+            database.ListRightPush(_key, element.ToString(SaveOptions.DisableFormatting));
+        }
+    }
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно