diff options
Diffstat (limited to 'Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs')
| -rw-r--r-- | Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs | 141 |
1 files changed, 120 insertions, 21 deletions
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 4013ac0c8..3ec1f81d3 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; @@ -18,20 +19,64 @@ namespace Emby.Server.Implementations.Library public string Name => "Default"; public bool IsEnabled => true; - + + // This is dumb and an artifact of the backwards way auth providers were designed. + // This version of authenticate was never meant to be called, but needs to be here for interface compat + // Only the providers that don't provide local user support use this public Task<ProviderAuthenticationResult> Authenticate(string username, string password) { throw new NotImplementedException(); } - + + // This is the verson that we need to use for local users. Because reasons. public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser) { + bool success = false; if (resolvedUser == null) { throw new Exception("Invalid username or password"); } - var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + // As long as jellyfin supports passwordless users, we need this little block here to accomodate + if (IsPasswordEmpty(resolvedUser, password)) + { + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + + PasswordHash readyHash = new PasswordHash(resolvedUser.Password); + byte[] calculatedHash; + string calculatedHashString; + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) + { + if (string.IsNullOrEmpty(readyHash.Salt)) + { + calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); + calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty); + } + else + { + calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); + calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty); + } + + if (calculatedHashString == readyHash.Hash) + { + success = true; + // throw new Exception("Invalid username or password"); + } + } + else + { + throw new Exception(string.Format($"Requested crypto method not available in provider: {readyHash.Id}")); + } + + // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); if (!success) { @@ -44,46 +89,86 @@ namespace Emby.Server.Implementations.Library }); } + // This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change + // but at least they are in the new format. + private void ConvertPasswordFormat(User user) + { + if (string.IsNullOrEmpty(user.Password)) + { + return; + } + + if (!user.Password.Contains("$")) + { + string hash = user.Password; + user.Password = string.Format("$SHA1${0}", hash); + } + + if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) + { + string hash = user.EasyPassword; + user.EasyPassword = string.Format("$SHA1${0}", hash); + } + } + public Task<bool> HasPassword(User user) { var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); return Task.FromResult(hasConfiguredPassword); } - private bool IsPasswordEmpty(User user, string passwordHash) + private bool IsPasswordEmpty(User user, string password) { - return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password)); } public Task ChangePassword(User user, string newPassword) { - string newPasswordHash = null; + ConvertPasswordFormat(user); + // This is needed to support changing a no password user to a password user + if (string.IsNullOrEmpty(user.Password)) + { + PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); + newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes); + newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; + newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash); + user.Password = newPasswordHash.ToString(); + return Task.CompletedTask; + } - if (newPassword != null) + PasswordHash passwordHash = new PasswordHash(user.Password); + if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { - newPasswordHash = GetHashedString(user, newPassword); + passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); + passwordHash.Id = _cryptographyProvider.DefaultHashMethod; + passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); + } + else if (newPassword != null) + { + passwordHash.Hash = GetHashedString(user, newPassword); } - if (string.IsNullOrWhiteSpace(newPasswordHash)) + if (string.IsNullOrWhiteSpace(passwordHash.Hash)) { - throw new ArgumentNullException(nameof(newPasswordHash)); + throw new ArgumentNullException(nameof(passwordHash.Hash)); } - user.Password = newPasswordHash; + user.Password = passwordHash.ToString(); return Task.CompletedTask; } public string GetPasswordHash(User user) { - return string.IsNullOrEmpty(user.Password) - ? GetEmptyHashedString(user) - : user.Password; + return user.Password; } - public string GetEmptyHashedString(User user) + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { - return GetHashedString(user, string.Empty); + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); } /// <summary> @@ -91,14 +176,28 @@ namespace Emby.Server.Implementations.Library /// </summary> public string GetHashedString(User user, string str) { - var salt = user.Salt; - if (salt != null) + PasswordHash passwordHash; + if (string.IsNullOrEmpty(user.Password)) + { + passwordHash = new PasswordHash(_cryptographyProvider); + } + else { - // return BCrypt.HashPassword(str, salt); + ConvertPasswordFormat(user); + passwordHash = new PasswordHash(user.Password); } - // legacy - return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + if (passwordHash.SaltBytes != null) + { + // the password is modern format with PBKDF and we should take advantage of that + passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + } + else + { + // the password has no salt and should be called with the older method for safety + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); + } } } } |
