DataProtectionProviderTests.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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.Security.Cryptography.X509Certificates;
  7. using Microsoft.AspNetCore.DataProtection.Test.Shared;
  8. using Microsoft.AspNetCore.Testing.xunit;
  9. using Xunit;
  10. namespace Microsoft.AspNetCore.DataProtection
  11. {
  12. public class DataProtectionProviderTests
  13. {
  14. [ConditionalFact]
  15. [ConditionalRunTestOnlyIfLocalAppDataAvailable]
  16. public void System_UsesProvidedDirectory()
  17. {
  18. WithUniqueTempDirectory(directory =>
  19. {
  20. // Step 1: directory should be completely empty
  21. directory.Create();
  22. Assert.Empty(directory.GetFiles());
  23. // Step 2: instantiate the system and round-trip a payload
  24. var protector = DataProtectionProvider.Create(directory).CreateProtector("purpose");
  25. Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
  26. // Step 3: validate that there's now a single key in the directory and that it's not protected
  27. var allFiles = directory.GetFiles();
  28. Assert.Equal(1, allFiles.Length);
  29. Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
  30. string fileText = File.ReadAllText(allFiles[0].FullName);
  31. Assert.Contains("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  32. Assert.DoesNotContain("Windows DPAPI", fileText, StringComparison.Ordinal);
  33. });
  34. }
  35. [ConditionalFact]
  36. [ConditionalRunTestOnlyIfLocalAppDataAvailable]
  37. [ConditionalRunTestOnlyOnWindows]
  38. public void System_NoKeysDirectoryProvided_UsesDefaultKeysDirectory()
  39. {
  40. var keysPath = Path.Combine(Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%"), "ASP.NET", "DataProtection-Keys");
  41. var tempPath = Path.Combine(Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%"), "ASP.NET", "DataProtection-KeysTemp");
  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 and that it's protected using Windows DPAPI.
  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. Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  58. Assert.Contains("This key is encrypted with Windows DPAPI.", fileText, StringComparison.Ordinal);
  59. }
  60. finally
  61. {
  62. if (Directory.Exists(keysPath))
  63. {
  64. Directory.Delete(keysPath, recursive: true);
  65. }
  66. if (Directory.Exists(tempPath))
  67. {
  68. Directory.Move(tempPath, keysPath);
  69. }
  70. }
  71. }
  72. [ConditionalFact]
  73. [ConditionalRunTestOnlyIfLocalAppDataAvailable]
  74. [ConditionalRunTestOnlyOnWindows]
  75. public void System_UsesProvidedDirectory_WithConfigurationCallback()
  76. {
  77. WithUniqueTempDirectory(directory =>
  78. {
  79. // Step 1: directory should be completely empty
  80. directory.Create();
  81. Assert.Empty(directory.GetFiles());
  82. // Step 2: instantiate the system and round-trip a payload
  83. var protector = DataProtectionProvider.Create(directory, configure =>
  84. {
  85. configure.ProtectKeysWithDpapi();
  86. }).CreateProtector("purpose");
  87. Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
  88. // Step 3: validate that there's now a single key in the directory and that it's protected with DPAPI
  89. var allFiles = directory.GetFiles();
  90. Assert.Equal(1, allFiles.Length);
  91. Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
  92. string fileText = File.ReadAllText(allFiles[0].FullName);
  93. Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  94. Assert.Contains("Windows DPAPI", fileText, StringComparison.Ordinal);
  95. });
  96. }
  97. #if !NETCOREAPP1_0 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml
  98. [ConditionalFact]
  99. [ConditionalRunTestOnlyIfLocalAppDataAvailable]
  100. [ConditionalRunTestOnlyOnWindows]
  101. public void System_UsesProvidedDirectoryAndCertificate()
  102. {
  103. var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");
  104. var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
  105. store.Open(OpenFlags.ReadWrite);
  106. store.Add(new X509Certificate2(filePath, "password"));
  107. store.Close();
  108. WithUniqueTempDirectory(directory =>
  109. {
  110. var certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
  111. certificateStore.Open(OpenFlags.ReadWrite);
  112. var certificate = certificateStore.Certificates.Find(X509FindType.FindBySubjectName, "TestCert", false)[0];
  113. try
  114. {
  115. // Step 1: directory should be completely empty
  116. directory.Create();
  117. Assert.Empty(directory.GetFiles());
  118. // Step 2: instantiate the system and round-trip a payload
  119. var protector = DataProtectionProvider.Create(directory, certificate).CreateProtector("purpose");
  120. Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
  121. // Step 3: validate that there's now a single key in the directory and that it's is protected using the certificate
  122. var allFiles = directory.GetFiles();
  123. Assert.Equal(1, allFiles.Length);
  124. Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
  125. string fileText = File.ReadAllText(allFiles[0].FullName);
  126. Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
  127. Assert.Contains("X509Certificate", fileText, StringComparison.Ordinal);
  128. }
  129. finally
  130. {
  131. certificateStore.Remove(certificate);
  132. certificateStore.Close();
  133. }
  134. });
  135. }
  136. #endif
  137. /// <summary>
  138. /// Runs a test and cleans up the temp directory afterward.
  139. /// </summary>
  140. private static void WithUniqueTempDirectory(Action<DirectoryInfo> testCode)
  141. {
  142. string uniqueTempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
  143. var dirInfo = new DirectoryInfo(uniqueTempPath);
  144. try
  145. {
  146. testCode(dirInfo);
  147. }
  148. finally
  149. {
  150. // clean up when test is done
  151. if (dirInfo.Exists)
  152. {
  153. dirInfo.Delete(recursive: true);
  154. }
  155. }
  156. }
  157. private class ConditionalRunTestOnlyIfLocalAppDataAvailable : Attribute, ITestCondition
  158. {
  159. public bool IsMet => Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%") != null;
  160. public string SkipReason { get; } = "%LOCALAPPDATA% couldn't be located.";
  161. }
  162. private static string GetTestFilesPath()
  163. {
  164. var projectName = typeof(DataProtectionProviderTests).GetTypeInfo().Assembly.GetName().Name;
  165. var projectPath = RecursiveFind(projectName, Path.GetFullPath("."));
  166. return Path.Combine(projectPath, projectName, "TestFiles");
  167. }
  168. private static string RecursiveFind(string path, string start)
  169. {
  170. var test = Path.Combine(start, path);
  171. if (Directory.Exists(test))
  172. {
  173. return start;
  174. }
  175. else
  176. {
  177. return RecursiveFind(path, new DirectoryInfo(start).Parent.FullName);
  178. }
  179. }
  180. }
  181. }