Add KeyVault encryption to DataProtection (#273)
This commit is contained in:
parent
9b45e7f118
commit
ee009982dc
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</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" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"CertificateThumbprint": "",
|
||||
"KeyId": "",
|
||||
"ClientId": ""
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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" />
|
||||
<PackageReference Include="Microsoft.Azure.KeyVault" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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")]
|
||||
|
|
@ -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<IXmlDecryptor>(decryptorTypeName);
|
||||
var decryptedElement = decryptorInstance.Decrypt(clonedElementWhichRequiresDecryption.Elements().Single());
|
||||
|
|
|
|||
|
|
@ -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<IKeyVaultWrappingClient>();
|
||||
mock.Setup(client => client.WrapKeyAsync("key", JsonWebKeyEncryptionAlgorithm.RSAOAEP, It.IsAny<byte[]>()))
|
||||
.Returns<string, string, byte[]>((_, __, 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<IKeyVaultWrappingClient>();
|
||||
mock.Setup(client => client.UnwrapKeyAsync("KeyId", JsonWebKeyEncryptionAlgorithm.RSAOAEP, It.IsAny<byte[]>()))
|
||||
.Returns<string, string, byte[]>((_, __, 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(
|
||||
@"<encryptedKey>
|
||||
<kid>KeyId</kid>
|
||||
<key>Dw4NDAsKCQgHBgUEAwIBAA==</key>
|
||||
<iv>AAECAwQFBgcICQoLDA0ODw==</iv>
|
||||
<value>VfLYL2prdymawfucH3Goso0zkPbQ4/GKqUsj2TRtLzsBPz7p7cL1SQaY6I29xSlsPQf6IjxHSz4sDJ427GvlLQ==</value>
|
||||
</encryptedKey>"));
|
||||
|
||||
mock.VerifyAll();
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("<Element />", result.ToString());
|
||||
}
|
||||
|
||||
private class MockNumberGenerator : RandomNumberGenerator
|
||||
{
|
||||
public override void GetBytes(byte[] data)
|
||||
{
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = (byte)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.DataProtection.AzureKeyVault\Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in New Issue