更新
更旧
// 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.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.DataProtection.Internal;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.AspNetCore.DataProtection.Test.Shared;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
namespace Microsoft.AspNetCore.DataProtection
{
public class DataProtectionProviderTests
{
public void System_UsesProvidedDirectory()
{
WithUniqueTempDirectory(directory =>
{
// 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).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 not protected
var allFiles = directory.GetFiles();
Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
string fileText = File.ReadAllText(allFiles[0].FullName);
Assert.Contains("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
Assert.DoesNotContain("Windows DPAPI", fileText, StringComparison.Ordinal);
});
}
public void System_NoKeysDirectoryProvided_UsesDefaultKeysDirectory()
{
var mock = new Mock<IDefaultKeyStorageDirectories>();
var keysPath = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName());
mock.Setup(m => m.GetKeyStorageDirectory()).Returns(new DirectoryInfo(keysPath));
// Step 1: Instantiate the system and round-trip a payload
var provider = DataProtectionProvider.CreateProvider(
keyDirectory: null,
certificate: null,
setupAction: builder =>
builder.SetApplicationName("TestApplication");
builder.Services.AddSingleton<IKeyManager>(s =>
new XmlKeyManager(
s.GetRequiredService<IOptions<KeyManagementOptions>>(),
s.GetRequiredService<IActivator>(),
NullLoggerFactory.Instance,
mock.Object));
});
var protector = provider.CreateProtector("Protector");
Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
// Step 2: Validate that there's now a single key in the directory
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);
// On Windows, validate that it's protected using Windows DPAPI.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
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);
Assert.Contains("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
}
}
public void System_UsesProvidedDirectory_WithConfigurationCallback()
{
WithUniqueTempDirectory(directory =>
{
// 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, configure =>
{
configure.ProtectKeysWithDpapi();
}).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 with DPAPI
var allFiles = directory.GetFiles();
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("Windows DPAPI", fileText, StringComparison.Ordinal);
});
}

Nate McMaster
已提交
[X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)]
[SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720
public void System_UsesProvidedDirectoryAndCertificate()
{
var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");

Nate McMaster
已提交
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
store.Add(new X509Certificate2(filePath, "password", X509KeyStorageFlags.Exportable));
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");

Nate McMaster
已提交
var data = protector.Protect("payload");
// add a cert without the private key to ensure the decryption will still fallback to the cert store
var certWithoutKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCertWithoutPrivateKey.pfx"), "password");
var unprotector = DataProtectionProvider.Create(directory, o => o.UnprotectKeysWithAnyCertificate(certWithoutKey)).CreateProtector("purpose");
Assert.Equal("payload", unprotector.Unprotect(data));
// 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.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();
}
});
}

Nate McMaster
已提交
[ConditionalFact]
[X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)]
[SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720

Nate McMaster
已提交
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
public void System_UsesProvidedCertificateNotFromStore()
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
var certWithoutKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCert3WithoutPrivateKey.pfx"), "password3", X509KeyStorageFlags.Exportable);
Assert.False(certWithoutKey.HasPrivateKey, "Cert should not have private key");
store.Add(certWithoutKey);
store.Close();
}
WithUniqueTempDirectory(directory =>
{
using (var certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
certificateStore.Open(OpenFlags.ReadWrite);
var certInStore = certificateStore.Certificates.Find(X509FindType.FindBySubjectName, "TestCert", false)[0];
Assert.NotNull(certInStore);
Assert.False(certInStore.HasPrivateKey);
try
{
var certWithKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCert3.pfx"), "password3");
var protector = DataProtectionProvider.Create(directory, certWithKey).CreateProtector("purpose");
var data = protector.Protect("payload");
var keylessUnprotector = DataProtectionProvider.Create(directory).CreateProtector("purpose");
Assert.Throws<CryptographicException>(() => keylessUnprotector.Unprotect(data));
var unprotector = DataProtectionProvider.Create(directory, o => o.UnprotectKeysWithAnyCertificate(certInStore, certWithKey)).CreateProtector("purpose");
Assert.Equal("payload", unprotector.Unprotect(data));
}
finally
{
certificateStore.Remove(certInStore);
certificateStore.Close();
}
}
});
}
[Fact]
public void System_UsesInMemoryCertificate()
{
var filePath = Path.Combine(GetTestFilesPath(), "TestCert2.pfx");
var certificate = new X509Certificate2(filePath, "password");

Nate McMaster
已提交
AssetStoreDoesNotContain(certificate);
WithUniqueTempDirectory(directory =>
{
// 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.Single(allFiles);
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);
});
}

Nate McMaster
已提交
private static void AssetStoreDoesNotContain(X509Certificate2 certificate)
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
try
{
store.Open(OpenFlags.ReadOnly);
}
catch
{
return;
}
// ensure this cert is not in the x509 store
Assert.Empty(store.Certificates.Find(X509FindType.FindByThumbprint, certificate.Thumbprint, false));
}
}
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
[Fact]
public void System_CanUnprotectWithCert()
{
var filePath = Path.Combine(GetTestFilesPath(), "TestCert2.pfx");
var certificate = new X509Certificate2(filePath, "password");
WithUniqueTempDirectory(directory =>
{
// Step 1: directory should be completely empty
directory.Create();
Assert.Empty(directory.GetFiles());
// Step 2: instantiate the system and create some data
var protector = DataProtectionProvider
.Create(directory, certificate)
.CreateProtector("purpose");
var data = 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.Single(allFiles);
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);
// Step 4: setup a second system and validate it can decrypt keys and unprotect data
var unprotector = DataProtectionProvider.Create(directory,
b => b.UnprotectKeysWithAnyCertificate(certificate));
Assert.Equal("payload", unprotector.CreateProtector("purpose").Unprotect(data));
});
}
/// <summary>
/// Runs a test and cleans up the temp directory afterward.
/// </summary>
private static void WithUniqueTempDirectory(Action<DirectoryInfo> testCode)
{

Nate McMaster
已提交
string uniqueTempPath = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName());
var dirInfo = new DirectoryInfo(uniqueTempPath);
try
{
testCode(dirInfo);
}
finally
{
// clean up when test is done
if (dirInfo.Exists)
{
dirInfo.Delete(recursive: true);
}
}
}
private static string GetTestFilesPath()
=> Path.Combine(AppContext.BaseDirectory, "TestFiles");