Skip to content
代码片段 群组 项目
DataProtectionProviderTests.cs 14.8 KB
更新 更旧
  • 了解如何忽略特定修订
  • // 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;
    
    Nate McMaster's avatar
    Nate McMaster 已提交
    using System.Runtime.InteropServices;
    
    using System.Security.Cryptography;
    
    using System.Security.Cryptography.X509Certificates;
    
    using Microsoft.AspNetCore.DataProtection.Internal;
    using Microsoft.AspNetCore.DataProtection.KeyManagement;
    
    Nate McMaster's avatar
    Nate McMaster 已提交
    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
        {
    
    Nate McMaster's avatar
    Nate McMaster 已提交
            [Fact]
    
            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.Single(allFiles);
    
                    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);
                });
            }
    
    
    Nate McMaster's avatar
    Nate McMaster 已提交
            [Fact]
    
            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);
    
            [ConditionalFact]
    
            [ConditionalRunTestOnlyOnWindows]
    
            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.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("Windows DPAPI", fileText, StringComparison.Ordinal);
                });
            }
    
    
            [ConditionalFact] 
    
            [X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)]
    
            [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720
    
            public void System_UsesProvidedDirectoryAndCertificate()
            {
                var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");
    
                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");
    
                        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.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);
                    }
                    finally
                    {
                        certificateStore.Remove(certificate);
                        certificateStore.Close();
                    }
                });
            }
    
    
            [ConditionalFact]
            [X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)]
    
            [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720
    
            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");
    
    
                    // Step 1: directory should be completely empty
                    directory.Create();
    
                    // 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);
                });
            }
    
    
            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));
                }
            }
    
    
            [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)
            {
    
                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");