DataProtectionProviderTests.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.IO;
  5. using System.Reflection;
  6. using System.Runtime.InteropServices;
  7. using System.Security.Cryptography.X509Certificates;
  8. using Microsoft.AspNetCore.DataProtection.Repositories;
  9. using Microsoft.AspNetCore.DataProtection.Test.Shared;
  10. using Microsoft.AspNetCore.Testing.xunit;
  11. using Xunit;
  12. namespace Microsoft.AspNetCore.DataProtection
  13. {
  14. public class DataProtectionProviderTests
  15. {
  16. [Fact]
  17. public void System_UsesProvidedDirectory()
  18. {
  19. WithUniqueTempDirectory(directory =>
  20. {
  21. // Step 1: directory should be completely empty
  22. directory.Create();
  23. Assert.Empty(directory.GetFiles());
  24. // Step 2: instantiate the system and round-trip a payload
  25. var protector = DataProtectionProvider.Create(directory).CreateProtector("purpose");
  26. Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
  27. // Step 3: validate that there's now a single key in the directory and that it's not protected
  28. var allFiles = directory.GetFiles();
  29. Assert.Equal(1, allFiles.Length);
  30. Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
  31. string fileText = File.ReadAllText(allFiles[0].FullName);
  32. Assert.Contains("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  33. Assert.DoesNotContain("Windows DPAPI", fileText, StringComparison.Ordinal);
  34. });
  35. }
  36. [Fact]
  37. public void System_NoKeysDirectoryProvided_UsesDefaultKeysDirectory()
  38. {
  39. Assert.NotNull(FileSystemXmlRepository.DefaultKeyStorageDirectory);
  40. var keysPath = FileSystemXmlRepository.DefaultKeyStorageDirectory.FullName;
  41. var tempPath = FileSystemXmlRepository.DefaultKeyStorageDirectory.FullName + "Temp";
  42. try
  43. {
  44. // Step 1: Move the current contents, if any, to a temporary directory.
  45. if (Directory.Exists(keysPath))
  46. {
  47. Directory.Move(keysPath, tempPath);
  48. }
  49. // Step 2: Instantiate the system and round-trip a payload
  50. var protector = DataProtectionProvider.Create("TestApplication").CreateProtector("purpose");
  51. Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
  52. // Step 3: Validate that there's now a single key in the directory
  53. var newFileName = Assert.Single(Directory.GetFiles(keysPath));
  54. var file = new FileInfo(newFileName);
  55. Assert.StartsWith("key-", file.Name, StringComparison.OrdinalIgnoreCase);
  56. var fileText = File.ReadAllText(file.FullName);
  57. // On Windows, validate that it's protected using Windows DPAPI.
  58. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  59. {
  60. Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  61. Assert.Contains("This key is encrypted with Windows DPAPI.", fileText, StringComparison.Ordinal);
  62. }
  63. else
  64. {
  65. Assert.Contains("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  66. }
  67. }
  68. finally
  69. {
  70. if (Directory.Exists(keysPath))
  71. {
  72. Directory.Delete(keysPath, recursive: true);
  73. }
  74. if (Directory.Exists(tempPath))
  75. {
  76. Directory.Move(tempPath, keysPath);
  77. }
  78. }
  79. }
  80. [ConditionalFact]
  81. [ConditionalRunTestOnlyOnWindows]
  82. public void System_UsesProvidedDirectory_WithConfigurationCallback()
  83. {
  84. WithUniqueTempDirectory(directory =>
  85. {
  86. // Step 1: directory should be completely empty
  87. directory.Create();
  88. Assert.Empty(directory.GetFiles());
  89. // Step 2: instantiate the system and round-trip a payload
  90. var protector = DataProtectionProvider.Create(directory, configure =>
  91. {
  92. configure.ProtectKeysWithDpapi();
  93. }).CreateProtector("purpose");
  94. Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
  95. // Step 3: validate that there's now a single key in the directory and that it's protected with DPAPI
  96. var allFiles = directory.GetFiles();
  97. Assert.Equal(1, allFiles.Length);
  98. Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
  99. string fileText = File.ReadAllText(allFiles[0].FullName);
  100. Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  101. Assert.Contains("Windows DPAPI", fileText, StringComparison.Ordinal);
  102. });
  103. }
  104. [Fact]
  105. public void System_UsesProvidedDirectoryAndCertificate()
  106. {
  107. var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");
  108. var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
  109. store.Open(OpenFlags.ReadWrite);
  110. store.Add(new X509Certificate2(filePath, "password", X509KeyStorageFlags.Exportable));
  111. store.Close();
  112. WithUniqueTempDirectory(directory =>
  113. {
  114. var certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
  115. certificateStore.Open(OpenFlags.ReadWrite);
  116. var certificate = certificateStore.Certificates.Find(X509FindType.FindBySubjectName, "TestCert", false)[0];
  117. try
  118. {
  119. // Step 1: directory should be completely empty
  120. directory.Create();
  121. Assert.Empty(directory.GetFiles());
  122. // Step 2: instantiate the system and round-trip a payload
  123. var protector = DataProtectionProvider.Create(directory, certificate).CreateProtector("purpose");
  124. Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
  125. // Step 3: validate that there's now a single key in the directory and that it's is protected using the certificate
  126. var allFiles = directory.GetFiles();
  127. Assert.Equal(1, allFiles.Length);
  128. Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
  129. string fileText = File.ReadAllText(allFiles[0].FullName);
  130. Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  131. Assert.Contains("X509Certificate", fileText, StringComparison.Ordinal);
  132. }
  133. finally
  134. {
  135. certificateStore.Remove(certificate);
  136. certificateStore.Close();
  137. }
  138. });
  139. }
  140. /// <summary>
  141. /// Runs a test and cleans up the temp directory afterward.
  142. /// </summary>
  143. private static void WithUniqueTempDirectory(Action<DirectoryInfo> testCode)
  144. {
  145. string uniqueTempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
  146. var dirInfo = new DirectoryInfo(uniqueTempPath);
  147. try
  148. {
  149. testCode(dirInfo);
  150. }
  151. finally
  152. {
  153. // clean up when test is done
  154. if (dirInfo.Exists)
  155. {
  156. dirInfo.Delete(recursive: true);
  157. }
  158. }
  159. }
  160. private static string GetTestFilesPath()
  161. {
  162. var projectName = typeof(DataProtectionProviderTests).GetTypeInfo().Assembly.GetName().Name;
  163. var projectPath = RecursiveFind(projectName, Path.GetFullPath("."));
  164. return Path.Combine(projectPath, projectName, "TestFiles");
  165. }
  166. private static string RecursiveFind(string path, string start)
  167. {
  168. var test = Path.Combine(start, path);
  169. if (Directory.Exists(test))
  170. {
  171. return start;
  172. }
  173. else
  174. {
  175. return RecursiveFind(path, new DirectoryInfo(start).Parent.FullName);
  176. }
  177. }
  178. }
  179. }