PasswordHashTests.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. using System;
  2. using System.Collections.Generic;
  3. using MediaBrowser.Model.Cryptography;
  4. using Xunit;
  5. namespace Jellyfin.Model.Tests.Cryptography
  6. {
  7. public static class PasswordHashTests
  8. {
  9. [Fact]
  10. public static void Ctor_Null_ThrowsArgumentNullException()
  11. {
  12. Assert.Throws<ArgumentNullException>(() => new PasswordHash(null!, Array.Empty<byte>()));
  13. }
  14. [Fact]
  15. public static void Ctor_Empty_ThrowsArgumentException()
  16. {
  17. Assert.Throws<ArgumentException>(() => new PasswordHash(string.Empty, Array.Empty<byte>()));
  18. }
  19. public static TheoryData<string, PasswordHash> Parse_Valid_TestData()
  20. {
  21. var data = new TheoryData<string, PasswordHash>();
  22. // Id
  23. data.Add(
  24. "$PBKDF2",
  25. new PasswordHash("PBKDF2", Array.Empty<byte>()));
  26. // Id + parameter
  27. data.Add(
  28. "$PBKDF2$iterations=1000",
  29. new PasswordHash(
  30. "PBKDF2",
  31. Array.Empty<byte>(),
  32. Array.Empty<byte>(),
  33. new Dictionary<string, string>()
  34. {
  35. { "iterations", "1000" },
  36. }));
  37. // Id + parameters
  38. data.Add(
  39. "$PBKDF2$iterations=1000,m=120",
  40. new PasswordHash(
  41. "PBKDF2",
  42. Array.Empty<byte>(),
  43. Array.Empty<byte>(),
  44. new Dictionary<string, string>()
  45. {
  46. { "iterations", "1000" },
  47. { "m", "120" }
  48. }));
  49. // Id + hash
  50. data.Add(
  51. "$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  52. new PasswordHash(
  53. "PBKDF2",
  54. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  55. Array.Empty<byte>(),
  56. new Dictionary<string, string>()));
  57. // Id + salt + hash
  58. data.Add(
  59. "$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  60. new PasswordHash(
  61. "PBKDF2",
  62. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  63. Convert.FromHexString("69F420"),
  64. new Dictionary<string, string>()));
  65. // Id + parameter + hash
  66. data.Add(
  67. "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  68. new PasswordHash(
  69. "PBKDF2",
  70. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  71. Array.Empty<byte>(),
  72. new Dictionary<string, string>()
  73. {
  74. { "iterations", "1000" }
  75. }));
  76. // Id + parameters + hash
  77. data.Add(
  78. "$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  79. new PasswordHash(
  80. "PBKDF2",
  81. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  82. Array.Empty<byte>(),
  83. new Dictionary<string, string>()
  84. {
  85. { "iterations", "1000" },
  86. { "m", "120" }
  87. }));
  88. // Id + parameters + salt + hash
  89. data.Add(
  90. "$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
  91. new PasswordHash(
  92. "PBKDF2",
  93. Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
  94. Convert.FromHexString("69F420"),
  95. new Dictionary<string, string>()
  96. {
  97. { "iterations", "1000" },
  98. { "m", "120" }
  99. }));
  100. return data;
  101. }
  102. [Theory]
  103. [MemberData(nameof(Parse_Valid_TestData))]
  104. public static void Parse_Valid_Success(string passwordHashString, PasswordHash expected)
  105. {
  106. var passwordHash = PasswordHash.Parse(passwordHashString);
  107. Assert.Equal(expected.Id, passwordHash.Id);
  108. Assert.Equal(expected.Parameters, passwordHash.Parameters);
  109. Assert.Equal(expected.Salt.ToArray(), passwordHash.Salt.ToArray());
  110. Assert.Equal(expected.Hash.ToArray(), passwordHash.Hash.ToArray());
  111. Assert.Equal(expected.ToString(), passwordHash.ToString());
  112. }
  113. [Theory]
  114. [InlineData("$PBKDF2")]
  115. [InlineData("$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  116. [InlineData("$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  117. [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  118. [InlineData("$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  119. [InlineData("$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
  120. [InlineData("$PBKDF2$iterations=1000,m=120")]
  121. public static void ToString_Roundtrip_Success(string passwordHash)
  122. {
  123. Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString());
  124. }
  125. [Fact]
  126. public static void Parse_Null_ThrowsArgumentException()
  127. {
  128. Assert.Throws<ArgumentException>(() => PasswordHash.Parse(null));
  129. }
  130. [Fact]
  131. public static void Parse_Empty_ThrowsArgumentException()
  132. {
  133. Assert.Throws<ArgumentException>(() => PasswordHash.Parse(string.Empty));
  134. }
  135. [Theory]
  136. [InlineData("$")] // No id
  137. [InlineData("$$")] // Empty segments
  138. [InlineData("PBKDF2$")] // Doesn't start with $
  139. [InlineData("$PBKDF2$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty segment
  140. [InlineData("$PBKDF2$iterations=1000$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty salt segment
  141. [InlineData("$PBKDF2$iterations=1000$69F420$")] // Empty hash segment
  142. [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter
  143. [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter
  144. [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter
  145. [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
  146. [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
  147. [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
  148. [InlineData("$PBKDF2$iterations=1000$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt
  149. [InlineData("$PBKDF2$iterations=1000$69F420$invalid hash")] // Invalid hash
  150. [InlineData("$PBKDF2$69F420$")] // Empty hash
  151. public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash)
  152. {
  153. Assert.Throws<FormatException>(() => PasswordHash.Parse(passwordHash));
  154. }
  155. }
  156. }