< Summary - Jellyfin

Information
Class: MediaBrowser.Model.Cryptography.PasswordHash
Assembly: MediaBrowser.Model
File(s): /srv/git/jellyfin/MediaBrowser.Model/Cryptography/PasswordHash.cs
Line coverage
100%
Covered lines: 80
Uncovered lines: 0
Coverable lines: 80
Total lines: 212
Line coverage: 100%
Branch coverage
100%
Covered branches: 46
Total branches: 46
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
get_Parameters()100%11100%
get_Salt()100%11100%
get_Hash()100%11100%
Parse(...)100%3838100%
SerializeParameters(...)100%44100%
ToString()100%44100%

File(s)

/srv/git/jellyfin/MediaBrowser.Model/Cryptography/PasswordHash.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Text;
 6
 7namespace MediaBrowser.Model.Cryptography
 8{
 9    // Defined from this hash storage spec
 10    // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
 11    // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
 12    // with one slight amendment to ease the transition, we're writing out the bytes in hex
 13    // rather than making them a BASE64 string with stripped padding
 14    public class PasswordHash
 15    {
 16        private readonly Dictionary<string, string> _parameters;
 17        private readonly byte[] _salt;
 18        private readonly byte[] _hash;
 19
 20        public PasswordHash(string id, byte[] hash)
 621            : this(id, hash, Array.Empty<byte>())
 22        {
 423        }
 24
 25        public PasswordHash(string id, byte[] hash, byte[] salt)
 626            : this(id, hash, salt, new Dictionary<string, string>())
 27        {
 428        }
 29
 30        public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters)
 31        {
 3532            ArgumentException.ThrowIfNullOrEmpty(id);
 33
 34            Id = id;
 3335            _hash = hash;
 3336            _salt = salt;
 3337            _parameters = parameters;
 3338        }
 39
 40        /// <summary>
 41        /// Gets the symbolic name for the function used.
 42        /// </summary>
 43        /// <value>Returns the symbolic name for the function used.</value>
 44        public string Id { get; }
 45
 46        /// <summary>
 47        /// Gets the additional parameters used by the hash function.
 48        /// </summary>
 1649        public IReadOnlyDictionary<string, string> Parameters => _parameters;
 50
 51        /// <summary>
 52        /// Gets the salt used for hashing the password.
 53        /// </summary>
 54        /// <value>Returns the salt used for hashing the password.</value>
 1655        public ReadOnlySpan<byte> Salt => _salt;
 56
 57        /// <summary>
 58        /// Gets the hashed password.
 59        /// </summary>
 60        /// <value>Return the hashed password.</value>
 1661        public ReadOnlySpan<byte> Hash => _hash;
 62
 63        public static PasswordHash Parse(ReadOnlySpan<char> hashString)
 64        {
 3265            if (hashString.IsEmpty)
 66            {
 267                throw new ArgumentException("String can't be empty", nameof(hashString));
 68            }
 69
 3070            if (hashString[0] != '$')
 71            {
 172                throw new FormatException("Hash string must start with a $");
 73            }
 74
 75            // Ignore first $
 2976            hashString = hashString[1..];
 77
 2978            int nextSegment = hashString.IndexOf('$');
 2979            if (hashString.IsEmpty || nextSegment == 0)
 80            {
 281                throw new FormatException("Hash string must contain a valid id");
 82            }
 83
 2784            if (nextSegment == -1)
 85            {
 286                return new PasswordHash(hashString.ToString(), Array.Empty<byte>());
 87            }
 88
 2589            ReadOnlySpan<char> id = hashString[..nextSegment];
 2590            hashString = hashString[(nextSegment + 1)..];
 2591            Dictionary<string, string>? parameters = null;
 92
 2593            nextSegment = hashString.IndexOf('$');
 94
 95            // Optional parameters
 2596            ReadOnlySpan<char> parametersSpan = nextSegment == -1 ? hashString : hashString[..nextSegment];
 2597            if (parametersSpan.Contains('='))
 98            {
 4199                while (!parametersSpan.IsEmpty)
 100                {
 101                    ReadOnlySpan<char> parameter;
 25102                    int index = parametersSpan.IndexOf(',');
 25103                    if (index == -1)
 104                    {
 19105                        parameter = parametersSpan;
 19106                        parametersSpan = ReadOnlySpan<char>.Empty;
 107                    }
 108                    else
 109                    {
 6110                        parameter = parametersSpan[..index];
 6111                        parametersSpan = parametersSpan[(index + 1)..];
 112                    }
 113
 25114                    int splitIndex = parameter.IndexOf('=');
 25115                    if (splitIndex == -1 || splitIndex == 0 || splitIndex == parameter.Length - 1)
 116                    {
 3117                        throw new FormatException("Malformed parameter in password hash string");
 118                    }
 119
 22120                    (parameters ??= new Dictionary<string, string>()).Add(
 22121                        parameter[..splitIndex].ToString(),
 22122                        parameter[(splitIndex + 1)..].ToString());
 123                }
 124
 16125                if (nextSegment == -1)
 126                {
 127                    // parameters can't be null here
 3128                    return new PasswordHash(id.ToString(), Array.Empty<byte>(), Array.Empty<byte>(), parameters!);
 129                }
 130
 13131                hashString = hashString[(nextSegment + 1)..];
 13132                nextSegment = hashString.IndexOf('$');
 133            }
 134
 19135            if (nextSegment == 0)
 136            {
 2137                throw new FormatException("Hash string contains an empty segment");
 138            }
 139
 140            byte[] hash;
 141            byte[] salt;
 142
 17143            if (nextSegment == -1)
 144            {
 6145                salt = Array.Empty<byte>();
 6146                hash = Convert.FromHexString(hashString);
 147            }
 148            else
 149            {
 11150                salt = Convert.FromHexString(hashString[..nextSegment]);
 10151                hashString = hashString[(nextSegment + 1)..];
 10152                nextSegment = hashString.IndexOf('$');
 10153                if (nextSegment != -1)
 154                {
 2155                    throw new FormatException("Hash string contains too many segments");
 156                }
 157
 8158                if (hashString.IsEmpty)
 159                {
 3160                    throw new FormatException("Hash segment is empty");
 161                }
 162
 5163                hash = Convert.FromHexString(hashString);
 164            }
 165
 10166            return new PasswordHash(id.ToString(), hash, salt, parameters ?? new Dictionary<string, string>());
 167        }
 168
 169        private void SerializeParameters(StringBuilder stringBuilder)
 170        {
 33171            if (_parameters.Count == 0)
 172            {
 12173                return;
 174            }
 175
 21176            stringBuilder.Append('$');
 108177            foreach (var pair in _parameters)
 178            {
 33179                stringBuilder.Append(pair.Key)
 33180                    .Append('=')
 33181                    .Append(pair.Value)
 33182                    .Append(',');
 183            }
 184
 185            // Remove last ','
 21186            stringBuilder.Length -= 1;
 21187        }
 188
 189        /// <inheritdoc />
 190        public override string ToString()
 191        {
 33192            var str = new StringBuilder()
 33193                .Append('$')
 33194                .Append(Id);
 33195            SerializeParameters(str);
 196
 33197            if (_salt.Length != 0)
 198            {
 10199                str.Append('$')
 10200                    .Append(Convert.ToHexString(_salt));
 201            }
 202
 33203            if (_hash.Length != 0)
 204            {
 22205                str.Append('$')
 22206                    .Append(Convert.ToHexString(_hash));
 207            }
 208
 33209            return str.ToString();
 210        }
 211    }
 212}