[Fixes #130] Added few DataProtectionProvider.Create overloads
This commit is contained in:
parent
8a592d1bee
commit
5654310a68
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
|
|
@ -14,6 +15,25 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
/// <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>
|
||||
|
|
@ -21,7 +41,12 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
/// represent a directory on a local disk or a UNC share.</param>
|
||||
public static IDataProtectionProvider Create(DirectoryInfo keyDirectory)
|
||||
{
|
||||
return Create(keyDirectory, setupAction: builder => { });
|
||||
if (keyDirectory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(keyDirectory));
|
||||
}
|
||||
|
||||
return CreateProvider(keyDirectory, setupAction: builder => { }, certificate: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -40,22 +65,116 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
{
|
||||
throw new ArgumentNullException(nameof(keyDirectory));
|
||||
}
|
||||
|
||||
if (setupAction == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(setupAction));
|
||||
}
|
||||
|
||||
return CreateProvider(keyDirectory, setupAction, certificate: null);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml
|
||||
/// <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);
|
||||
}
|
||||
#endif
|
||||
|
||||
private static IDataProtectionProvider CreateProvider(
|
||||
DirectoryInfo keyDirectory,
|
||||
Action<IDataProtectionBuilder> setupAction,
|
||||
X509Certificate2 certificate)
|
||||
{
|
||||
// build the service collection
|
||||
var serviceCollection = new ServiceCollection();
|
||||
var builder = serviceCollection.AddDataProtection();
|
||||
builder.PersistKeysToFileSystem(keyDirectory);
|
||||
|
||||
if (setupAction != null)
|
||||
if (keyDirectory != null)
|
||||
{
|
||||
setupAction(builder);
|
||||
builder.PersistKeysToFileSystem(keyDirectory);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml
|
||||
if (certificate != null)
|
||||
{
|
||||
builder.ProtectKeysWithCertificate(certificate);
|
||||
}
|
||||
#endif
|
||||
|
||||
setupAction(builder);
|
||||
|
||||
// extract the provider instance from the service collection
|
||||
return serviceCollection.BuildServiceProvider().GetRequiredService<IDataProtectionProvider>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.DataProtection.Test.Shared;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Xunit;
|
||||
|
|
@ -35,6 +37,47 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
});
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[ConditionalRunTestOnlyIfLocalAppDataAvailable]
|
||||
[ConditionalRunTestOnlyOnWindows]
|
||||
public void System_NoKeysDirectoryProvided_UsesDefaultKeysDirectory()
|
||||
{
|
||||
var keysPath = Path.Combine(Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%"), "ASP.NET", "DataProtection-Keys");
|
||||
var tempPath = Path.Combine(Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%"), "ASP.NET", "DataProtection-KeysTemp");
|
||||
|
||||
try
|
||||
{
|
||||
// Step 1: Move the current contents, if any, to a temporary directory.
|
||||
if (Directory.Exists(keysPath))
|
||||
{
|
||||
Directory.Move(keysPath, tempPath);
|
||||
}
|
||||
|
||||
// Step 2: Instantiate the system and round-trip a payload
|
||||
var protector = DataProtectionProvider.Create("TestApplication").CreateProtector("purpose");
|
||||
Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
|
||||
|
||||
// Step 3: Validate that there's now a single key in the directory and that it's protected using Windows DPAPI.
|
||||
var newFileName = Assert.Single(Directory.GetFiles(keysPath));
|
||||
var file = new FileInfo(newFileName);
|
||||
Assert.StartsWith("key-", file.Name, StringComparison.OrdinalIgnoreCase);
|
||||
var fileText = File.ReadAllText(file.FullName);
|
||||
Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
|
||||
Assert.Contains("This key is encrypted with Windows DPAPI.", fileText, StringComparison.Ordinal);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(keysPath))
|
||||
{
|
||||
Directory.Delete(keysPath, recursive: true);
|
||||
}
|
||||
if (Directory.Exists(tempPath))
|
||||
{
|
||||
Directory.Move(tempPath, keysPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[ConditionalRunTestOnlyIfLocalAppDataAvailable]
|
||||
[ConditionalRunTestOnlyOnWindows]
|
||||
|
|
@ -63,6 +106,51 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
});
|
||||
}
|
||||
|
||||
#if !NETSTANDARDAPP1_5 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml
|
||||
[ConditionalFact]
|
||||
[ConditionalRunTestOnlyIfLocalAppDataAvailable]
|
||||
[ConditionalRunTestOnlyOnWindows]
|
||||
public void System_UsesProvidedDirectoryAndCertificate()
|
||||
{
|
||||
var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");
|
||||
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
store.Add(new X509Certificate2(filePath, "password"));
|
||||
store.Close();
|
||||
|
||||
WithUniqueTempDirectory(directory =>
|
||||
{
|
||||
var certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
|
||||
certificateStore.Open(OpenFlags.ReadWrite);
|
||||
var certificate = certificateStore.Certificates.Find(X509FindType.FindBySubjectName, "TestCert", false)[0];
|
||||
|
||||
try
|
||||
{
|
||||
// Step 1: directory should be completely empty
|
||||
directory.Create();
|
||||
Assert.Empty(directory.GetFiles());
|
||||
|
||||
// Step 2: instantiate the system and round-trip a payload
|
||||
var protector = DataProtectionProvider.Create(directory, certificate).CreateProtector("purpose");
|
||||
Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
|
||||
|
||||
// Step 3: validate that there's now a single key in the directory and that it's is protected using the certificate
|
||||
var allFiles = directory.GetFiles();
|
||||
Assert.Equal(1, allFiles.Length);
|
||||
Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
|
||||
string fileText = File.ReadAllText(allFiles[0].FullName);
|
||||
Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
|
||||
Assert.Contains("X509Certificate", fileText, StringComparison.Ordinal);
|
||||
}
|
||||
finally
|
||||
{
|
||||
certificateStore.Remove(certificate);
|
||||
certificateStore.Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Runs a test and cleans up the temp directory afterward.
|
||||
/// </summary>
|
||||
|
|
@ -90,5 +178,26 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
public string SkipReason { get; } = "%LOCALAPPDATA% couldn't be located.";
|
||||
}
|
||||
|
||||
private static string GetTestFilesPath()
|
||||
{
|
||||
var projectName = typeof(DataProtectionProviderTests).GetTypeInfo().Assembly.GetName().Name;
|
||||
var projectPath = RecursiveFind(projectName, Path.GetFullPath("."));
|
||||
|
||||
return Path.Combine(projectPath, projectName, "TestFiles");
|
||||
}
|
||||
|
||||
private static string RecursiveFind(string path, string start)
|
||||
{
|
||||
var test = Path.Combine(start, path);
|
||||
if (Directory.Exists(test))
|
||||
{
|
||||
return start;
|
||||
}
|
||||
else
|
||||
{
|
||||
return RecursiveFind(path, new DirectoryInfo(start).Parent.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue