XmlKeyManagerTests.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  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.Collections.Generic;
  5. using System.Linq;
  6. using System.Xml;
  7. using System.Xml.Linq;
  8. using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
  9. using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
  10. using Microsoft.AspNetCore.DataProtection.Internal;
  11. using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
  12. using Microsoft.AspNetCore.DataProtection.Repositories;
  13. using Microsoft.AspNetCore.DataProtection.XmlEncryption;
  14. using Microsoft.Extensions.DependencyInjection;
  15. using Microsoft.Extensions.Logging;
  16. using Moq;
  17. using Xunit;
  18. namespace Microsoft.AspNetCore.DataProtection.KeyManagement
  19. {
  20. public class XmlKeyManagerTests
  21. {
  22. private static readonly XElement serializedDescriptor = XElement.Parse(@"
  23. <theElement>
  24. <secret enc:requiresEncryption='true' xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
  25. <![CDATA[This is a secret value.]]>
  26. </secret>
  27. </theElement>");
  28. [Fact]
  29. public void Ctor_WithoutEncryptorOrRepository_UsesFallback()
  30. {
  31. // Arrange
  32. var expectedEncryptor = new Mock<IXmlEncryptor>().Object;
  33. var expectedRepository = new Mock<IXmlRepository>().Object;
  34. var mockFallback = new Mock<IDefaultKeyServices>();
  35. mockFallback.Setup(o => o.GetKeyEncryptor()).Returns(expectedEncryptor);
  36. mockFallback.Setup(o => o.GetKeyRepository()).Returns(expectedRepository);
  37. var serviceCollection = new ServiceCollection();
  38. serviceCollection.AddSingleton<IDefaultKeyServices>(mockFallback.Object);
  39. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(new Mock<IAuthenticatedEncryptorConfiguration>().Object);
  40. var services = serviceCollection.BuildServiceProvider();
  41. // Act
  42. var keyManager = new XmlKeyManager(services);
  43. // Assert
  44. Assert.Same(expectedEncryptor, keyManager.KeyEncryptor);
  45. Assert.Same(expectedRepository, keyManager.KeyRepository);
  46. }
  47. [Fact]
  48. public void Ctor_WithEncryptorButNoRepository_IgnoresFallback_FailsWithServiceNotFound()
  49. {
  50. // Arrange
  51. var mockFallback = new Mock<IDefaultKeyServices>();
  52. mockFallback.Setup(o => o.GetKeyEncryptor()).Returns(new Mock<IXmlEncryptor>().Object);
  53. mockFallback.Setup(o => o.GetKeyRepository()).Returns(new Mock<IXmlRepository>().Object);
  54. var serviceCollection = new ServiceCollection();
  55. serviceCollection.AddSingleton<IDefaultKeyServices>(mockFallback.Object);
  56. serviceCollection.AddSingleton<IXmlEncryptor>(new Mock<IXmlEncryptor>().Object);
  57. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(new Mock<IAuthenticatedEncryptorConfiguration>().Object);
  58. var services = serviceCollection.BuildServiceProvider();
  59. // Act & assert - we don't care about exception type, only exception message
  60. Exception ex = Assert.ThrowsAny<Exception>(() => new XmlKeyManager(services));
  61. Assert.Contains("IXmlRepository", ex.Message);
  62. }
  63. [Fact]
  64. public void CreateNewKey_Internal_NoEscrowOrEncryption()
  65. {
  66. // Constants
  67. var creationDate = new DateTimeOffset(2014, 01, 01, 0, 0, 0, TimeSpan.Zero);
  68. var activationDate = new DateTimeOffset(2014, 02, 01, 0, 0, 0, TimeSpan.Zero);
  69. var expirationDate = new DateTimeOffset(2014, 03, 01, 0, 0, 0, TimeSpan.Zero);
  70. var keyId = new Guid("3d6d01fd-c0e7-44ae-82dd-013b996b4093");
  71. // Arrange - mocks
  72. XElement elementStoredInRepository = null;
  73. string friendlyNameStoredInRepository = null;
  74. var expectedAuthenticatedEncryptor = new Mock<IAuthenticatedEncryptor>().Object;
  75. var mockDescriptor = new Mock<IAuthenticatedEncryptorDescriptor>();
  76. mockDescriptor.Setup(o => o.ExportToXml()).Returns(new XmlSerializedDescriptorInfo(serializedDescriptor, typeof(MyDeserializer)));
  77. mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(expectedAuthenticatedEncryptor);
  78. var mockConfiguration = new Mock<IAuthenticatedEncryptorConfiguration>();
  79. mockConfiguration.Setup(o => o.CreateNewDescriptor()).Returns(mockDescriptor.Object);
  80. var mockXmlRepository = new Mock<IXmlRepository>();
  81. mockXmlRepository
  82. .Setup(o => o.StoreElement(It.IsAny<XElement>(), It.IsAny<string>()))
  83. .Callback<XElement, string>((el, friendlyName) =>
  84. {
  85. elementStoredInRepository = el;
  86. friendlyNameStoredInRepository = friendlyName;
  87. });
  88. // Arrange - services
  89. var serviceCollection = new ServiceCollection();
  90. serviceCollection.AddSingleton<IXmlRepository>(mockXmlRepository.Object);
  91. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(mockConfiguration.Object);
  92. var services = serviceCollection.BuildServiceProvider();
  93. var keyManager = new XmlKeyManager(services);
  94. // Act & assert
  95. // The cancellation token should not already be fired
  96. var firstCancellationToken = keyManager.GetCacheExpirationToken();
  97. Assert.False(firstCancellationToken.IsCancellationRequested);
  98. // After the call to CreateNewKey, the first CT should be fired,
  99. // and we should've gotten a new CT.
  100. var newKey = ((IInternalXmlKeyManager)keyManager).CreateNewKey(
  101. keyId: keyId,
  102. creationDate: creationDate,
  103. activationDate: activationDate,
  104. expirationDate: expirationDate);
  105. var secondCancellationToken = keyManager.GetCacheExpirationToken();
  106. Assert.True(firstCancellationToken.IsCancellationRequested);
  107. Assert.False(secondCancellationToken.IsCancellationRequested);
  108. // Does the IKey have the properties we requested?
  109. Assert.Equal(keyId, newKey.KeyId);
  110. Assert.Equal(creationDate, newKey.CreationDate);
  111. Assert.Equal(activationDate, newKey.ActivationDate);
  112. Assert.Equal(expirationDate, newKey.ExpirationDate);
  113. Assert.False(newKey.IsRevoked);
  114. Assert.Same(expectedAuthenticatedEncryptor, newKey.CreateEncryptorInstance());
  115. // Finally, was the correct element stored in the repository?
  116. string expectedXml = String.Format(@"
  117. <key id='3d6d01fd-c0e7-44ae-82dd-013b996b4093' version='1' xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
  118. {1}
  119. {2}
  120. {3}
  121. <descriptor deserializerType='{0}'>
  122. <theElement>
  123. <secret enc:requiresEncryption='true'>
  124. <![CDATA[This is a secret value.]]>
  125. </secret>
  126. </theElement>
  127. </descriptor>
  128. </key>",
  129. typeof(MyDeserializer).AssemblyQualifiedName,
  130. new XElement("creationDate", creationDate),
  131. new XElement("activationDate", activationDate),
  132. new XElement("expirationDate", expirationDate));
  133. XmlAssert.Equal(expectedXml, elementStoredInRepository);
  134. Assert.Equal("key-3d6d01fd-c0e7-44ae-82dd-013b996b4093", friendlyNameStoredInRepository);
  135. }
  136. [Fact]
  137. public void CreateNewKey_Internal_WithEscrowAndEncryption()
  138. {
  139. // Constants
  140. var creationDate = new DateTimeOffset(2014, 01, 01, 0, 0, 0, TimeSpan.Zero);
  141. var activationDate = new DateTimeOffset(2014, 02, 01, 0, 0, 0, TimeSpan.Zero);
  142. var expirationDate = new DateTimeOffset(2014, 03, 01, 0, 0, 0, TimeSpan.Zero);
  143. var keyId = new Guid("3d6d01fd-c0e7-44ae-82dd-013b996b4093");
  144. // Arrange - mocks
  145. XElement elementStoredInEscrow = null;
  146. Guid? keyIdStoredInEscrow = null;
  147. XElement elementStoredInRepository = null;
  148. string friendlyNameStoredInRepository = null;
  149. var expectedAuthenticatedEncryptor = new Mock<IAuthenticatedEncryptor>().Object;
  150. var mockDescriptor = new Mock<IAuthenticatedEncryptorDescriptor>();
  151. mockDescriptor.Setup(o => o.ExportToXml()).Returns(new XmlSerializedDescriptorInfo(serializedDescriptor, typeof(MyDeserializer)));
  152. mockDescriptor.Setup(o => o.CreateEncryptorInstance()).Returns(expectedAuthenticatedEncryptor);
  153. var mockConfiguration = new Mock<IAuthenticatedEncryptorConfiguration>();
  154. mockConfiguration.Setup(o => o.CreateNewDescriptor()).Returns(mockDescriptor.Object);
  155. var mockXmlRepository = new Mock<IXmlRepository>();
  156. mockXmlRepository
  157. .Setup(o => o.StoreElement(It.IsAny<XElement>(), It.IsAny<string>()))
  158. .Callback<XElement, string>((el, friendlyName) =>
  159. {
  160. elementStoredInRepository = el;
  161. friendlyNameStoredInRepository = friendlyName;
  162. });
  163. var mockKeyEscrow = new Mock<IKeyEscrowSink>();
  164. mockKeyEscrow
  165. .Setup(o => o.Store(It.IsAny<Guid>(), It.IsAny<XElement>()))
  166. .Callback<Guid, XElement>((innerKeyId, el) =>
  167. {
  168. keyIdStoredInEscrow = innerKeyId;
  169. elementStoredInEscrow = el;
  170. });
  171. // Arrange - services
  172. var serviceCollection = new ServiceCollection();
  173. serviceCollection.AddSingleton<IXmlRepository>(mockXmlRepository.Object);
  174. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(mockConfiguration.Object);
  175. serviceCollection.AddSingleton<IKeyEscrowSink>(mockKeyEscrow.Object);
  176. serviceCollection.AddSingleton<IXmlEncryptor, NullXmlEncryptor>();
  177. var services = serviceCollection.BuildServiceProvider();
  178. var keyManager = new XmlKeyManager(services);
  179. // Act & assert
  180. // The cancellation token should not already be fired
  181. var firstCancellationToken = keyManager.GetCacheExpirationToken();
  182. Assert.False(firstCancellationToken.IsCancellationRequested);
  183. // After the call to CreateNewKey, the first CT should be fired,
  184. // and we should've gotten a new CT.
  185. var newKey = ((IInternalXmlKeyManager)keyManager).CreateNewKey(
  186. keyId: keyId,
  187. creationDate: creationDate,
  188. activationDate: activationDate,
  189. expirationDate: expirationDate);
  190. var secondCancellationToken = keyManager.GetCacheExpirationToken();
  191. Assert.True(firstCancellationToken.IsCancellationRequested);
  192. Assert.False(secondCancellationToken.IsCancellationRequested);
  193. // Does the IKey have the properties we requested?
  194. Assert.Equal(keyId, newKey.KeyId);
  195. Assert.Equal(creationDate, newKey.CreationDate);
  196. Assert.Equal(activationDate, newKey.ActivationDate);
  197. Assert.Equal(expirationDate, newKey.ExpirationDate);
  198. Assert.False(newKey.IsRevoked);
  199. Assert.Same(expectedAuthenticatedEncryptor, newKey.CreateEncryptorInstance());
  200. // Was the correct element stored in escrow?
  201. // This should not have gone through the encryptor.
  202. string expectedEscrowXml = String.Format(@"
  203. <key id='3d6d01fd-c0e7-44ae-82dd-013b996b4093' version='1' xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
  204. {1}
  205. {2}
  206. {3}
  207. <descriptor deserializerType='{0}'>
  208. <theElement>
  209. <secret enc:requiresEncryption='true'>
  210. <![CDATA[This is a secret value.]]>
  211. </secret>
  212. </theElement>
  213. </descriptor>
  214. </key>",
  215. typeof(MyDeserializer).AssemblyQualifiedName,
  216. new XElement("creationDate", creationDate),
  217. new XElement("activationDate", activationDate),
  218. new XElement("expirationDate", expirationDate));
  219. XmlAssert.Equal(expectedEscrowXml, elementStoredInEscrow);
  220. Assert.Equal(keyId, keyIdStoredInEscrow.Value);
  221. // Finally, was the correct element stored in the repository?
  222. // This should have gone through the encryptor (which we set to be the null encryptor in this test)
  223. string expectedRepositoryXml = String.Format(@"
  224. <key id='3d6d01fd-c0e7-44ae-82dd-013b996b4093' version='1' xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
  225. {2}
  226. {3}
  227. {4}
  228. <descriptor deserializerType='{0}'>
  229. <theElement>
  230. <enc:encryptedSecret decryptorType='{1}'>
  231. <unencryptedKey>
  232. <secret enc:requiresEncryption='true'>
  233. <![CDATA[This is a secret value.]]>
  234. </secret>
  235. </unencryptedKey>
  236. </enc:encryptedSecret>
  237. </theElement>
  238. </descriptor>
  239. </key>",
  240. typeof(MyDeserializer).AssemblyQualifiedName,
  241. typeof(NullXmlDecryptor).AssemblyQualifiedName,
  242. new XElement("creationDate", creationDate),
  243. new XElement("activationDate", activationDate),
  244. new XElement("expirationDate", expirationDate));
  245. XmlAssert.Equal(expectedRepositoryXml, elementStoredInRepository);
  246. Assert.Equal("key-3d6d01fd-c0e7-44ae-82dd-013b996b4093", friendlyNameStoredInRepository);
  247. }
  248. [Fact]
  249. public void CreateNewKey_CallsInternalManager()
  250. {
  251. // Arrange - mocks
  252. DateTimeOffset minCreationDate = DateTimeOffset.UtcNow;
  253. DateTimeOffset? actualCreationDate = null;
  254. DateTimeOffset activationDate = minCreationDate + TimeSpan.FromDays(7);
  255. DateTimeOffset expirationDate = activationDate.AddMonths(1);
  256. var mockInternalKeyManager = new Mock<IInternalXmlKeyManager>();
  257. mockInternalKeyManager
  258. .Setup(o => o.CreateNewKey(It.IsAny<Guid>(), It.IsAny<DateTimeOffset>(), activationDate, expirationDate))
  259. .Callback<Guid, DateTimeOffset, DateTimeOffset, DateTimeOffset>((innerKeyId, innerCreationDate, innerActivationDate, innerExpirationDate) =>
  260. {
  261. actualCreationDate = innerCreationDate;
  262. });
  263. // Arrange - services
  264. var serviceCollection = new ServiceCollection();
  265. serviceCollection.AddSingleton<IXmlRepository>(new Mock<IXmlRepository>().Object);
  266. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(new Mock<IAuthenticatedEncryptorConfiguration>().Object);
  267. serviceCollection.AddSingleton<IInternalXmlKeyManager>(mockInternalKeyManager.Object);
  268. var services = serviceCollection.BuildServiceProvider();
  269. var keyManager = new XmlKeyManager(services);
  270. // Act
  271. keyManager.CreateNewKey(activationDate, expirationDate);
  272. // Assert
  273. Assert.InRange(actualCreationDate.Value, minCreationDate, DateTimeOffset.UtcNow);
  274. }
  275. [Fact]
  276. public void GetAllKeys_Empty()
  277. {
  278. // Arrange
  279. const string xml = @"<root />";
  280. var activator = new Mock<IActivator>().Object;
  281. // Act
  282. var keys = RunGetAllKeysCore(xml, activator);
  283. // Assert
  284. Assert.Equal(0, keys.Count);
  285. }
  286. [Fact]
  287. public void GetAllKeys_IgnoresUnknownElements()
  288. {
  289. // Arrange
  290. const string xml = @"
  291. <root>
  292. <key id='62a72ad9-42d7-4e97-b3fa-05bad5d53d33' version='1'>
  293. <creationDate>2015-01-01T00:00:00Z</creationDate>
  294. <activationDate>2015-02-01T00:00:00Z</activationDate>
  295. <expirationDate>2015-03-01T00:00:00Z</expirationDate>
  296. <descriptor deserializerType='deserializer-A'>
  297. <elementA />
  298. </descriptor>
  299. </key>
  300. <unknown>
  301. <![CDATA[Unknown elements are ignored.]]>
  302. </unknown>
  303. <key id='041be4c0-52d7-48b4-8d32-f8c0ff315459' version='1'>
  304. <creationDate>2015-04-01T00:00:00Z</creationDate>
  305. <activationDate>2015-05-01T00:00:00Z</activationDate>
  306. <expirationDate>2015-06-01T00:00:00Z</expirationDate>
  307. <descriptor deserializerType='deserializer-B'>
  308. <elementB />
  309. </descriptor>
  310. </key>
  311. </root>";
  312. var encryptorA = new Mock<IAuthenticatedEncryptor>().Object;
  313. var encryptorB = new Mock<IAuthenticatedEncryptor>().Object;
  314. var mockActivator = new Mock<IActivator>();
  315. mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("deserializer-A", "<elementA />", encryptorA);
  316. mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("deserializer-B", "<elementB />", encryptorB);
  317. // Act
  318. var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray();
  319. // Assert
  320. Assert.Equal(2, keys.Length);
  321. Assert.Equal(new Guid("62a72ad9-42d7-4e97-b3fa-05bad5d53d33"), keys[0].KeyId);
  322. Assert.Equal(XmlConvert.ToDateTimeOffset("2015-01-01T00:00:00Z"), keys[0].CreationDate);
  323. Assert.Equal(XmlConvert.ToDateTimeOffset("2015-02-01T00:00:00Z"), keys[0].ActivationDate);
  324. Assert.Equal(XmlConvert.ToDateTimeOffset("2015-03-01T00:00:00Z"), keys[0].ExpirationDate);
  325. Assert.False(keys[0].IsRevoked);
  326. Assert.Same(encryptorA, keys[0].CreateEncryptorInstance());
  327. Assert.Equal(new Guid("041be4c0-52d7-48b4-8d32-f8c0ff315459"), keys[1].KeyId);
  328. Assert.Equal(XmlConvert.ToDateTimeOffset("2015-04-01T00:00:00Z"), keys[1].CreationDate);
  329. Assert.Equal(XmlConvert.ToDateTimeOffset("2015-05-01T00:00:00Z"), keys[1].ActivationDate);
  330. Assert.Equal(XmlConvert.ToDateTimeOffset("2015-06-01T00:00:00Z"), keys[1].ExpirationDate);
  331. Assert.False(keys[1].IsRevoked);
  332. Assert.Same(encryptorB, keys[1].CreateEncryptorInstance());
  333. }
  334. [Fact]
  335. public void GetAllKeys_UnderstandsRevocations()
  336. {
  337. // Arrange
  338. const string xml = @"
  339. <root>
  340. <key id='67f9cdea-83ba-41ed-b160-2b1d0ea30251' version='1'>
  341. <creationDate>2015-01-01T00:00:00Z</creationDate>
  342. <activationDate>2015-02-01T00:00:00Z</activationDate>
  343. <expirationDate>2015-03-01T00:00:00Z</expirationDate>
  344. <descriptor deserializerType='theDeserializer'>
  345. <node />
  346. </descriptor>
  347. </key>
  348. <key id='0cf83742-d175-42a8-94b5-1ec049b354c3' version='1'>
  349. <creationDate>2016-01-01T00:00:00Z</creationDate>
  350. <activationDate>2016-02-01T00:00:00Z</activationDate>
  351. <expirationDate>2016-03-01T00:00:00Z</expirationDate>
  352. <descriptor deserializerType='theDeserializer'>
  353. <node />
  354. </descriptor>
  355. </key>
  356. <key id='21580ac4-c83a-493c-bde6-29a1cc97ca0f' version='1'>
  357. <creationDate>2017-01-01T00:00:00Z</creationDate>
  358. <activationDate>2017-02-01T00:00:00Z</activationDate>
  359. <expirationDate>2017-03-01T00:00:00Z</expirationDate>
  360. <descriptor deserializerType='theDeserializer'>
  361. <node />
  362. </descriptor>
  363. </key>
  364. <key id='6bd14f12-0bb8-4822-91d7-04b360de0497' version='1'>
  365. <creationDate>2018-01-01T00:00:00Z</creationDate>
  366. <activationDate>2018-02-01T00:00:00Z</activationDate>
  367. <expirationDate>2018-03-01T00:00:00Z</expirationDate>
  368. <descriptor deserializerType='theDeserializer'>
  369. <node />
  370. </descriptor>
  371. </key>
  372. <revocation version='1'>
  373. <!-- The below will revoke no keys. -->
  374. <revocationDate>2014-01-01T00:00:00Z</revocationDate>
  375. <key id='*' />
  376. </revocation>
  377. <revocation version='1'>
  378. <!-- The below will revoke the first two keys. -->
  379. <revocationDate>2017-01-01T00:00:00Z</revocationDate>
  380. <key id='*' />
  381. </revocation>
  382. <revocation version='1'>
  383. <!-- The below will revoke only the last key. -->
  384. <revocationDate>2020-01-01T00:00:00Z</revocationDate>
  385. <key id='6bd14f12-0bb8-4822-91d7-04b360de0497' />
  386. </revocation>
  387. </root>";
  388. var mockActivator = new Mock<IActivator>();
  389. mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("theDeserializer", "<node />", new Mock<IAuthenticatedEncryptor>().Object);
  390. // Act
  391. var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray();
  392. // Assert
  393. Assert.Equal(4, keys.Length);
  394. Assert.Equal(new Guid("67f9cdea-83ba-41ed-b160-2b1d0ea30251"), keys[0].KeyId);
  395. Assert.True(keys[0].IsRevoked);
  396. Assert.Equal(new Guid("0cf83742-d175-42a8-94b5-1ec049b354c3"), keys[1].KeyId);
  397. Assert.True(keys[1].IsRevoked);
  398. Assert.Equal(new Guid("21580ac4-c83a-493c-bde6-29a1cc97ca0f"), keys[2].KeyId);
  399. Assert.False(keys[2].IsRevoked);
  400. Assert.Equal(new Guid("6bd14f12-0bb8-4822-91d7-04b360de0497"), keys[3].KeyId);
  401. Assert.True(keys[3].IsRevoked);
  402. }
  403. [Fact]
  404. public void GetAllKeys_PerformsDecryption()
  405. {
  406. // Arrange
  407. const string xml = @"
  408. <root xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
  409. <key id='09712588-ba68-438a-a5ee-fe842b3453b2' version='1'>
  410. <creationDate>2015-01-01T00:00:00Z</creationDate>
  411. <activationDate>2015-02-01T00:00:00Z</activationDate>
  412. <expirationDate>2015-03-01T00:00:00Z</expirationDate>
  413. <descriptor deserializerType='theDeserializer'>
  414. <enc:encryptedSecret decryptorType='theDecryptor'>
  415. <node xmlns='private' />
  416. </enc:encryptedSecret>
  417. </descriptor>
  418. </key>
  419. </root>";
  420. var expectedEncryptor = new Mock<IAuthenticatedEncryptor>().Object;
  421. var mockActivator = new Mock<IActivator>();
  422. mockActivator.ReturnDecryptedElementGivenDecryptorTypeNameAndInput("theDecryptor", "<node xmlns='private' />", "<decryptedNode />");
  423. mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("theDeserializer", "<decryptedNode />", expectedEncryptor);
  424. // Act
  425. var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray();
  426. // Assert
  427. Assert.Equal(1, keys.Length);
  428. Assert.Equal(new Guid("09712588-ba68-438a-a5ee-fe842b3453b2"), keys[0].KeyId);
  429. Assert.Same(expectedEncryptor, keys[0].CreateEncryptorInstance());
  430. }
  431. [Fact]
  432. public void GetAllKeys_SwallowsKeyDeserializationErrors()
  433. {
  434. // Arrange
  435. const string xml = @"
  436. <root>
  437. <!-- The below key will throw an exception when deserializing. -->
  438. <key id='78cd498e-9375-4e55-ac0d-d79527ecd09d' version='1'>
  439. <creationDate>2015-01-01T00:00:00Z</creationDate>
  440. <activationDate>2015-02-01T00:00:00Z</activationDate>
  441. <expirationDate>NOT A VALID DATE</expirationDate>
  442. <descriptor deserializerType='badDeserializer'>
  443. <node />
  444. </descriptor>
  445. </key>
  446. <!-- The below key will deserialize properly. -->
  447. <key id='49c0cda9-0232-4d8c-a541-de20cc5a73d6' version='1'>
  448. <creationDate>2015-01-01T00:00:00Z</creationDate>
  449. <activationDate>2015-02-01T00:00:00Z</activationDate>
  450. <expirationDate>2015-03-01T00:00:00Z</expirationDate>
  451. <descriptor deserializerType='goodDeserializer'>
  452. <node xmlns='private' />
  453. </descriptor>
  454. </key>
  455. </root>";
  456. var expectedEncryptor = new Mock<IAuthenticatedEncryptor>().Object;
  457. var mockActivator = new Mock<IActivator>();
  458. mockActivator.ReturnAuthenticatedEncryptorGivenDeserializerTypeNameAndInput("goodDeserializer", "<node xmlns='private' />", expectedEncryptor);
  459. // Act
  460. var keys = RunGetAllKeysCore(xml, mockActivator.Object).ToArray();
  461. // Assert
  462. Assert.Equal(1, keys.Length);
  463. Assert.Equal(new Guid("49c0cda9-0232-4d8c-a541-de20cc5a73d6"), keys[0].KeyId);
  464. Assert.Same(expectedEncryptor, keys[0].CreateEncryptorInstance());
  465. }
  466. [Fact]
  467. public void GetAllKeys_WithKeyDeserializationError_LogLevelDebug_DoesNotWriteSensitiveInformation()
  468. {
  469. // Arrange
  470. const string xml = @"
  471. <root>
  472. <!-- The below key will throw an exception when deserializing. -->
  473. <key id='78cd498e-9375-4e55-ac0d-d79527ecd09d' version='1'>
  474. <creationDate>2015-01-01T00:00:00Z</creationDate>
  475. <activationDate>2015-02-01T00:00:00Z</activationDate>
  476. <expirationDate>NOT A VALID DATE</expirationDate>
  477. <!-- Secret information: 1A2B3C4D -->
  478. </key>
  479. </root>";
  480. var loggerFactory = new StringLoggerFactory(LogLevel.Debug);
  481. // Act
  482. RunGetAllKeysCore(xml, new Mock<IActivator>().Object, loggerFactory).ToArray();
  483. // Assert
  484. Assert.False(loggerFactory.ToString().Contains("1A2B3C4D"), "The secret '1A2B3C4D' should not have been logged.");
  485. }
  486. [Fact]
  487. public void GetAllKeys_WithKeyDeserializationError_LogLevelTrace_WritesSensitiveInformation()
  488. {
  489. // Arrange
  490. const string xml = @"
  491. <root>
  492. <!-- The below key will throw an exception when deserializing. -->
  493. <key id='78cd498e-9375-4e55-ac0d-d79527ecd09d' version='1'>
  494. <creationDate>2015-01-01T00:00:00Z</creationDate>
  495. <activationDate>2015-02-01T00:00:00Z</activationDate>
  496. <expirationDate>NOT A VALID DATE</expirationDate>
  497. <!-- Secret information: 1A2B3C4D -->
  498. </key>
  499. </root>";
  500. var loggerFactory = new StringLoggerFactory(LogLevel.Trace);
  501. // Act
  502. RunGetAllKeysCore(xml, new Mock<IActivator>().Object, loggerFactory).ToArray();
  503. // Assert
  504. Assert.True(loggerFactory.ToString().Contains("1A2B3C4D"), "The secret '1A2B3C4D' should have been logged.");
  505. }
  506. [Fact]
  507. public void GetAllKeys_SurfacesRevocationDeserializationErrors()
  508. {
  509. // Arrange
  510. const string xml = @"
  511. <root>
  512. <revocation version='1'>
  513. <revocationDate>2015-01-01T00:00:00Z</revocationDate>
  514. <key id='{invalid}' />
  515. </revocation>
  516. </root>";
  517. // Act & assert
  518. // Bad GUID will lead to FormatException
  519. Assert.Throws<FormatException>(() => RunGetAllKeysCore(xml, new Mock<IActivator>().Object));
  520. }
  521. private static IReadOnlyCollection<IKey> RunGetAllKeysCore(string xml, IActivator activator, ILoggerFactory loggerFactory = null)
  522. {
  523. // Arrange - mocks
  524. var mockXmlRepository = new Mock<IXmlRepository>();
  525. mockXmlRepository.Setup(o => o.GetAllElements()).Returns(XElement.Parse(xml).Elements().ToArray());
  526. // Arrange - services
  527. var serviceCollection = new ServiceCollection();
  528. serviceCollection.AddSingleton<IXmlRepository>(mockXmlRepository.Object);
  529. serviceCollection.AddSingleton<IActivator>(activator);
  530. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(new Mock<IAuthenticatedEncryptorConfiguration>().Object);
  531. if (loggerFactory != null)
  532. {
  533. serviceCollection.AddSingleton<ILoggerFactory>(loggerFactory);
  534. }
  535. var services = serviceCollection.BuildServiceProvider();
  536. var keyManager = new XmlKeyManager(services);
  537. // Act
  538. return keyManager.GetAllKeys();
  539. }
  540. [Fact]
  541. public void RevokeAllKeys()
  542. {
  543. // Arrange - mocks
  544. XElement elementStoredInRepository = null;
  545. string friendlyNameStoredInRepository = null;
  546. var mockXmlRepository = new Mock<IXmlRepository>();
  547. mockXmlRepository
  548. .Setup(o => o.StoreElement(It.IsAny<XElement>(), It.IsAny<string>()))
  549. .Callback<XElement, string>((el, friendlyName) =>
  550. {
  551. elementStoredInRepository = el;
  552. friendlyNameStoredInRepository = friendlyName;
  553. });
  554. // Arrange - services
  555. var serviceCollection = new ServiceCollection();
  556. serviceCollection.AddSingleton<IXmlRepository>(mockXmlRepository.Object);
  557. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(new Mock<IAuthenticatedEncryptorConfiguration>().Object);
  558. var services = serviceCollection.BuildServiceProvider();
  559. var keyManager = new XmlKeyManager(services);
  560. var revocationDate = XmlConvert.ToDateTimeOffset("2015-03-01T19:13:19.7573854-08:00");
  561. // Act & assert
  562. // The cancellation token should not already be fired
  563. var firstCancellationToken = keyManager.GetCacheExpirationToken();
  564. Assert.False(firstCancellationToken.IsCancellationRequested);
  565. // After the call to RevokeAllKeys, the first CT should be fired,
  566. // and we should've gotten a new CT.
  567. keyManager.RevokeAllKeys(revocationDate, "Here's some reason text.");
  568. var secondCancellationToken = keyManager.GetCacheExpirationToken();
  569. Assert.True(firstCancellationToken.IsCancellationRequested);
  570. Assert.False(secondCancellationToken.IsCancellationRequested);
  571. // Was the correct element stored in the repository?
  572. const string expectedRepositoryXml = @"
  573. <revocation version='1'>
  574. <revocationDate>2015-03-01T19:13:19.7573854-08:00</revocationDate>
  575. <!--All keys created before the revocation date are revoked.-->
  576. <key id='*' />
  577. <reason>Here's some reason text.</reason>
  578. </revocation>";
  579. XmlAssert.Equal(expectedRepositoryXml, elementStoredInRepository);
  580. Assert.Equal("revocation-20150302T0313197573854Z", friendlyNameStoredInRepository);
  581. }
  582. [Fact]
  583. public void RevokeSingleKey_Internal()
  584. {
  585. // Arrange - mocks
  586. XElement elementStoredInRepository = null;
  587. string friendlyNameStoredInRepository = null;
  588. var mockXmlRepository = new Mock<IXmlRepository>();
  589. mockXmlRepository
  590. .Setup(o => o.StoreElement(It.IsAny<XElement>(), It.IsAny<string>()))
  591. .Callback<XElement, string>((el, friendlyName) =>
  592. {
  593. elementStoredInRepository = el;
  594. friendlyNameStoredInRepository = friendlyName;
  595. });
  596. // Arrange - services
  597. var serviceCollection = new ServiceCollection();
  598. serviceCollection.AddSingleton<IXmlRepository>(mockXmlRepository.Object);
  599. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(new Mock<IAuthenticatedEncryptorConfiguration>().Object);
  600. var services = serviceCollection.BuildServiceProvider();
  601. var keyManager = new XmlKeyManager(services);
  602. var revocationDate = new DateTimeOffset(2014, 01, 01, 0, 0, 0, TimeSpan.Zero);
  603. // Act & assert
  604. // The cancellation token should not already be fired
  605. var firstCancellationToken = keyManager.GetCacheExpirationToken();
  606. Assert.False(firstCancellationToken.IsCancellationRequested);
  607. // After the call to RevokeKey, the first CT should be fired,
  608. // and we should've gotten a new CT.
  609. ((IInternalXmlKeyManager)keyManager).RevokeSingleKey(
  610. keyId: new Guid("a11f35fc-1fed-4bd4-b727-056a63b70932"),
  611. revocationDate: revocationDate,
  612. reason: "Here's some reason text.");
  613. var secondCancellationToken = keyManager.GetCacheExpirationToken();
  614. Assert.True(firstCancellationToken.IsCancellationRequested);
  615. Assert.False(secondCancellationToken.IsCancellationRequested);
  616. // Was the correct element stored in the repository?
  617. var expectedRepositoryXml = string.Format(@"
  618. <revocation version='1'>
  619. {0}
  620. <key id='a11f35fc-1fed-4bd4-b727-056a63b70932' />
  621. <reason>Here's some reason text.</reason>
  622. </revocation>",
  623. new XElement("revocationDate", revocationDate));
  624. XmlAssert.Equal(expectedRepositoryXml, elementStoredInRepository);
  625. Assert.Equal("revocation-a11f35fc-1fed-4bd4-b727-056a63b70932", friendlyNameStoredInRepository);
  626. }
  627. [Fact]
  628. public void RevokeKey_CallsInternalManager()
  629. {
  630. // Arrange - mocks
  631. var keyToRevoke = new Guid("a11f35fc-1fed-4bd4-b727-056a63b70932");
  632. DateTimeOffset minRevocationDate = DateTimeOffset.UtcNow;
  633. DateTimeOffset? actualRevocationDate = null;
  634. var mockInternalKeyManager = new Mock<IInternalXmlKeyManager>();
  635. mockInternalKeyManager
  636. .Setup(o => o.RevokeSingleKey(keyToRevoke, It.IsAny<DateTimeOffset>(), "Here's some reason text."))
  637. .Callback<Guid, DateTimeOffset, string>((innerKeyId, innerRevocationDate, innerReason) =>
  638. {
  639. actualRevocationDate = innerRevocationDate;
  640. });
  641. // Arrange - services
  642. var serviceCollection = new ServiceCollection();
  643. serviceCollection.AddSingleton<IXmlRepository>(new Mock<IXmlRepository>().Object);
  644. serviceCollection.AddSingleton<IAuthenticatedEncryptorConfiguration>(new Mock<IAuthenticatedEncryptorConfiguration>().Object);
  645. serviceCollection.AddSingleton<IInternalXmlKeyManager>(mockInternalKeyManager.Object);
  646. var services = serviceCollection.BuildServiceProvider();
  647. var keyManager = new XmlKeyManager(services);
  648. // Act
  649. keyManager.RevokeKey(keyToRevoke, "Here's some reason text.");
  650. // Assert
  651. Assert.InRange(actualRevocationDate.Value, minRevocationDate, DateTimeOffset.UtcNow);
  652. }
  653. private class MyDeserializer : IAuthenticatedEncryptorDescriptorDeserializer
  654. {
  655. public IAuthenticatedEncryptorDescriptor ImportFromXml(XElement element)
  656. {
  657. throw new NotImplementedException();
  658. }
  659. }
  660. }
  661. }