TimeLimitedDataProtectorTests.cs 8.4 KB

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