diff --git a/DataProtection.sln b/DataProtection.sln
index ead0e13a92..4c1adcfabb 100644
--- a/DataProtection.sln
+++ b/DataProtection.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26504.1
+VisualStudioVersion = 15.0.26814.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}"
EndProject
@@ -10,7 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5A3A
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E1D86B1B-41D8-43C9-97FD-C2BF65C414E2}"
ProjectSection(SolutionItems) = preProject
- build\common.props = build\common.props
build\dependencies.props = build\dependencies.props
NuGet.config = NuGet.config
EndProjectSection
@@ -55,6 +54,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyManagementSample", "samp
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
@@ -219,6 +224,30 @@ Global
{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
@@ -244,5 +273,11 @@ Global
{32CF970B-E2F1-4CD9-8DB3-F5715475373A} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
{6E066F8D-2910-404F-8949-F58125E28495} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
{F4D59BBD-6145-4EE0-BA6E-AD03605BF151} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {295E8539-5450-4764-B3F5-51F968628022} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {C85ED942-8121-453F-8308-9DB730843B63} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DD305D75-BD1B-43AE-BF04-869DA6A0858F}
EndGlobalSection
EndGlobal
diff --git a/samples/AzureKeyVault/AzureKeyVault.csproj b/samples/AzureKeyVault/AzureKeyVault.csproj
new file mode 100644
index 0000000000..4907ff7925
--- /dev/null
+++ b/samples/AzureKeyVault/AzureKeyVault.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp2.0
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/AzureKeyVault/Program.cs b/samples/AzureKeyVault/Program.cs
new file mode 100644
index 0000000000..7d6299f3e5
--- /dev/null
+++ b/samples/AzureKeyVault/Program.cs
@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace ConsoleApplication
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var builder = new ConfigurationBuilder();
+ builder.SetBasePath(Directory.GetCurrentDirectory());
+ builder.AddJsonFile("settings.json");
+ var config = builder.Build();
+
+ var store = new X509Store(StoreLocation.CurrentUser);
+ store.Open(OpenFlags.ReadOnly);
+ var cert = store.Certificates.Find(X509FindType.FindByThumbprint, config["CertificateThumbprint"], false);
+
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddLogging();
+ serviceCollection.AddDataProtection()
+ .PersistKeysToFileSystem(new DirectoryInfo("."))
+ .ProtectKeysWithAzureKeyVault(config["KeyId"], config["ClientId"], cert.OfType().Single());
+
+ var serviceProvider = serviceCollection.BuildServiceProvider();
+
+ var loggerFactory = serviceProvider.GetService();
+ loggerFactory.AddConsole();
+
+ var protector = serviceProvider.GetDataProtector("Test");
+
+ Console.WriteLine(protector.Protect("Hello world"));
+ }
+ }
+}
diff --git a/samples/AzureKeyVault/settings.json b/samples/AzureKeyVault/settings.json
new file mode 100644
index 0000000000..ef7d4d81b8
--- /dev/null
+++ b/samples/AzureKeyVault/settings.json
@@ -0,0 +1,5 @@
+{
+ "CertificateThumbprint": "",
+ "KeyId": "",
+ "ClientId": ""
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs
new file mode 100644
index 0000000000..0701220b4b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs
@@ -0,0 +1,118 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.DataProtection.AzureKeyVault;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains Azure KeyVault-specific extension methods for modifying a .
+ ///
+ public static class AzureDataProtectionBuilderExtensions
+ {
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The application client id.
+ ///
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, string keyIdentifier, string clientId, X509Certificate2 certificate)
+ {
+ if (string.IsNullOrEmpty(clientId))
+ {
+ throw new ArgumentException(nameof(clientId));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ KeyVaultClient.AuthenticationCallback callback =
+ (authority, resource, scope) => GetTokenFromClientCertificate(authority, resource, clientId, certificate);
+
+ return ProtectKeysWithAzureKeyVault(builder, new KeyVaultClient(callback), keyIdentifier);
+ }
+
+ private static async Task GetTokenFromClientCertificate(string authority, string resource, string clientId, X509Certificate2 certificate)
+ {
+ var authContext = new AuthenticationContext(authority);
+ var result = await authContext.AcquireTokenAsync(resource, new ClientAssertionCertificate(clientId, certificate));
+ return result.AccessToken;
+ }
+
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The application client id.
+ /// The client secret to use for authentication.
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, string keyIdentifier, string clientId, string clientSecret)
+ {
+ if (string.IsNullOrEmpty(clientId))
+ {
+ throw new ArgumentNullException(nameof(clientId));
+ }
+ if (string.IsNullOrEmpty(clientSecret))
+ {
+ throw new ArgumentNullException(nameof(clientSecret));
+ }
+
+ KeyVaultClient.AuthenticationCallback callback =
+ (authority, resource, scope) => GetTokenFromClientSecret(authority, resource, clientId, clientSecret);
+
+ return ProtectKeysWithAzureKeyVault(builder, new KeyVaultClient(callback), keyIdentifier);
+ }
+
+ private static async Task GetTokenFromClientSecret(string authority, string resource, string clientId, string clientSecret)
+ {
+ var authContext = new AuthenticationContext(authority);
+ var clientCred = new ClientCredential(clientId, clientSecret);
+ var result = await authContext.AcquireTokenAsync(resource, clientCred);
+ return result.AccessToken;
+ }
+
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The to use for KeyVault access.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, KeyVaultClient client, string keyIdentifier)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+ if (string.IsNullOrEmpty(keyIdentifier))
+ {
+ throw new ArgumentException(nameof(keyIdentifier));
+ }
+
+ var vaultClientWrapper = new KeyVaultClientWrapper(client);
+
+ builder.Services.AddSingleton(vaultClientWrapper);
+ builder.Services.Configure(options =>
+ {
+ options.XmlEncryptor = new AzureKeyVaultXmlEncryptor(vaultClientWrapper, keyIdentifier);
+ });
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs
new file mode 100644
index 0000000000..b9942fa84f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class AzureKeyVaultXmlDecryptor: IXmlDecryptor
+ {
+ private readonly IKeyVaultWrappingClient _client;
+
+ public AzureKeyVaultXmlDecryptor(IServiceProvider serviceProvider)
+ {
+ _client = serviceProvider.GetService();
+ }
+
+ public XElement Decrypt(XElement encryptedElement)
+ {
+ return DecryptAsync(encryptedElement).GetAwaiter().GetResult();
+ }
+
+ private async Task DecryptAsync(XElement encryptedElement)
+ {
+ var kid = (string)encryptedElement.Element("kid");
+ var symmetricKey = Convert.FromBase64String((string)encryptedElement.Element("key"));
+ var symmetricIV = Convert.FromBase64String((string)encryptedElement.Element("iv"));
+
+ var encryptedValue = Convert.FromBase64String((string)encryptedElement.Element("value"));
+
+ var result = await _client.UnwrapKeyAsync(kid, AzureKeyVaultXmlEncryptor.DefaultKeyEncryption, symmetricKey);
+
+ byte[] decryptedValue;
+ using (var symmetricAlgorithm = AzureKeyVaultXmlEncryptor.DefaultSymmetricAlgorithmFactory())
+ {
+ using (var decryptor = symmetricAlgorithm.CreateDecryptor(result.Result, symmetricIV))
+ {
+ decryptedValue = decryptor.TransformFinalBlock(encryptedValue, 0, encryptedValue.Length);
+ }
+ }
+
+ using (var memoryStream = new MemoryStream(decryptedValue))
+ {
+ return XElement.Load(memoryStream);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs
new file mode 100644
index 0000000000..3451c3ded2
--- /dev/null
+++ b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs
@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Azure.KeyVault.WebKey;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class AzureKeyVaultXmlEncryptor : IXmlEncryptor
+ {
+ internal static string DefaultKeyEncryption = JsonWebKeyEncryptionAlgorithm.RSAOAEP;
+ internal static Func DefaultSymmetricAlgorithmFactory = Aes.Create;
+
+ private readonly RandomNumberGenerator _randomNumberGenerator;
+ private readonly IKeyVaultWrappingClient _client;
+ private readonly string _keyId;
+
+ public AzureKeyVaultXmlEncryptor(IKeyVaultWrappingClient client, string keyId)
+ : this(client, keyId, RandomNumberGenerator.Create())
+ {
+ }
+
+ internal AzureKeyVaultXmlEncryptor(IKeyVaultWrappingClient client, string keyId, RandomNumberGenerator randomNumberGenerator)
+ {
+ _client = client;
+ _keyId = keyId;
+ _randomNumberGenerator = randomNumberGenerator;
+ }
+
+ public EncryptedXmlInfo Encrypt(XElement plaintextElement)
+ {
+ return EncryptAsync(plaintextElement).GetAwaiter().GetResult();
+ }
+
+ private async Task EncryptAsync(XElement plaintextElement)
+ {
+ byte[] value;
+ using (var memoryStream = new MemoryStream())
+ {
+ plaintextElement.Save(memoryStream, SaveOptions.DisableFormatting);
+ value = memoryStream.ToArray();
+ }
+
+ using (var symmetricAlgorithm = DefaultSymmetricAlgorithmFactory())
+ {
+ var symmetricBlockSize = symmetricAlgorithm.BlockSize / 8;
+ var symmetricKey = new byte[symmetricBlockSize];
+ var symmetricIV = new byte[symmetricBlockSize];
+ _randomNumberGenerator.GetBytes(symmetricKey);
+ _randomNumberGenerator.GetBytes(symmetricIV);
+
+ byte[] encryptedValue;
+ using (var encryptor = symmetricAlgorithm.CreateEncryptor(symmetricKey, symmetricIV))
+ {
+ encryptedValue = encryptor.TransformFinalBlock(value, 0, value.Length);
+ }
+
+ var wrappedKey = await _client.WrapKeyAsync(_keyId, DefaultKeyEncryption, symmetricKey);
+
+ var element = new XElement("encryptedKey",
+ new XComment(" This key is encrypted with Azure KeyVault. "),
+ new XElement("kid", wrappedKey.Kid),
+ new XElement("key", Convert.ToBase64String(wrappedKey.Result)),
+ new XElement("iv", Convert.ToBase64String(symmetricIV)),
+ new XElement("value", Convert.ToBase64String(encryptedValue)));
+
+ return new EncryptedXmlInfo(element, typeof(AzureKeyVaultXmlDecryptor));
+ }
+
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs
new file mode 100644
index 0000000000..2347460dc3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.Azure.KeyVault.Models;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal interface IKeyVaultWrappingClient
+ {
+ Task UnwrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText);
+ Task WrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs
new file mode 100644
index 0000000000..82fe0649e2
--- /dev/null
+++ b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Azure.KeyVault.Models;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class KeyVaultClientWrapper : IKeyVaultWrappingClient
+ {
+ private readonly KeyVaultClient _client;
+
+ public KeyVaultClientWrapper(KeyVaultClient client)
+ {
+ _client = client;
+ }
+
+ public Task UnwrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText)
+ {
+ return _client.UnwrapKeyAsync(keyIdentifier, algorithm, cipherText);
+ }
+
+ public Task WrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText)
+ {
+ return _client.WrapKeyAsync(keyIdentifier, algorithm, cipherText);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
new file mode 100644
index 0000000000..ee7b42ab87
--- /dev/null
+++ b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Microsoft Azure KeyVault key encryption support.
+ netstandard2.0
+ true
+ aspnetcore;dataprotection;azure;keyvault
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..c23a3410b7
--- /dev/null
+++ b/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs
@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
diff --git a/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/XmlEncryptionExtensions.cs b/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/XmlEncryptionExtensions.cs
index 74189cfad1..cfc65a44a2 100644
--- a/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/XmlEncryptionExtensions.cs
+++ b/src/Microsoft.AspNetCore.DataProtection/XmlEncryption/XmlEncryptionExtensions.cs
@@ -46,7 +46,6 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
// the original document or other data structures. The element we pass to
// the decryptor should be the child of the 'encryptedSecret' element.
var clonedElementWhichRequiresDecryption = new XElement(elementWhichRequiresDecryption);
- var innerDoc = new XDocument(clonedElementWhichRequiresDecryption);
string decryptorTypeName = (string)clonedElementWhichRequiresDecryption.Attribute(XmlConstants.DecryptorTypeAttributeName);
var decryptorInstance = activator.CreateInstance(decryptorTypeName);
var decryptedElement = decryptorInstance.Decrypt(clonedElementWhichRequiresDecryption.Elements().Single());
diff --git a/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test/AzureKeyVaultXmlEncryptorTests.cs b/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test/AzureKeyVaultXmlEncryptorTests.cs
new file mode 100644
index 0000000000..faa9bd1c96
--- /dev/null
+++ b/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test/AzureKeyVaultXmlEncryptorTests.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.Linq;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.Azure.KeyVault.Models;
+using Microsoft.Azure.KeyVault.WebKey;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test
+{
+ public class AzureKeyVaultXmlEncryptorTests
+ {
+ [Fact]
+ public void UsesKeyVaultToEncryptKey()
+ {
+ var mock = new Mock();
+ mock.Setup(client => client.WrapKeyAsync("key", JsonWebKeyEncryptionAlgorithm.RSAOAEP, It.IsAny()))
+ .Returns((_, __, data) => Task.FromResult(new KeyOperationResult("KeyId", data.Reverse().ToArray())));
+
+ var encryptor = new AzureKeyVaultXmlEncryptor(mock.Object, "key", new MockNumberGenerator());
+ var result = encryptor.Encrypt(new XElement("Element"));
+
+ var encryptedElement = result.EncryptedElement;
+ var value = encryptedElement.Element("value");
+
+ mock.VerifyAll();
+ Assert.NotNull(result);
+ Assert.NotNull(value);
+ Assert.Equal(typeof(AzureKeyVaultXmlDecryptor), result.DecryptorType);
+ Assert.Equal("VfLYL2prdymawfucH3Goso0zkPbQ4/GKqUsj2TRtLzsBPz7p7cL1SQaY6I29xSlsPQf6IjxHSz4sDJ427GvlLQ==", encryptedElement.Element("value").Value);
+ Assert.Equal("AAECAwQFBgcICQoLDA0ODw==", encryptedElement.Element("iv").Value);
+ Assert.Equal("Dw4NDAsKCQgHBgUEAwIBAA==", encryptedElement.Element("key").Value);
+ Assert.Equal("KeyId", encryptedElement.Element("kid").Value);
+ }
+
+ [Fact]
+ public void UsesKeyVaultToDecryptKey()
+ {
+ var mock = new Mock();
+ mock.Setup(client => client.UnwrapKeyAsync("KeyId", JsonWebKeyEncryptionAlgorithm.RSAOAEP, It.IsAny()))
+ .Returns((_, __, data) => Task.FromResult(new KeyOperationResult(null, data.Reverse().ToArray())))
+ .Verifiable();
+
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(mock.Object);
+
+ var encryptor = new AzureKeyVaultXmlDecryptor(serviceCollection.BuildServiceProvider());
+
+ var result = encryptor.Decrypt(XElement.Parse(
+ @"
+ KeyId
+ Dw4NDAsKCQgHBgUEAwIBAA==
+ AAECAwQFBgcICQoLDA0ODw==
+ VfLYL2prdymawfucH3Goso0zkPbQ4/GKqUsj2TRtLzsBPz7p7cL1SQaY6I29xSlsPQf6IjxHSz4sDJ427GvlLQ==
+ "));
+
+ mock.VerifyAll();
+ Assert.NotNull(result);
+ Assert.Equal("", result.ToString());
+ }
+
+ private class MockNumberGenerator : RandomNumberGenerator
+ {
+ public override void GetBytes(byte[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i] = (byte)i;
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test.csproj b/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test.csproj
new file mode 100644
index 0000000000..6983aebb33
--- /dev/null
+++ b/test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test/Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netcoreapp2.0;net461
+ netcoreapp2.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+