[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better default authentication #870

Merged
merged 22 commits into from
Mar 7, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
49d9649
added submodule dir to gitignore
LogicalPhallacy Jan 31, 2019
4519ce2
Upgrade crypto provider, retarget better framework
LogicalPhallacy Jan 31, 2019
8bf88f4
Merge pull request #9 from jellyfin/master
LogicalPhallacy Feb 12, 2019
05bbf71
sha256 with salt auth and sha1 interop
LogicalPhallacy Feb 12, 2019
1dc5a62
fixed gitignore fail
LogicalPhallacy Feb 13, 2019
1ffd443
fixed nul user check to be first per justaman
LogicalPhallacy Feb 13, 2019
77602af
Minor fixes re:PR870, added null checks from PR876
LogicalPhallacy Feb 13, 2019
9e58e31
Update Emby.Server.Implementations/Library/DefaultAuthenticationProvi…
cvium Feb 13, 2019
d8e6808
Update Emby.Server.Implementations/Library/DefaultAuthenticationProvi…
cvium Feb 13, 2019
9f3aa2c
Apply suggestions from code review
LogicalPhallacy Feb 18, 2019
48e7274
added justaman notes, fixed new bug from emty has removals
LogicalPhallacy Feb 18, 2019
56e3063
little fixes for JustAMan
LogicalPhallacy Feb 18, 2019
6bbb968
minor changes and return to netstandard
LogicalPhallacy Feb 20, 2019
a0d31a4
merging with master to clear merge conflict
LogicalPhallacy Feb 20, 2019
098de6b
made newlines into linux newlines
LogicalPhallacy Feb 20, 2019
edba82d
fixed logic flip in auth empty check and fixed crypto algo choice
LogicalPhallacy Feb 28, 2019
2c26517
minor style fixes
LogicalPhallacy Mar 5, 2019
bef665b
Minor fixes to address style issues
LogicalPhallacy Mar 6, 2019
c31b0b3
Apply suggestions from code review
Bond-009 Mar 7, 2019
8f4895e
more fixes for perf and style
LogicalPhallacy Mar 7, 2019
dfb1d70
made hashset static and readonly
LogicalPhallacy Mar 7, 2019
f486f59
Update Emby.Server.Implementations/Library/DefaultAuthenticationProvi…
Bond-009 Mar 7, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,4 @@ deployment/**/pkg-dist-tmp/
deployment/collect-dist/

jellyfin_version.ini
MediaBrowser.WebDashboard/jellyfin-web
JustAMan marked this conversation as resolved.
Show resolved Hide resolved
171 changes: 131 additions & 40 deletions Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,131 @@
using System;
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
using System.IO;
using System.Security.Cryptography;
using System.Text;
using MediaBrowser.Model.Cryptography;

namespace Emby.Server.Implementations.Cryptography
{
public class CryptographyProvider : ICryptoProvider
{
public Guid GetMD5(string str)
{
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
}

public byte[] ComputeSHA1(byte[] bytes)
{
using (var provider = SHA1.Create())
{
return provider.ComputeHash(bytes);
}
}

public byte[] ComputeMD5(Stream str)
{
using (var provider = MD5.Create())
{
return provider.ComputeHash(str);
}
}

public byte[] ComputeMD5(byte[] bytes)
{
using (var provider = MD5.Create())
{
return provider.ComputeHash(bytes);
}
}
}
}
using System;
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using MediaBrowser.Model.Cryptography;

namespace Emby.Server.Implementations.Cryptography
{
public class CryptographyProvider : ICryptoProvider
{
private List<string> SupportedHashMethods = new List<string>();
public string DefaultHashMethod => "SHA256";
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
private RandomNumberGenerator rng;
private int defaultiterations = 1000;
public CryptographyProvider()
{
//Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
//there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
SupportedHashMethods = new List<string>
{
"MD5"
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
,"System.Security.Cryptography.MD5"
,"SHA"
,"SHA1"
,"System.Security.Cryptography.SHA1"
,"SHA256"
,"SHA-256"
,"System.Security.Cryptography.SHA256"
,"SHA384"
,"SHA-384"
,"System.Security.Cryptography.SHA384"
,"SHA512"
,"SHA-512"
,"System.Security.Cryptography.SHA512"
};
rng = RandomNumberGenerator.Create();
}

public Guid GetMD5(string str)
{
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
}

public byte[] ComputeSHA1(byte[] bytes)
{
using (var provider = SHA1.Create())
{
return provider.ComputeHash(bytes);
}
}

public byte[] ComputeMD5(Stream str)
{
using (var provider = MD5.Create())
{
return provider.ComputeHash(str);
}
}

public byte[] ComputeMD5(byte[] bytes)
{
using (var provider = MD5.Create())
{
return provider.ComputeHash(bytes);
}
}

public IEnumerable<string> GetSupportedHashMethods()
{
return SupportedHashMethods;
}

private byte[] PBKDF2(string method, byte[] bytes, byte[] salt)
{
using (var r = new Rfc2898DeriveBytes(bytes, salt, defaultiterations, new HashAlgorithmName(method)))
{
return r.GetBytes(32);
}
}

public byte[] ComputeHash(string HashMethod, byte[] bytes)
{
return ComputeHash(HashMethod, bytes, new byte[0]);
}

public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
{
return ComputeHash(DefaultHashMethod, bytes);
}

public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt)
{
if (SupportedHashMethods.Contains(HashMethod))
{
if (salt.Length == 0)
{
using (var h = HashAlgorithm.Create(HashMethod))
{
return h.ComputeHash(bytes);
}
}
else
{
return PBKDF2(HashMethod, bytes, salt);
}
}
else
{
throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod));
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
}
}

public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
{
return PBKDF2(DefaultHashMethod, bytes, salt);
}

public byte[] ComputeHash(PasswordHash hash)
{
return ComputeHash(hash.Id, hash.HashBytes, hash.SaltBytes);
}

public byte[] GenerateSalt()
{
byte[] salt = new byte[8];
rng.GetBytes(salt);
return salt;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</ItemGroup>

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

Expand Down
167 changes: 137 additions & 30 deletions Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Controller.Authentication;
Expand All @@ -19,31 +20,110 @@ public DefaultAuthenticationProvider(ICryptoProvider crypto)

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();
}

public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
{
if (resolvedUser == null)
{
throw new Exception("Invalid username or password");
}

var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);

if (!success)
{
throw new Exception("Invalid username or password");
}
//This is the verson that we need to use for local users. Because reasons.
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
{
ConvertPasswordFormat(resolvedUser);
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
bool success = false;
if (resolvedUser == null)
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
{
success = false;
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
throw new Exception("Invalid username or password");
}
if (!resolvedUser.Password.Contains("$"))
{
ConvertPasswordFormat(resolvedUser);
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
}
PasswordHash ReadyHash = new PasswordHash(resolvedUser.Password);
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
byte[] CalculatedHash;
string CalculatedHashString;
if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == 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);
}
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
if (CalculatedHashString == ReadyHash.Hash)
{
success = true;
//throw new Exception("Invalid username or password");
}
}
else
{
success = false;
JustAMan marked this conversation as resolved.
Show resolved Hide resolved
throw new Exception(String.Format("Requested crypto method not available in provider: {0}", ReadyHash.Id));
}

//var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);

if (!success)
{
throw new Exception("Invalid username or password");
}

return Task.FromResult(new ProviderAuthenticationResult
{
Username = username
});
}

return Task.FromResult(new ProviderAuthenticationResult
{
Username = username
});
//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))
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
{
if (!user.Password.Contains("$"))
{
string hash = user.Password;
user.Password = String.Format("$SHA1${0}", hash);
}
if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
{
string hash = user.EasyPassword;
user.EasyPassword = String.Format("$SHA1${0}", hash);
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

// OLD VERSION //public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
// OLD VERSION //{
// OLD VERSION // if (resolvedUser == null)
// OLD VERSION // {
// OLD VERSION // throw new Exception("Invalid username or password");
// OLD VERSION // }
// OLD VERSION //
// OLD VERSION // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
// OLD VERSION //
// OLD VERSION // if (!success)
// OLD VERSION // {
// OLD VERSION // throw new Exception("Invalid username or password");
// OLD VERSION // }
// OLD VERSION //
// OLD VERSION // return Task.FromResult(new ProviderAuthenticationResult
// OLD VERSION // {
// OLD VERSION // Username = username
// OLD VERSION // });
// OLD VERSION //}

public Task<bool> HasPassword(User user)
{
var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));
Expand All @@ -57,19 +137,26 @@ private bool IsPasswordEmpty(User user, string passwordHash)

public Task ChangePassword(User user, string newPassword)
{
string newPasswordHash = null;

if (newPassword != null)
//string newPasswordHash = null;
ConvertPasswordFormat(user);
PasswordHash passwordHash = new PasswordHash(user.Password);
if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt))
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
{
newPasswordHash = GetHashedString(user, newPassword);
passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
passwordHash.Salt = BitConverter.ToString(passwordHash.SaltBytes).Replace("-","");
passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash);
}else if (newPassword != null)
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
{
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;
}
Expand All @@ -86,19 +173,39 @@ public string GetEmptyHashedString(User user)
return GetHashedString(user, string.Empty);
}

public string GetHashedStringChangeAuth(string NewPassword, PasswordHash passwordHash)
{
return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(NewPassword), passwordHash.SaltBytes)).Replace("-", string.Empty);
}

/// <summary>
/// Gets the hashed string.
/// </summary>
public string GetHashedString(User user, string str)
{
var salt = user.Salt;
if (salt != null)
public string GetHashedString(User user, string str)
{
//This is legacy. Deprecated in the auth method.
//return BitConverter.ToString(_cryptoProvider2.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty);
PasswordHash passwordHash;
if (String.IsNullOrEmpty(user.Password))
{
passwordHash = new PasswordHash(_cryptographyProvider);
}
else
{
// return BCrypt.HashPassword(str, salt);
ConvertPasswordFormat(user);
passwordHash = new PasswordHash(user.Password);
}
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
if (passwordHash.SaltBytes != null)
{
return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str), passwordHash.SaltBytes)).Replace("-",string.Empty);
LogicalPhallacy marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty);
//throw new Exception("User does not have a hash, this should not be possible");
}

// legacy
return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty);

}
}
}
Loading