TimeLimitedDataProtectorTests.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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.Globalization;
  5. using System.Security.Cryptography;
  6. using Microsoft.AspNetCore.DataProtection.Extensions;
  7. using Microsoft.Extensions.Logging.Abstractions;
  8. using Moq;
  9. using Xunit;
  10. namespace Microsoft.AspNetCore.DataProtection
  11. {
  12. public class TimeLimitedDataProtectorTests
  13. {
  14. private const string TimeLimitedPurposeString = "Microsoft.AspNetCore.DataProtection.TimeLimitedDataProtector.v1";
  15. [Fact]
  16. public void Protect_LifetimeSpecified()
  17. {
  18. // Arrange
  19. // 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
  20. DateTimeOffset expiration = StringToDateTime("2000-01-01 00:00:00Z");
  21. var mockInnerProtector = new Mock<IDataProtector>();
  22. mockInnerProtector.Setup(o => o.CreateProtector("new purpose").CreateProtector(TimeLimitedPurposeString).Protect(
  23. new byte[] {
  24. 0x08, 0xc1, 0x22, 0x02, 0x47, 0xe4, 0x40, 0x00, /* header */
  25. 0x01, 0x02, 0x03, 0x04, 0x05 /* payload */
  26. })).Returns(new byte[] { 0x10, 0x11 });
  27. var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
  28. // Act
  29. var subProtector = timeLimitedProtector.CreateProtector("new purpose");
  30. var protectedPayload = subProtector.Protect(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }, expiration);
  31. // Assert
  32. Assert.Equal(new byte[] { 0x10, 0x11 }, protectedPayload);
  33. }
  34. [Fact]
  35. public void Protect_LifetimeNotSpecified_UsesInfiniteLifetime()
  36. {
  37. // Arrange
  38. // 0x2bca2875f4373fff is the representation of DateTimeOffset.MaxValue.
  39. DateTimeOffset expiration = StringToDateTime("2000-01-01 00:00:00Z");
  40. var mockInnerProtector = new Mock<IDataProtector>();
  41. mockInnerProtector.Setup(o => o.CreateProtector("new purpose").CreateProtector(TimeLimitedPurposeString).Protect(
  42. new byte[] {
  43. 0x2b, 0xca, 0x28, 0x75, 0xf4, 0x37, 0x3f, 0xff, /* header */
  44. 0x01, 0x02, 0x03, 0x04, 0x05 /* payload */
  45. })).Returns(new byte[] { 0x10, 0x11 });
  46. var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
  47. // Act
  48. var subProtector = timeLimitedProtector.CreateProtector("new purpose");
  49. var protectedPayload = subProtector.Protect(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
  50. // Assert
  51. Assert.Equal(new byte[] { 0x10, 0x11 }, protectedPayload);
  52. }
  53. [Fact]
  54. public void Unprotect_WithinPayloadValidityPeriod_Success()
  55. {
  56. // Arrange
  57. // 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
  58. DateTimeOffset expectedExpiration = StringToDateTime("2000-01-01 00:00:00Z");
  59. DateTimeOffset now = StringToDateTime("1999-01-01 00:00:00Z");
  60. var mockInnerProtector = new Mock<IDataProtector>();
  61. mockInnerProtector.Setup(o => o.CreateProtector(TimeLimitedPurposeString).Unprotect(new byte[] { 0x10, 0x11 })).Returns(
  62. new byte[] {
  63. 0x08, 0xc1, 0x22, 0x02, 0x47, 0xe4, 0x40, 0x00, /* header */
  64. 0x01, 0x02, 0x03, 0x04, 0x05 /* payload */
  65. });
  66. var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
  67. // Act
  68. DateTimeOffset actualExpiration;
  69. var retVal = timeLimitedProtector.UnprotectCore(new byte[] { 0x10, 0x11 }, now, out actualExpiration);
  70. // Assert
  71. Assert.Equal(expectedExpiration, actualExpiration);
  72. Assert.Equal(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }, retVal);
  73. }
  74. [Fact]
  75. public void Unprotect_PayloadHasExpired_Fails()
  76. {
  77. // Arrange
  78. // 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
  79. DateTimeOffset expectedExpiration = StringToDateTime("2000-01-01 00:00:00Z");
  80. DateTimeOffset now = StringToDateTime("2001-01-01 00:00:00Z");
  81. var mockInnerProtector = new Mock<IDataProtector>();
  82. mockInnerProtector.Setup(o => o.CreateProtector(TimeLimitedPurposeString).Unprotect(new byte[] { 0x10, 0x11 })).Returns(
  83. new byte[] {
  84. 0x08, 0xc1, 0x22, 0x02, 0x47, 0xe4, 0x40, 0x00, /* header */
  85. 0x01, 0x02, 0x03, 0x04, 0x05 /* payload */
  86. });
  87. var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
  88. // Act & assert
  89. DateTimeOffset unused;
  90. var ex = Assert.Throws<CryptographicException>(() => timeLimitedProtector.UnprotectCore(new byte[] { 0x10, 0x11 }, now, out unused));
  91. // Assert
  92. Assert.Equal(Resources.FormatTimeLimitedDataProtector_PayloadExpired(expectedExpiration), ex.Message);
  93. }
  94. [Fact]
  95. public void Unprotect_ProtectedDataMalformed_Fails()
  96. {
  97. // Arrange
  98. // 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
  99. var mockInnerProtector = new Mock<IDataProtector>();
  100. mockInnerProtector.Setup(o => o.CreateProtector(TimeLimitedPurposeString).Unprotect(new byte[] { 0x10, 0x11 })).Returns(
  101. new byte[] {
  102. 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 /* header too short */
  103. });
  104. var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
  105. // Act & assert
  106. DateTimeOffset unused;
  107. var ex = Assert.Throws<CryptographicException>(() => timeLimitedProtector.Unprotect(new byte[] { 0x10, 0x11 }, out unused));
  108. // Assert
  109. Assert.Equal(Resources.TimeLimitedDataProtector_PayloadInvalid, ex.Message);
  110. }
  111. [Fact]
  112. public void Unprotect_UnprotectOperationFails_HomogenizesExceptionToCryptographicException()
  113. {
  114. // Arrange
  115. // 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
  116. var mockInnerProtector = new Mock<IDataProtector>();
  117. mockInnerProtector.Setup(o => o.CreateProtector(TimeLimitedPurposeString).Unprotect(new byte[] { 0x10, 0x11 })).Throws(new Exception("How exceptional!"));
  118. var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
  119. // Act & assert
  120. DateTimeOffset unused;
  121. var ex = Assert.Throws<CryptographicException>(() => timeLimitedProtector.Unprotect(new byte[] { 0x10, 0x11 }, out unused));
  122. // Assert
  123. Assert.Equal(Resources.CryptCommon_GenericError, ex.Message);
  124. Assert.Equal("How exceptional!", ex.InnerException.Message);
  125. }
  126. [Fact]
  127. public void RoundTrip_ProtectedData()
  128. {
  129. // Arrange
  130. var ephemeralProtector = new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("my purpose");
  131. var timeLimitedProtector = new TimeLimitedDataProtector(ephemeralProtector);
  132. var expectedExpiration = StringToDateTime("2020-01-01 00:00:00Z");
  133. // Act
  134. byte[] ephemeralProtectedPayload = ephemeralProtector.Protect(new byte[] { 0x01, 0x02, 0x03, 0x04 });
  135. byte[] timeLimitedProtectedPayload = timeLimitedProtector.Protect(new byte[] { 0x11, 0x22, 0x33, 0x44 }, expectedExpiration);
  136. // Assert
  137. DateTimeOffset actualExpiration;
  138. Assert.Equal(new byte[] { 0x11, 0x22, 0x33, 0x44 }, timeLimitedProtector.UnprotectCore(timeLimitedProtectedPayload, StringToDateTime("2010-01-01 00:00:00Z"), out actualExpiration));
  139. Assert.Equal(expectedExpiration, actualExpiration);
  140. // the two providers shouldn't be able to talk to one another (due to the purpose chaining)
  141. Assert.Throws<CryptographicException>(() => ephemeralProtector.Unprotect(timeLimitedProtectedPayload));
  142. Assert.Throws<CryptographicException>(() => timeLimitedProtector.Unprotect(ephemeralProtectedPayload, out actualExpiration));
  143. }
  144. private static DateTime StringToDateTime(string input)
  145. {
  146. return DateTimeOffset.ParseExact(input, "u", CultureInfo.InvariantCulture).UtcDateTime;
  147. }
  148. }
  149. }