diff options
| author | Bond-009 <bond.009@outlook.com> | 2019-09-17 18:07:15 +0200 |
|---|---|---|
| committer | Anthony Lavado <anthony@lavado.ca> | 2019-09-17 12:07:15 -0400 |
| commit | 6f17a0b7af5775386e554f2e2e2a4a6829d2895d (patch) | |
| tree | ce792d21af0f8e5d0208aec1aba55e8047f2f439 /MediaBrowser.Common | |
| parent | adc2a68a98a572e6541ffac587fd9f6247aec6d5 (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.cs | 18 | ||||
| -rw-r--r-- | MediaBrowser.Common/Cryptography/Extensions.cs | 35 | ||||
| -rw-r--r-- | MediaBrowser.Common/Cryptography/PasswordHash.cs | 155 | ||||
| -rw-r--r-- | MediaBrowser.Common/HexHelper.cs (renamed from MediaBrowser.Common/Extensions/HexHelper.cs) | 2 | ||||
| -rw-r--r-- | MediaBrowser.Common/MediaBrowser.Common.csproj | 6 |
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> |
