Adds Argon2 for password protection (#123)
This commit is contained in:
parent
29467a3ed9
commit
dad8a64fad
54 changed files with 1152 additions and 167 deletions
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Xml;
|
||||
using Server.Accounting.Security;
|
||||
using Server.Misc;
|
||||
using Server.Mobiles;
|
||||
using Server.Multis;
|
||||
|
|
@ -297,13 +298,9 @@ namespace Server.Accounting
|
|||
|
||||
public bool GetBanTags(out DateTime banTime, out TimeSpan banDuration)
|
||||
{
|
||||
string tagTime = GetTag("BanTime");
|
||||
string tagDuration = GetTag("BanDuration");
|
||||
|
||||
if (tagTime != null)
|
||||
banTime = Utility.GetXMLDateTime(tagTime, DateTime.MinValue);
|
||||
else
|
||||
banTime = DateTime.MinValue;
|
||||
banTime = Utility.GetXMLDateTime(GetTag("BanTime"), DateTime.MinValue);
|
||||
|
||||
if (tagDuration == "Infinite")
|
||||
banDuration = TimeSpan.MaxValue;
|
||||
|
|
@ -323,7 +320,9 @@ namespace Server.Accounting
|
|||
|
||||
public bool CheckPassword(string plainPassword)
|
||||
{
|
||||
bool ok = AccountSecurity.GetPasswordProtection(m_PasswordAlgorithm).ValidatePassword(Password, plainPassword);
|
||||
string phrase = m_PasswordAlgorithm == PasswordProtectionAlgorithm.SHA1 ? $"{Username}{plainPassword}" : plainPassword;
|
||||
|
||||
bool ok = AccountSecurity.GetPasswordProtection(m_PasswordAlgorithm).ValidatePassword(Password, phrase);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
|
|
@ -459,6 +458,24 @@ namespace Server.Accounting
|
|||
// TODO: Offload passwords to its own module so it can be easily written/upgraded
|
||||
Password = Utility.GetText(node["password"], null);
|
||||
Enum.TryParse(Utility.GetText(node["passwordAlgorithm"], null), true, out m_PasswordAlgorithm);
|
||||
|
||||
if (m_PasswordAlgorithm == PasswordProtectionAlgorithm.None)
|
||||
{
|
||||
string md5Password = Utility.GetText(node["cryptPassword"], null);
|
||||
string sha1Password = Utility.GetText(node["newCryptPassword"], null);
|
||||
|
||||
if (sha1Password != null)
|
||||
{
|
||||
Password = sha1Password;
|
||||
m_PasswordAlgorithm = PasswordProtectionAlgorithm.SHA1;
|
||||
}
|
||||
else if (md5Password != null)
|
||||
{
|
||||
Password = md5Password;
|
||||
m_PasswordAlgorithm = PasswordProtectionAlgorithm.MD5;
|
||||
}
|
||||
}
|
||||
|
||||
Enum.TryParse(Utility.GetText(node["accessLevel"], "Player"), true, out m_AccessLevel);
|
||||
Flags = Utility.GetXMLInt32(Utility.GetText(node["flags"], "0"), 0);
|
||||
Created = Utility.GetXMLDateTime(Utility.GetText(node["created"], null), DateTime.UtcNow);
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
namespace Server.Accounting
|
||||
{
|
||||
public enum PasswordProtectionAlgorithm
|
||||
{
|
||||
PBKDF2
|
||||
}
|
||||
|
||||
public static class AccountSecurity
|
||||
{
|
||||
// TODO: Put it in a configuration
|
||||
public const PasswordProtectionAlgorithm AlgorithmName = PasswordProtectionAlgorithm.PBKDF2;
|
||||
|
||||
public static readonly IPasswordProtection CurrentPasswordProtection = GetPasswordProtection(AlgorithmName);
|
||||
|
||||
public static IPasswordProtection GetPasswordProtection(PasswordProtectionAlgorithm algorithm)
|
||||
{
|
||||
var passwordProtection = algorithm switch
|
||||
{
|
||||
PasswordProtectionAlgorithm.PBKDF2 => PBKDF2PasswordProtection.Instance,
|
||||
_ => null
|
||||
};
|
||||
|
||||
return passwordProtection;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Projects/Scripts/Accounting/Security/AccountSecurity.cs
Normal file
45
Projects/Scripts/Accounting/Security/AccountSecurity.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public enum PasswordProtectionAlgorithm
|
||||
{
|
||||
// Obsolete algorithms. These are not secure!
|
||||
// They are included for password upgrades only.
|
||||
None,
|
||||
MD5,
|
||||
SHA1,
|
||||
|
||||
// Support algorithms
|
||||
PBKDF2,
|
||||
Argon2 // Recommended algorithm for real security.
|
||||
}
|
||||
|
||||
public static class AccountSecurity
|
||||
{
|
||||
// TODO: Put it in a configuration
|
||||
public const PasswordProtectionAlgorithm AlgorithmName = PasswordProtectionAlgorithm.Argon2;
|
||||
|
||||
public static readonly IPasswordProtection CurrentPasswordProtection = GetPasswordProtection(AlgorithmName);
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
if (AlgorithmName < PasswordProtectionAlgorithm.PBKDF2)
|
||||
throw new Exception($"Security: {AlgorithmName} is obselete and not secure. Do not use it.");
|
||||
}
|
||||
|
||||
public static IPasswordProtection GetPasswordProtection(PasswordProtectionAlgorithm algorithm)
|
||||
{
|
||||
var passwordProtection = algorithm switch
|
||||
{
|
||||
PasswordProtectionAlgorithm.MD5 => MD5PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.SHA1 => SHA1PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.PBKDF2 => PBKDF2PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.Argon2 => Argon2PasswordProtection.Instance,
|
||||
_ => null
|
||||
};
|
||||
|
||||
return passwordProtection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class Argon2PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new Argon2PasswordProtection();
|
||||
private Argon2PasswordHasher m_PasswordHasher = new Argon2PasswordHasher();
|
||||
|
||||
public string EncryptPassword(string plainPassword) =>
|
||||
m_PasswordHasher.Hash(plainPassword);
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
m_PasswordHasher.Verify(encryptedPassword, plainPassword);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class MD5PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new MD5PasswordProtection();
|
||||
private MD5CryptoServiceProvider m_MD5HashProvider = new MD5CryptoServiceProvider();
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
|
||||
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
|
||||
Encoding.ASCII.GetBytes(password, bytes);
|
||||
|
||||
return BitConverter.ToString(m_MD5HashProvider.ComputeHash(bytes));
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
EncryptPassword(plainPassword) == encryptedPassword;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,11 @@ using System.Buffers.Binary;
|
|||
using System.Security.Cryptography;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class PBKDF2PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static PBKDF2PasswordProtection Instance = new PBKDF2PasswordProtection();
|
||||
public static IPasswordProtection Instance = new PBKDF2PasswordProtection();
|
||||
|
||||
private const ushort m_MinIterations = 1024;
|
||||
private const ushort m_MaxIterations = 1536;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class SHA1PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new SHA1PasswordProtection();
|
||||
private SHA1CryptoServiceProvider m_SHA1HashProvider = new SHA1CryptoServiceProvider();
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
|
||||
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
|
||||
Encoding.ASCII.GetBytes(password, bytes);
|
||||
|
||||
return BitConverter.ToString(m_SHA1HashProvider.ComputeHash(bytes));
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
EncryptPassword(plainPassword) == encryptedPassword;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,8 @@
|
|||
<PublishDir>$(SolutionDir)\Distribution\Assemblies</PublishDir>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Configurations>Debug;Release;Analyze</Configurations>
|
||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'win-x64'">WINDOWS</DefineConstants>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<Target Name="CleanPub" AfterTargets="Clean">
|
||||
<Message Text="Removing distribution assemblies..." />
|
||||
|
|
@ -38,14 +40,14 @@
|
|||
<Delete Files="$(SolutionDir)\Distribution\Assemblies\$(AssemblyName).pdb" ContinueOnError="true" />
|
||||
<Delete Files="$(SolutionDir)\Distribution\Assemblies\System.IO.Pipelines.dll" ContinueOnError="true" />
|
||||
</Target>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DefineConstants>TRACE;DEBUG;$(DefineConstants)</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Analyze|AnyCPU'">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Analyze|x64'">
|
||||
<Optimize>true</Optimize>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
|
||||
|
|
@ -64,6 +66,7 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Argon2.Bindings" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Analyze|AnyCPU'">
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
|
||||
|
|
@ -74,4 +77,21 @@
|
|||
</PackageReference>
|
||||
<AdditionalFiles Include="$(SolutionDir)\Rules.ruleset" Link="Rules.rulest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Analyze|x64'">
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<Version>1.1.118</Version>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="$(SolutionDir)\stylecop.json">
|
||||
<Link>stylecop.json</Link>
|
||||
</AdditionalFiles>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">
|
||||
<Version>2.9.8</Version>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="$(SolutionDir)\Rules.ruleset">
|
||||
<Link>Rules.rulest</Link>
|
||||
</AdditionalFiles>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue