aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Common
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2019-09-17 18:07:15 +0200
committerAnthony Lavado <anthony@lavado.ca>2019-09-17 12:07:15 -0400
commit6f17a0b7af5775386e554f2e2e2a4a6829d2895d (patch)
treece792d21af0f8e5d0208aec1aba55e8047f2f439 /MediaBrowser.Common
parentadc2a68a98a572e6541ffac587fd9f6247aec6d5 (diff)
Remove legacy auth code (#1677)
* Remove legacy auth code * Adds tests so we don't break PasswordHash (again) * Clean up interfaces * Remove duplicate code * Use auto properties * static using * Don't use 'this' * Fix build
Diffstat (limited to 'MediaBrowser.Common')
-rw-r--r--MediaBrowser.Common/Cryptography/Constants.cs18
-rw-r--r--MediaBrowser.Common/Cryptography/Extensions.cs35
-rw-r--r--MediaBrowser.Common/Cryptography/PasswordHash.cs155
-rw-r--r--MediaBrowser.Common/HexHelper.cs (renamed from MediaBrowser.Common/Extensions/HexHelper.cs)2
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj6
5 files changed, 215 insertions, 1 deletions
diff --git a/MediaBrowser.Common/Cryptography/Constants.cs b/MediaBrowser.Common/Cryptography/Constants.cs
new file mode 100644
index 000000000..354114232
--- /dev/null
+++ b/MediaBrowser.Common/Cryptography/Constants.cs
@@ -0,0 +1,18 @@
+namespace MediaBrowser.Common.Cryptography
+{
+ /// <summary>
+ /// Class containing global constants for Jellyfin Cryptography.
+ /// </summary>
+ public static class Constants
+ {
+ /// <summary>
+ /// The default length for new salts.
+ /// </summary>
+ public const int DefaultSaltLength = 64;
+
+ /// <summary>
+ /// The default amount of iterations for hashing passwords.
+ /// </summary>
+ public const int DefaultIterations = 1000;
+ }
+}
diff --git a/MediaBrowser.Common/Cryptography/Extensions.cs b/MediaBrowser.Common/Cryptography/Extensions.cs
new file mode 100644
index 000000000..1e32a6d1a
--- /dev/null
+++ b/MediaBrowser.Common/Cryptography/Extensions.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using MediaBrowser.Model.Cryptography;
+using static MediaBrowser.Common.Cryptography.Constants;
+
+namespace MediaBrowser.Common.Cryptography
+{
+ /// <summary>
+ /// Class containing extension methods for working with Jellyfin cryptography objects.
+ /// </summary>
+ public static class Extensions
+ {
+ /// <summary>
+ /// Creates a new <see cref="PasswordHash" /> instance.
+ /// </summary>
+ /// <param name="cryptoProvider">The <see cref="ICryptoProvider" /> instance used.</param>
+ /// <param name="password">The password that will be hashed.</param>
+ /// <returns>A <see cref="PasswordHash" /> instance with the hash method, hash, salt and number of iterations.</returns>
+ public static PasswordHash CreatePasswordHash(this ICryptoProvider cryptoProvider, string password)
+ {
+ byte[] salt = cryptoProvider.GenerateSalt();
+ return new PasswordHash(
+ cryptoProvider.DefaultHashMethod,
+ cryptoProvider.ComputeHashWithDefaultMethod(
+ Encoding.UTF8.GetBytes(password),
+ salt),
+ salt,
+ new Dictionary<string, string>
+ {
+ { "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) }
+ });
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs
new file mode 100644
index 000000000..5b28d344f
--- /dev/null
+++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using static MediaBrowser.Common.HexHelper;
+
+namespace MediaBrowser.Common.Cryptography
+{
+ // Defined from this hash storage spec
+ // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
+ // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
+ // with one slight amendment to ease the transition, we're writing out the bytes in hex
+ // rather than making them a BASE64 string with stripped padding
+ public class PasswordHash
+ {
+ private readonly Dictionary<string, string> _parameters;
+
+ public PasswordHash(string id, byte[] hash)
+ : this(id, hash, Array.Empty<byte>())
+ {
+
+ }
+
+ public PasswordHash(string id, byte[] hash, byte[] salt)
+ : this(id, hash, salt, new Dictionary<string, string>())
+ {
+
+ }
+
+ public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters)
+ {
+ Id = id;
+ Hash = hash;
+ Salt = salt;
+ _parameters = parameters;
+ }
+
+ /// <summary>
+ /// Gets the symbolic name for the function used.
+ /// </summary>
+ /// <value>Returns the symbolic name for the function used.</value>
+ public string Id { get; }
+
+ /// <summary>
+ /// Gets the additional parameters used by the hash function.
+ /// </summary>
+ /// <value></value>
+ public IReadOnlyDictionary<string, string> Parameters => _parameters;
+
+ /// <summary>
+ /// Gets the salt used for hashing the password.
+ /// </summary>
+ /// <value>Returns the salt used for hashing the password.</value>
+ public byte[] Salt { get; }
+
+ /// <summary>
+ /// Gets the hashed password.
+ /// </summary>
+ /// <value>Return the hashed password.</value>
+ public byte[] Hash { get; }
+
+ public static PasswordHash Parse(string storageString)
+ {
+ string[] splitted = storageString.Split('$');
+ // The string should at least contain the hash function and the hash itself
+ if (splitted.Length < 3)
+ {
+ throw new ArgumentException("String doesn't contain enough segments", nameof(storageString));
+ }
+
+ // Start at 1, the first index shouldn't contain any data
+ int index = 1;
+
+ // Name of the hash function
+ string id = splitted[index++];
+
+ // Optional parameters
+ Dictionary<string, string> parameters = new Dictionary<string, string>();
+ if (splitted[index].IndexOf('=') != -1)
+ {
+ foreach (string paramset in splitted[index++].Split(','))
+ {
+ if (string.IsNullOrEmpty(paramset))
+ {
+ continue;
+ }
+
+ string[] fields = paramset.Split('=');
+ if (fields.Length != 2)
+ {
+ throw new InvalidDataException($"Malformed parameter in password hash string {paramset}");
+ }
+
+ parameters.Add(fields[0], fields[1]);
+ }
+ }
+
+ byte[] hash;
+ byte[] salt;
+ // Check if the string also contains a salt
+ if (splitted.Length - index == 2)
+ {
+ salt = FromHexString(splitted[index++]);
+ hash = FromHexString(splitted[index++]);
+ }
+ else
+ {
+ salt = Array.Empty<byte>();
+ hash = FromHexString(splitted[index++]);
+ }
+
+ return new PasswordHash(id, hash, salt, parameters);
+ }
+
+ private void SerializeParameters(StringBuilder stringBuilder)
+ {
+ if (_parameters.Count == 0)
+ {
+ return;
+ }
+
+ stringBuilder.Append('$');
+ foreach (var pair in _parameters)
+ {
+ stringBuilder.Append(pair.Key);
+ stringBuilder.Append('=');
+ stringBuilder.Append(pair.Value);
+ stringBuilder.Append(',');
+ }
+
+ // Remove last ','
+ stringBuilder.Length -= 1;
+ }
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ var str = new StringBuilder();
+ str.Append('$');
+ str.Append(Id);
+ SerializeParameters(str);
+
+ if (Salt.Length != 0)
+ {
+ str.Append('$');
+ str.Append(ToHexString(Salt));
+ }
+
+ str.Append('$');
+ str.Append(ToHexString(Hash));
+
+ return str.ToString();
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Extensions/HexHelper.cs b/MediaBrowser.Common/HexHelper.cs
index 3d80d94ac..5587c03fd 100644
--- a/MediaBrowser.Common/Extensions/HexHelper.cs
+++ b/MediaBrowser.Common/HexHelper.cs
@@ -1,7 +1,7 @@
using System;
using System.Globalization;
-namespace MediaBrowser.Common.Extensions
+namespace MediaBrowser.Common
{
public static class HexHelper
{
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 91ab066f9..1a40f5ea2 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -31,4 +31,10 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
+ <ItemGroup>
+ <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
+ <_Parameter1>Jellyfin.Common.Tests</_Parameter1>
+ </AssemblyAttribute>
+ </ItemGroup>
+
</Project>