Adds Argon2 for password protection (#123)

This commit is contained in:
Kamron Batman 2020-05-01 22:42:39 -07:00 committed by GitHub
parent 29467a3ed9
commit dad8a64fad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 1152 additions and 167 deletions

13
.circleci/checksum.txt Normal file
View file

@ -0,0 +1,13 @@
95482048463cdc99c77a69951654de7b28fd6880 ../Projects/Argon2/Argon2.csproj
ebafae4e8160057cbf408cb13114479ca26e1b58 ../Projects/Scripts.Tests/Scripts.Tests.csproj
6c6a48220658bdfa7b2e1e173077fd139ed4af2c ../Projects/Scripts/Scripts.csproj
6213c10f697b45729bab15f7870c42dcf49b026b ../Projects/Server.Tests/Server.Tests.csproj
4b3626e04f76dff1ab445e207658a3b386fce4e5 ../Projects/Server/Server.csproj
a663d309b6b86c7632ea5cd175468dd5180808c1 ../Projects/Argon2/Argon2.cs
1e89625abe364f895346d0c5753f3ca6653cf7df ../Projects/Argon2/Argon2Context.cs
abbb34d85f55ca1d78a400b4d46afbd6965f642b ../Projects/Argon2/Argon2Error.cs
00ef35a6809d4959bdda1de01f45e39ff388270f ../Projects/Argon2/Argon2Exception.cs
53164d1621e24adefc6917379d36d7273551f457 ../Projects/Argon2/Argon2PasswordHasher.cs
a137d2fe0478d065d478366168f80ab9eaaa9c3a ../Projects/Argon2/Argon2Type.cs
c09f7a2f9690d56ca838aafb28640c09950c5a8c ../Projects/Argon2/HashMetadata.cs
b5da07548eb41ec9bd35bc2def006f23698b357e ../Projects/Argon2/Utility.cs

View file

@ -19,17 +19,20 @@ jobs:
- checkout
- restore_cache:
keys:
- dotnet-packages-linux-{{ checksum "Projects/Server/Server.csproj" }}-{{ checksum "Projects/Scripts/Scripts.csproj" }}
- dotnet-packages-v1-{{ arch }}-{{ checksum ".circleci/checksum.txt" }}
- run:
name: Build Argon2
command: dotnet build -c Release -r linux-x64 Projects/Argon2/Argon2.csproj
- run:
name: Install ModernUO dependencies
command: dotnet restore
- save_cache:
paths:
- ~/.nuget/packages
key: dotnet-packages-linux-{{ checksum "Projects/Server/Server.csproj" }}-{{ checksum "Projects/Scripts/Scripts.csproj" }}
- /mnt/ramdisk/packages
key: dotnet-packages-v1-{{ arch }}-{{ checksum ".circleci/checksum.txt" }}
- run:
name: Building ModernUO for Linux (Debug)
command: dotnet build -c Release /p:PublishProfile=Linux
command: dotnet build -c Debug -r linux-x64
build_windows:
description: Build application
executor: win/default
@ -37,17 +40,20 @@ jobs:
- checkout
- restore_cache:
keys:
- dotnet-packages-windows-{{ checksum "Projects/Server/Server.csproj" }}-{{ checksum "Projects/Scripts/Scripts.csproj" }}
- dotnet-packages-v1-{{ arch }}-{{ checksum ".circleci/checksum.txt" }}
- run:
name: Build Argon2
command: dotnet build -c Debug -r win-x64 Projects/Argon2/Argon2.csproj
- run:
name: Install ModernUO dependencies
command: dotnet restore
- save_cache:
paths:
- ~/.nuget/packages
key: dotnet-packages-windows-{{ checksum "Projects/Server/Server.csproj" }}-{{ checksum "Projects/Scripts/Scripts.csproj" }}
- /mnt/ramdisk/packages
key: dotnet-packages-v1-{{ arch }}-{{ checksum ".circleci/checksum.txt" }}
- run:
name: Building ModernUO for Windows (Debug)
command: dotnet build -c Release /p:PublishProfile=Windows
command: dotnet build -c Debug -r win-x64
code_styling:
description: Code analysis
executor: linux-build
@ -56,17 +62,20 @@ jobs:
- checkout
- restore_cache:
keys:
- dotnet-packages-linux-{{ checksum "Projects/Server/Server.csproj" }}-{{ checksum "Projects/Scripts/Scripts.csproj" }}
- dotnet-packages-v1-{{ arch }}-{{ checksum ".circleci/checksum.txt" }}
- run:
name: Build Argon2
command: dotnet build -c Debug -r linux-x64 Projects/Argon2/Argon2.csproj
- run:
name: Install ModernUO dependencies
command: dotnet restore
- save_cache:
paths:
- ~/.nuget/packages
key: dotnet-packages-linux-{{ checksum "Projects/Server/Server.csproj" }}-{{ checksum "Projects/Scripts/Scripts.csproj" }}
- /mnt/ramdisk/packages
key: dotnet-packages-v1-{{ arch }}-{{ checksum ".circleci/checksum.txt" }}
- run:
name: Building with Analyzers
command: dotnet build -c Analyze /p:PublishProfile=Linux
command: dotnet build -c Analyze -r linux-x64
tests:
description: Unit Tests
executor: linux-build
@ -75,17 +84,20 @@ jobs:
- checkout
- restore_cache:
keys:
- dotnet-packages-linux-{{ checksum "Projects/Server/Server.csproj" }}-{{ checksum "Projects/Scripts/Scripts.csproj" }}
- dotnet-packages-v1-{{ arch }}-{{ checksum ".circleci/checksum.txt" }}
- run:
name: Build Argon2
command: dotnet build -c Debug -r linux-x64 Projects/Argon2/Argon2.csproj
- run:
name: Install ModernUO dependencies
command: dotnet restore
- save_cache:
paths:
- ~/.nuget/packages
key: dotnet-packages-linux-{{ checksum "Projects/Server/Server.csproj" }}-{{ checksum "Projects/Scripts/Scripts.csproj" }}
- /mnt/ramdisk/packages
key: dotnet-packages-linux-{{ checksum ".circleci/checksum.txt" }}
- run:
name: Running unit tests
command: dotnet test
command: dotnet test -r linux-x64
workflows:
build-and-test:
jobs:

1
.circleci/genchecksum.sh Executable file
View file

@ -0,0 +1 @@
sha1sum ../Projects/*/*.csproj ../Projects/Argon2/*.cs > "checksum.txt"

3
.gitignore vendored
View file

@ -39,3 +39,6 @@
/.vs
.DS_Store
/packages/*
!/packages/do_not_delete

View file

@ -2,45 +2,53 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29102.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Argon2", "Projects\Argon2\Argon2.csproj", "{97150E9D-5561-4D3C-9B78-E795544507D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Projects\Server\Server.csproj", "{5E93BB35-3661-4822-9A8A-859726BAD87F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scripts", "Projects\Scripts\Scripts.csproj", "{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Tests", "Projects\Server.Tests\Server.Tests.csproj", "{D7A5D3AF-D607-46EF-BAAD-0D424190311F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server.Tests", "Projects\Server.Tests\Server.Tests.csproj", "{D7A5D3AF-D607-46EF-BAAD-0D424190311F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scripts.Tests", "Projects\Scripts.Tests\Scripts.Tests.csproj", "{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scripts.Tests", "Projects\Scripts.Tests\Scripts.Tests.csproj", "{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Analyze|Any CPU = Analyze|Any CPU
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Analyze|x64 = Analyze|x64
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Analyze|Any CPU.ActiveCfg = Analyze|Any CPU
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Analyze|Any CPU.Build.0 = Analyze|Any CPU
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Release|Any CPU.Build.0 = Release|Any CPU
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Analyze|Any CPU.ActiveCfg = Analyze|Any CPU
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Analyze|Any CPU.Build.0 = Analyze|Any CPU
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Release|Any CPU.Build.0 = Release|Any CPU
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Analyze|Any CPU.ActiveCfg = Debug|Any CPU
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Analyze|Any CPU.Build.0 = Debug|Any CPU
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Release|Any CPU.Build.0 = Release|Any CPU
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Analyze|Any CPU.ActiveCfg = Debug|Any CPU
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Analyze|Any CPU.Build.0 = Debug|Any CPU
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|Any CPU.Build.0 = Release|Any CPU
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Analyze|x64.ActiveCfg = Release|x64
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Analyze|x64.Build.0 = Release|x64
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Debug|x64.ActiveCfg = Debug|x64
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Debug|x64.Build.0 = Debug|x64
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Release|x64.ActiveCfg = Release|x64
{5E93BB35-3661-4822-9A8A-859726BAD87F}.Release|x64.Build.0 = Release|x64
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Analyze|x64.ActiveCfg = Release|x64
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Analyze|x64.Build.0 = Release|x64
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Debug|x64.ActiveCfg = Debug|x64
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Debug|x64.Build.0 = Debug|x64
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Release|x64.ActiveCfg = Release|x64
{83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Release|x64.Build.0 = Release|x64
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Analyze|x64.ActiveCfg = Debug|x64
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Analyze|x64.Build.0 = Debug|x64
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Debug|x64.ActiveCfg = Debug|x64
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Debug|x64.Build.0 = Debug|x64
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Release|x64.ActiveCfg = Release|x64
{D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Release|x64.Build.0 = Release|x64
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Analyze|x64.ActiveCfg = Debug|x64
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Analyze|x64.Build.0 = Debug|x64
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Debug|x64.ActiveCfg = Debug|x64
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Debug|x64.Build.0 = Debug|x64
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|x64.ActiveCfg = Release|x64
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|x64.Build.0 = Release|x64
{97150E9D-5561-4D3C-9B78-E795544507D0}.Analyze|x64.ActiveCfg = Release|x64
{97150E9D-5561-4D3C-9B78-E795544507D0}.Analyze|x64.Build.0 = Release|x64
{97150E9D-5561-4D3C-9B78-E795544507D0}.Debug|x64.ActiveCfg = Debug|x64
{97150E9D-5561-4D3C-9B78-E795544507D0}.Debug|x64.Build.0 = Debug|x64
{97150E9D-5561-4D3C-9B78-E795544507D0}.Release|x64.ActiveCfg = Release|x64
{97150E9D-5561-4D3C-9B78-E795544507D0}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

6
NuGet.Config Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="local" value="packages" />
</packageSources>
</configuration>

128
Projects/Argon2/Argon2.cs Normal file
View file

@ -0,0 +1,128 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Server.Accounting.Security
{
internal interface IArgon2
{
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
byte[] pwd,
byte[] salt,
byte[] hash,
byte[] encoded,
int type, int version);
Argon2Error Verify(byte[] encoded, byte[] pwd, int pwdlen, int type);
// TODO: Change str to use ReadOnlySpan<char> then convert to pointer later
Argon2Error Decode(Argon2Context ctx, string str, int type);
}
internal static class Argon2
{
internal static readonly IArgon2 Library;
static Argon2()
{
if (RuntimeUtility.Unix)
Library = new UnixArgon2();
else
Library = new WindowsArgon2();
}
}
internal class WindowsArgon2 : IArgon2
{
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
byte[] pwd,
byte[] salt,
byte[] hash,
byte[] encoded,
int type, int version) =>
NativeMethods.crypto_argon2_hash(t_cost, m_cost, parallelism,
pwd, pwd.Length,
salt, salt.Length,
hash, hash.Length,
encoded, encoded.Length,
type, version
);
public Argon2Error Verify(byte[] encoded, byte[] pwd, int pwdlen, int type) =>
NativeMethods.crypto_argon2_verify(encoded, pwd, pwdlen, type);
public Argon2Error Decode(Argon2Context ctx, string str, int type)
{
byte[] bytes = new byte[str.Length];
Encoding.ASCII.GetBytes(str, bytes);
// TODO: Use pointers instead
return NativeMethods.crypto_decode_string(ctx, bytes, type);
}
internal static class NativeMethods
{
[DllImport("argon2.dll", EntryPoint = "crypto_argon2_hash", CallingConvention = CallingConvention.Cdecl)]
internal static extern Argon2Error crypto_argon2_hash(uint t_cost, uint m_cost, uint parallelism,
byte[] pwd, int pwdlen,
byte[] salt, int saltlen,
byte[] hash, int hashlen,
byte[] encoded, int encodedlen,
int type, int version
);
[DllImport("argon2.dll", EntryPoint = "crypto_argon2_verify", CallingConvention = CallingConvention.Cdecl)]
internal static extern Argon2Error crypto_argon2_verify(byte[] encoded, byte[] pwd, int pwdlen, int type);
[DllImport("argon2.dll", EntryPoint = "crypto_decode_string", CallingConvention = CallingConvention.Cdecl)]
internal static extern Argon2Error crypto_decode_string(Argon2Context ctx, byte[] str, int type);
}
}
internal class UnixArgon2 : IArgon2
{
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
byte[] pwd,
byte[] salt,
byte[] hash,
byte[] encoded,
int type, int version) =>
NativeMethods.crypto_argon2_hash(t_cost, m_cost, parallelism,
pwd, pwd.Length,
salt, salt.Length,
hash, hash.Length,
encoded, encoded.Length,
type, version
);
public Argon2Error Verify(byte[] encoded, byte[] pwd, int pwdlen, int type) =>
NativeMethods.crypto_argon2_verify(encoded, pwd, pwdlen, type);
public Argon2Error Decode(Argon2Context ctx, string str, int type)
{
byte[] bytes = new byte[str.Length];
Encoding.ASCII.GetBytes(str, bytes);
// TODO: Use pointers instead
return NativeMethods.crypto_decode_string(ctx, bytes, type);
}
internal static class NativeMethods
{
[DllImport("libargon2", EntryPoint = "argon2_hash")]
internal static extern Argon2Error crypto_argon2_hash(uint t_cost, uint m_cost, uint parallelism,
byte[] pwd, int pwdlen,
byte[] salt, int saltlen,
byte[] hash, int hashlen,
byte[] encoded, int encodedlen,
int type, int version
);
[DllImport("libargon2", EntryPoint = "argon2_verify")]
internal static extern Argon2Error crypto_argon2_verify(byte[] encoded, byte[] pwd, int pwdlen, int type);
[DllImport("libargon2", EntryPoint = "decode_string")]
internal static extern Argon2Error crypto_decode_string(Argon2Context ctx, byte[] str, int type);
}
}
}

View file

@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<PackageVersion>1.0.0</PackageVersion>
<RootNamespace>Server</RootNamespace>
<AssemblyName>Argon2.Bindings</AssemblyName>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>8.0</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>true</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Configurations>Debug;Release;Analyze</Configurations>
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'win-x64'">WINDOWS</DefineConstants>
<AssemblyVersion>1.0.0</AssemblyVersion>
<Platforms>x64</Platforms>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\win-x64\native\argon2.dll">
<Pack>true</Pack>
<PackagePath>runtimes/win-x64/native</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="runtimes\osx-x64\native\libargon2.dylib">
<Pack>true</Pack>
<PackagePath>runtimes/osx-x64/native</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="runtimes\linux-x64\native\libargon2.so">
<Pack>true</Pack>
<PackagePath>runtimes/linux-x64/native</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Target Name="CopyPackage" AfterTargets="Pack">
<Exec Command="dotnet nuget push $(OutputPath)..\..\$(PackageId).$(PackageVersion).nupkg -s local" />
</Target>
<!-- <Target Name="CopyPackage" AfterTargets="Pack">
<Exec Command="nuget add $(OutputPath)..\$(PackageId).$(PackageVersion).nupkg -Source ../../packages" />
</Target> -->
</Project>

View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>Argon2.Bindings</id>
<version>1.0.0</version>
<authors>Kamron Batman</authors>
<owners>Kamron Batman</owners>
<license type="expression">MIT</license>
<projectUrl>https://github.com/modernUO/ModernUO</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Argon2 C# Bindings for ModernUO</description>
<releaseNotes>Initial release</releaseNotes>
<copyright>Copyright 2020</copyright>
</metadata>
</package>

View file

@ -0,0 +1,34 @@
using System;
using System.Runtime.InteropServices;
namespace Server.Accounting.Security
{
[StructLayout(LayoutKind.Sequential)]
internal class Argon2Context
{
public IntPtr Out;
public uint OutLen;
public IntPtr Pwd;
public uint PwdLen;
public IntPtr Salt;
public uint SaltLen;
public IntPtr Secret;
public uint SecretLen;
public IntPtr AssocData;
public uint AssocDataLen;
public uint TimeCost;
public uint MemoryCost;
public uint Lanes;
public uint Threads;
public IntPtr AllocateCallback;
public IntPtr FreeCallback;
public uint Flags;
}
}

View file

@ -0,0 +1,77 @@
namespace Server.Accounting.Security
{
/// <summary>
/// An enumeration of the possible error codes which are returned from Daniel Dinu and
/// Dmitry Khovratovich's Argon2 library.
///
/// Some of these error conditions cannot be reached while using the C# PasswordHasher wrapper
/// </summary>
public enum Argon2Error
{
/// <summary>
/// The operation was successful
/// </summary>
OK = 0,
/// <summary>
/// The output hash length is less than 4 bytes
/// </summary>
OUTPUT_TOO_SHORT = -2,
/// <summary>
/// The salt is less than 8 bytes
/// </summary>
SALT_TOO_SHORT = -6,
/// <summary>
/// The time cost is less than 1
/// </summary>
TIME_TOO_SMALL = -12,
/// <summary>
/// The memory cost is less than 8 (KiB)
/// </summary>
MEMORY_TOO_LITTLE = -14,
/// <summary>
/// The memory cost is greater than 2^21 (KiB) (2 GiB)
/// </summary>
MEMORY_TOO_MUCH = -15,
/// <summary>
/// The parallelism is less than 1
/// </summary>
LANES_TOO_FEW = -16,
/// <summary>
/// The parallelism is greater than 16,777,215
/// </summary>
LANES_TOO_MANY = -17,
/// <summary>
/// Memory allocation failed
/// </summary>
MEMORY_ALLOCATION_ERROR = -22,
/// <summary>
/// The parallelism is less than 1
/// </summary>
THREADS_TOO_FEW = -28,
/// <summary>
/// The parallelism is greater than 16,777,215
/// </summary>
THREADS_TOO_MANY = -29,
/// <summary>
/// This will not be returned from the C# PasswordHasher wrapper
/// </summary>
DECODING_FAIL = -32,
/// <summary>
/// Unable to create the number of threads requested
/// </summary>
THREAD_FAIL = -33,
/// <summary>
/// This will not be returned from the C# PasswordHasher wrapper
/// </summary>
VERIFY_MISMATCH = -35
}
}

View file

@ -0,0 +1,20 @@
using System;
namespace Server.Accounting.Security
{
/// <summary>
/// An exception class to wrap the errors returned by Daniel Dinu and Dmitry Khovratovich's Argon2 library.
///
/// Except through very unusual conditions, the only exceptions which could be thrown from PasswordHasher
/// are Argon2Exception, ArgumentNullException, DllNotFoundException (if libargon2.dll is not found)
/// </summary>
public class Argon2Exception : Exception
{
/// <summary>
/// Construct an Argon2Exception with the specified Argon2 error code
/// <param name="action">Which method the Argon2Exception originated from</param>
/// <param name="error">The error returned from the Argon2 library</param>
/// </summary>
public Argon2Exception(string action, Argon2Error error) : base(string.Format("Error during Argon2 {0}: ({1}) {2}", action, (int)error, error)) {}
}
}

View file

@ -0,0 +1,388 @@
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
// using System.Text.RegularExpressions;
namespace Server.Accounting.Security
{
/// <summary>
/// PasswordHasher is a class for creating Argon2 hashes and verifying them. This is a wrapper around
/// Daniel Dinu and Dmitry Khovratovich's Argon2 library.
/// </summary>
public class Argon2PasswordHasher
{
private static readonly RNGCryptoServiceProvider Rng = new RNGCryptoServiceProvider();
// private static readonly Regex HashRegex = new Regex(@"^\$argon2([di])\$v=(\d+)$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$", RegexOptions.Compiled);
/// <summary>
/// How many iterations of the Argon2 hash to perform
/// </summary>
public uint TimeCost { get; set; }
/// <summary>
/// How much memory to use while hashing in kibibytes (KiB)
/// </summary>
public uint MemoryCost { get; set; }
/// <summary>
/// How many threads to use while hashing
/// </summary>
public uint Parallelism { get; set; }
/// <summary>
/// The type of Argon2 hashing algorithm to use
/// Argon2d - The memory access is dependent upon the hash value (vulnerable to side-channel attacks)
/// Argon2i - The memory access is independent upon the hash value (safe from side-channel atacks)
/// </summary>
public Argon2Type ArgonType { get; set; }
/// <summary>
/// Length of the generated raw hash in bytes
/// </summary>
public uint HashLength { get; set; }
/// <summary>
/// How strings should be decoded when passed to the Hash method.
/// The default is Encoding.UTF8.
/// </summary>
public Encoding StringEncoding { get; set; }
/// <summary>
/// Initialize the Argon2 PasswordHasher with default performance and algorithm settings based upon the environment the hashing will be used in.
/// You should perform your own profiling to determine what the parameters should be for your specific usage; however, this attempts to provide
/// some reasonable defaults.
/// </summary>
public Argon2PasswordHasher()
{
TimeCost = 3;
MemoryCost = 8192;
Parallelism = 1;
ArgonType = Argon2Type.Argon2i;
HashLength = 32;
StringEncoding = Encoding.UTF8;
}
/// <summary>
/// Initialize the Argon2 PasswordHasher with the performance and algorithm settings to use while hashing
/// <param name="timeCost">How many iterations of the Argon2 hash to perform (default: 3, must be at least 1)</param>
/// <param name="memoryCost">How much memory to use while hashing in kibibytes (KiB) (default: 8192 KiB [8 MiB], must be at least 8 KiB)</param>
/// <param name="parallelism">How many threads to use while hashing (default: 1, must be at least 1)</param>
/// <param name="argonType">The type of Argon2 hashing algorithm to use (Independent [default] or Dependent)</param>
/// <param name="hashLength">The length of the resulting hash in bytes (default: 32)</param>
/// </summary>
public Argon2PasswordHasher(uint timeCost = 3, uint memoryCost = 8192, uint parallelism = 1, Argon2Type argonType = Argon2Type.Argon2i, uint hashLength = 32)
{
TimeCost = timeCost;
MemoryCost = memoryCost;
Parallelism = parallelism;
ArgonType = argonType;
HashLength = hashLength;
StringEncoding = Encoding.UTF8;
}
/// <summary>
/// Hash the password using Argon2 with a cryptographically-secure, random, 16-byte salt.
/// This is the only overload of the Hash method that the typical user will need to use for password storage. The other overloads are provided for interoperability purposes.
/// Do not compare two Argon2 hashes directly. Instead, use the Verify or VerifyAndUpdate methods.
/// <param name="password">A string representing the password to be hashed. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
/// </summary>
public string Hash(string password)
{
CheckNull("Hash", "password", password);
return Hash(StringEncoding.GetBytes(password));
}
/// <summary>
/// Hash the raw password bytes using Argon2 with a cryptographically-secure, random, 16-byte salt.
/// Do not compare two Argon2 hashes directly. Instead, use the Verify or VerifyAndUpdate methods.
/// <param name="password">The raw bytes of the password to be hashed</param>
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
/// </summary>
public string Hash(byte[] password)
{
CheckNull("Hash", "password", password);
return Hash(password, GenerateSalt());
}
/// <summary>
/// Hash the password using Argon2 with the specified salt.
/// Unless you need to specify your own salt for interoperability purposes, prefer the Hash(string password) overload instead.
/// Do not compare two Argon2 hashes directly. Instead, use the Verify or VerifyAndUpdate methods.
/// <param name="password">A string representing the password to be hashed. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
/// <param name="salt">A string representing the salt to be used for the hash. The salt must be at least 8 bytes. The salt is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
/// </summary>
public string Hash(string password, string salt)
{
CheckNull("Hash", "password", password, "salt", salt);
return Hash(StringEncoding.GetBytes(password), StringEncoding.GetBytes(salt));
}
/// <summary>
/// Hash the raw password bytes using Argon2 with the specified salt bytes.
/// Unless you need to specify your own salt for interoperability purposes, prefer the Hash(byte[] password) overload instead.
/// Do not compare two Argon2 hashes directly. Instead, use the Verify or VerifyAndUpdate methods.
/// <param name="password">The raw bytes of the password to be hashed</param>
/// <param name="salt">The raw salt bytes to be used for the hash. The salt must be at least 8 bytes.</param>
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
/// </summary>
public string Hash(byte[] password, byte[] salt)
{
CheckNull("Hash", "password", password, "salt", salt);
byte[] hash = new byte[HashLength];
byte[] encoded = new byte[39 + ((HashLength + salt.Length) * 4 + 3) / 3];
var result = Argon2.Library.Hash(
TimeCost,
MemoryCost,
Parallelism,
password,
salt,
hash,
encoded,
(int)ArgonType,
0x13
);
if (result != Argon2Error.OK)
throw new Argon2Exception("hashing", result);
var firstNonNull = encoded.Length - 2;
while (encoded[firstNonNull] == 0)
firstNonNull--;
return Encoding.ASCII.GetString(encoded, 0, firstNonNull + 1);
}
/// <summary>
/// Hash the password using Argon2 with the specified salt. The HashRaw methods may be used for password-based key derivation.
/// Unless you're using HashRaw for key deriviation or for interoperability purposes, the Hash methods should be used in favor of the HashRaw methods.
/// <param name="password">A string representing the password to be hashed. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
/// <param name="salt">A string representing the salt to be used for the hash. The salt must be at least 8 bytes. The salt is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
/// <returns>A byte array containing only the resulting hash</returns>
/// </summary>
public byte[] HashRaw(string password, string salt)
{
CheckNull("HashRaw", "password", password, "salt", salt);
return HashRaw(StringEncoding.GetBytes(password), StringEncoding.GetBytes(salt));
}
/// <summary>
/// Hash the password using Argon2 with the specified salt. The HashRaw methods may be used for password-based key derivation.
/// Unless you're using HashRaw for key deriviation or for interoperability purposes, the Hash methods should be used in favor of the HashRaw methods.
/// <param name="password">The raw bytes of the password to be hashed</param>
/// <param name="salt">The raw salt bytes to be used for the hash. The salt must be at least 8 bytes.</param>
/// <returns>A byte array containing only the resulting hash</returns>
/// </summary>
public byte[] HashRaw(byte[] password, byte[] salt)
{
byte[] hash = new byte[(int)HashLength];
var result = Argon2.Library.Hash(
TimeCost,
MemoryCost,
Parallelism,
password,
salt,
hash,
null,
(int)ArgonType,
0x13
);
if (result != Argon2Error.OK)
throw new Argon2Exception("raw hashing", result);
return hash;
}
/// <summary>
/// Hashes the password and verifies that the password results in the specified hash.
/// The ArgonType must of this PasswordHasher object must match what was used to generate expectedHash.
/// The other parameters (timeCost, etc.) do not need to match and the parameters embedded in the expectedHash will be used.
/// <param name="expectedHash">Hashing the password should result in this hash</param>
/// <param name="password">The password to hash and compare its result to expectedHash. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
/// <returns>Whether the password results in the expectedHash when hashed</returns>
/// </summary>
public bool Verify(string expectedHash, string password)
{
CheckNull("Verify", "expectedHash", expectedHash, "password", password);
return Verify(expectedHash, StringEncoding.GetBytes(password));
}
/// <summary>
/// Hashes the raw password bytes and verifies that the password results in the specified hash.
/// The ArgonType must of this PasswordHasher object must match what was used to generate expectedHash.
/// The other parameters (timeCost, etc.) do not need to match and the parameters embedded in the expectedHash will be used.
/// <param name="expectedHash">Hashing the password should result in this hash</param>
/// <param name="password">The raw password bytes to hash and compare its result to expectedHash</param>
/// <returns>Whether the password results in the expectedHash when hashed</returns>
/// </summary>
public bool Verify(string expectedHash, byte[] password)
{
CheckNull("Verify", "expectedHash", expectedHash, "password", password);
var result = Argon2.Library.Verify(StringEncoding.GetBytes(expectedHash), password, password.Length, (int)ArgonType);
if (result == Argon2Error.OK || result == Argon2Error.VERIFY_MISMATCH || result == Argon2Error.DECODING_FAIL)
return result == Argon2Error.OK;
throw new Argon2Exception("verifying", result);
}
/// <summary>
/// Hashes the password and verifies that the password results in the specified hash. (See Verify method)
/// If the password verification is successful, this method checks to see if the memory cost, time cost, and parallelism
/// match the parameters the PasswordHasher object was constructed with. If they do not much, then the password is rehashed
/// using the new parameters and the result is outputted via the newFormattedHash parameter.
/// <param name="expectedHash">Hashing the password should result in this hash</param>
/// <param name="password">The password to hash and compare its result to expectedHash. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
/// <param name="isUpdated">Whether the cost parameters of expectedHash differ from the PasswordHasher object and if the password was rehashed using th new parameters. This is always false if the password was incorrect.</param>
/// <param name="newFormattedHash">If isUpdated is true, then newFormattedHash is the password hashed with the new cost parameters. If isUpdated is false, then newFormattedHash is expectedHash.</param>
/// <returns>Whether the password results in the expectedHash when hashed</returns>
/// </summary>
public bool VerifyAndUpdate(string expectedHash, string password, out bool isUpdated, out string newFormattedHash)
{
CheckNull("VerifyAndUpdate", "expectedHash", expectedHash, "password", password);
return VerifyAndUpdate(expectedHash, StringEncoding.GetBytes(password), out isUpdated, out newFormattedHash);
}
/// <summary>
/// Hashes the password and verifies that the password results in the specified hash. (See Verify method)
/// If the password verification is successful, this method checks to see if the memory cost, time cost, and parallelism
/// match the parameters the PasswordHasher object was constructed with. If they do not much, then the password is rehashed
/// using the new parameters and the result is outputted via the newFormattedHash parameter.
/// <param name="expectedHash">Hashing the password should result in this hash</param>
/// <param name="password">The raw password bytes to hash and compare its result to expectedHash</param>
/// <param name="isUpdated">Whether the cost parameters of expectedHash differ from the PasswordHasher object and if the password was rehashed using th new parameters. This is always false if the password was incorrect.</param>
/// <param name="newFormattedHash">If isUpdated is true, then newFormattedHash is the password hashed with the new cost parameters. If isUpdated is false, then newFormattedHash is expectedHash.</param>
/// <returns>Whether the password results in the expectedHash when hashed</returns>
/// </summary>
public bool VerifyAndUpdate(string expectedHash, byte[] password, out bool isUpdated, out string newFormattedHash)
{
CheckNull("VerifyAndUpdate", "expectedHash", expectedHash, "password", password);
if (Verify(expectedHash, password))
{
var hashMetadata = ExtractMetadata(expectedHash);
if (hashMetadata.MemoryCost != MemoryCost || hashMetadata.TimeCost != TimeCost || hashMetadata.Parallelism != Parallelism)
{
isUpdated = true;
byte[] salt = hashMetadata.Salt;
newFormattedHash = Hash(password, salt);
}
else
{
isUpdated = false;
newFormattedHash = expectedHash;
}
return true;
}
isUpdated = false;
newFormattedHash = expectedHash;
return false;
}
/// <summary>
/// Generate salt using a Cryptographically-Secure Pseudo-Random Number Generator
/// <param name="byteLength">The number of bytes of salt to generate (default: 16)</param>
/// <returns>A array of randomly-generated bytes</returns>
/// </summary>
public static byte[] GenerateSalt(uint byteLength = 16)
{
var salt = new byte[byteLength];
Rng.GetBytes(salt);
return salt;
}
/// <summary>
/// Extracts the memory cost, time cost, etc. used to generate the Argon2 hash.
/// <param name="formattedHash">An encoded Argon2 hash created by the Hash method</param>
/// <returns>The hash metadata or null if the formattedHash was not a valid encoded Argon2 hash</returns>
/// </summary>
public static HashMetadata ExtractMetadata(string formattedHash)
{
CheckNull("ExtractMetadata", "formattedHash", formattedHash);
var context = new Argon2Context
{
Out = Marshal.AllocHGlobal(formattedHash.Length), // ensure the space to hold the hash is long enough
OutLen = (uint)formattedHash.Length,
Pwd = Marshal.AllocHGlobal(1),
PwdLen = 1,
Salt = Marshal.AllocHGlobal(formattedHash.Length), // ensure the space to hold the salt is long enough
SaltLen = (uint)formattedHash.Length,
Secret = Marshal.AllocHGlobal(1),
SecretLen = 1,
AssocData = Marshal.AllocHGlobal(1),
AssocDataLen = 1,
TimeCost = 0,
MemoryCost = 0,
Lanes = 0,
Threads = 0
};
try
{
var type = formattedHash.StartsWith("$argon2i") ? Argon2Type.Argon2i : Argon2Type.Argon2d;
var result = Argon2.Library.Decode(context, $"{formattedHash}\0", (int)type);
if (result != Argon2Error.OK)
return null;
var salt = new byte[context.SaltLen];
var hash = new byte[context.OutLen];
Marshal.Copy(context.Salt, salt, 0, salt.Length);
Marshal.Copy(context.Out, hash, 0, hash.Length);
return new HashMetadata
{
ArgonType = type,
MemoryCost = context.MemoryCost,
TimeCost = context.TimeCost,
Parallelism = context.Threads,
Salt = salt,
Hash = hash
};
}
finally
{
Marshal.FreeHGlobal(context.Out);
Marshal.FreeHGlobal(context.Pwd);
Marshal.FreeHGlobal(context.Salt);
Marshal.FreeHGlobal(context.Secret);
Marshal.FreeHGlobal(context.AssocData);
}
}
private static void CheckNull(string methodName, params object[] arguments)
{
for (var i = 0; i < arguments.Length; i += 2)
if (arguments[i + 1] == null)
throw new ArgumentNullException(arguments[i].ToString(), string.Format("Argument {0} to method PasswordHasher.{1} is null", arguments[i], methodName));
}
}
}

View file

@ -0,0 +1,18 @@
namespace Server.Accounting.Security
{
/// <summary>
/// The type of Argon2 hashing algorithm to use.
/// </summary>
public enum Argon2Type
{
/// <summary>
/// The memory access is dependent upon the hash value (vulnerable to side-channel attacks)
/// </summary>
Argon2d = 0,
/// <summary>
/// The memory access is independent upon the hash value (safe from side-channel atacks)
/// </summary>
Argon2i = 1
}
}

View file

@ -0,0 +1,60 @@
using System;
namespace Server.Accounting.Security
{
/// <summary>
/// HashMetadata represents the information stored in the encoded Argon2 format
/// </summary>
public class HashMetadata
{
/// <summary>
/// The type of Argon2 hashing algorithm to use
/// Argon2d - The memory access is dependent upon the hash value (vulnerable to side-channel attacks)
/// Argon2i - The memory access is independent upon the hash value (safe from side-channel atacks)
/// </summary>
public Argon2Type ArgonType { get; set; }
/// <summary>
/// How much memory to use while hashing in kibibytes (KiB)
/// </summary>
public uint MemoryCost { get; set; }
/// <summary>
/// How many iterations of the Argon2 hash to perform
/// </summary>
public uint TimeCost { get; set; }
/// <summary>
/// How many threads to use while hashing
/// </summary>
public uint Parallelism { get; set; }
/// <summary>
/// The raw bytes of the salt
/// </summary>
public byte[] Salt { get; set; }
/// <summary>
/// The raw bytes of the hash
/// </summary>
public byte[] Hash { get; set; }
/// <summary>
/// A base-64 encoded string of the salt, minus the padding (=) characters
/// </summary>
public string GetBase64Salt() => Convert.ToBase64String(Salt).Replace("=", "");
/// <summary>
/// A base-64 encoded string of the hash, minus the padding (=) characters
/// </summary>
public string GetBase64Hash() => Convert.ToBase64String(Hash).Replace("=", "");
/// <summary>
/// Converts HashMetadata back into the original Argon2 formatted string.
/// </summary>
public override string ToString() =>
$"$argon2{(ArgonType == Argon2Type.Argon2i ? "i" : "d")}$v=19$m={MemoryCost},t={TimeCost},p={Parallelism}${GetBase64Salt()}${GetBase64Hash()}";
}
}

View file

@ -0,0 +1,13 @@
using System.Runtime.InteropServices;
namespace Server
{
internal static class RuntimeUtility
{
public static bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public static bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
public static bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD;
public static bool Unix = IsDarwin || IsFreeBSD || IsLinux;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,30 @@
using Server.Accounting.Security;
using Xunit;
namespace Server.Tests.Accounting.Security
{
public class Argon2PasswordProtectionTest
{
private const string plainPassword = "hello-good-sir";
[Fact]
public void TestValidates()
{
var passwordProtection = new Argon2PasswordProtection();
string encryptedPassword = passwordProtection.EncryptPassword(plainPassword);
Assert.True(passwordProtection.ValidatePassword(encryptedPassword, plainPassword));
}
[Fact]
public void TestPasswordDoesNotValidate()
{
var passwordProtection = new Argon2PasswordProtection();
string encryptedPassword = passwordProtection.EncryptPassword(plainPassword);
Assert.False(passwordProtection.ValidatePassword(encryptedPassword, "Not the same password"));
}
}
}

View file

@ -1,7 +1,7 @@
using Server.Accounting.Security;
using Xunit;
using Server.Accounting;
namespace Server.Tests.Accounting
namespace Server.Tests.Accounting.Security
{
public class PBKDF2PasswordProtectionTest
{

View file

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<Platforms>x64</Platforms>
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'win-x64'">WINDOWS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />

View file

@ -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);

View file

@ -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;
}
}
}

View 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;
}
}
}

View file

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

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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>

View file

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />

View file

@ -2,7 +2,6 @@
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

View file

@ -6,7 +6,7 @@
<StartupObject>Server.Core</StartupObject>
<AssemblyName>ModernUO</AssemblyName>
<Win32Resource />
<Version>0.3.0</Version>
<Version>0.3.1</Version>
<Authors>Kamron Batman</Authors>
<Company>ModernUO</Company>
<Product>ModernUO Server</Product>
@ -17,13 +17,14 @@
<PlatformTarget>x64</PlatformTarget>
<LangVersion>8.0</LangVersion>
<PublishDir>$(SolutionDir)\Distribution</PublishDir>
<OutDir>$(SolutionDir)\Distribution</OutDir>
<OutputPath>$(SolutionDir)\Distribution</OutputPath>
<!-- <OutDir>$(SolutionDir)\Distribution</OutDir>
<OutputPath>$(SolutionDir)\Distribution</OutputPath> -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<Configurations>Debug;Release;Analyze</Configurations>
<Platforms>x64</Platforms>
</PropertyGroup>
<Target Name="CleanPub" AfterTargets="Clean">
<Message Text="Removing distribution files..." />
@ -48,14 +49,14 @@
<Delete Files="$(SolutionDir)\Distribution\$(AssemblyName).runtimeconfig.json" ContinueOnError="true" />
<Delete Files="$(SolutionDir)\Distribution\System.IO.Pipelines.dll" ContinueOnError="true" />
</Target>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>TRACE;DEBUG</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>
@ -86,13 +87,21 @@
<ItemGroup>
<None Remove="Kestrel\Transport.Libuv\**" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Analyze|AnyCPU'">
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<AdditionalFiles Include="$(SolutionDir)\stylecop.json" Link="stylecop.json" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
<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" />
<AdditionalFiles Include="$(SolutionDir)\Rules.ruleset">
<Link>Rules.rulest</Link>
</AdditionalFiles>
</ItemGroup>
</Project>

View file

@ -580,6 +580,8 @@ consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
License notice for BufferReader/BufferWrite
---------------------------
The MIT License (MIT)
Copyright (c) Harry Pierson
@ -601,3 +603,27 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
License notice for csharp-argon-2
---------------------------
The MIT License (MIT)
Copyright (c) 2016 Kevin Spinar (Alipha)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,4 +1,5 @@
rm -rf Projects/*/obj
rm -rf Projects/*/bin
rm -f Distribution/Assemblies/BouncyCastle.Crypto.dll
rm -f Distribution/Assemblies/MailKit.dll
@ -12,10 +13,15 @@ rm -f Distribution/Assemblies/Microsoft.Extensions.Logging.Abstractions.dll
rm -f Distribution/Assemblies/Microsoft.Extensions.Primitives.dll
rm -f Distribution/Assemblies/MimeKit.dll
rm -f Distribution/Assemblies/System.IO.Pipelines.dll
rm -f Distribution/Assemblies/Argon2.Bindings.dll
rm -f Distribution/Assemblies/libargon2.so
rm -f Distribution/Assemblies/libargon2.dylib
rm -f Distribution/Assemblies/argon2.dll
rm -f Distribution/libdrng.dll
rm -f Distribution/libuv.dll
rm -f Distribution/libuv.so
rm -f Distribution/libuv.dylib
rm -f Distribution/Microsoft.AspNetCore.Connections.Abstractions.dll
rm -f Distribution/Microsoft.AspNetCore.Http.Features.dll
rm -f Distribution/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.dll

View file

@ -1 +1,2 @@
dotnet publish -c Debug /p:PublishProfile=Linux
dotnet build -c Debug -r linux-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Debug -r linux-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Debug /p:PublishProfile=Linux
dotnet build -c Debug -r linux-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Debug -r linux-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Release /p:PublishProfile=Linux
dotnet build -c Release -r linux-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Release -r linux-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Release /p:PublishProfile=Linux
dotnet build -c Release -r linux-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Release -r linux-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Debug /p:PublishProfile=OSX
dotnet build -c Debug -r osx-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Debug -r osx-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Debug /p:PublishProfile=OSX
dotnet build -c Debug -r osx-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Debug -r osx-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Release /p:PublishProfile=OSX
dotnet build -c Release -r osx-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Release -r osx-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Release /p:PublishProfile=OSX
dotnet build -c Release -r osx-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Release -r osx-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Debug /p:PublishProfile=Windows
dotnet build -c Debug -r win-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Debug -r win-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Debug /p:PublishProfile=Windows
dotnet build -c Debug -r win-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Debug -r win-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Release /p:PublishProfile=Windows
dotnet build -c Release -r win-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Release -r win-x64 --self-contained=false

View file

@ -1 +1,2 @@
dotnet publish -c Release /p:PublishProfile=Windows
dotnet build -c Release -r win-x64 Projects/Argon2/Argon2.csproj
dotnet publish -c Release -r win-x64 --self-contained=false

0
packages/do_not_delete Normal file
View file