diff --git a/Account.cs b/Account.cs index 823962a..43a9b3a 100644 --- a/Account.cs +++ b/Account.cs @@ -1,9 +1,6 @@ using System; -using System.Linq; using System.Numerics; -using System.Text; using System.Collections.Generic; -using System.Security.Cryptography; using MySql.Data.MySqlClient; using System.Globalization; @@ -163,55 +160,7 @@ namespace NightmareCoreWeb2 catch (Exception) { } } - return AuthenticateWithToken(password) || VerifySRP6Login(this.Username, password, salt, verifier); - } - public bool VerifySRP6Login(string username, string password, byte[] salt, byte[] verifier) - { - // re-calculate the verifier using the provided username + password and the stored salt - byte[] checkVerifier = CalculateVerifier(username, password, salt); - Console.WriteLine($"{Encoding.ASCII.GetString(verifier)} {verifier.Length} bytes\n{Encoding.ASCII.GetString(checkVerifier)} {checkVerifier.Length} bytes"); - Console.WriteLine($"DB {new BigInteger(verifier)}\nTC {new BigInteger(CalculateVerifier(username, password, salt))}"); - // compare it against the stored verifier - return verifier.SequenceEqual(checkVerifier.Reverse().ToArray()); - } - public byte[] Hash(byte[] componentOne, byte[] componentTwo) - { - if (componentOne == null) throw new ArgumentNullException(nameof(componentOne)); - if (componentTwo == null) throw new ArgumentNullException(nameof(componentTwo)); - return Hash(componentOne.Concat(componentTwo).ToArray()); - } - public byte[] Hash(byte[] bytes) - { - if (bytes == null) throw new ArgumentNullException(nameof(bytes)); - - //WoW expects non-secure SHA1 hashing. SRP6 is deprecated too. We need to do it anyway - using (SHA1 shaProvider = SHA1.Create()) - { - return shaProvider.ComputeHash(bytes); - } - } - public byte[] CalculateVerifier(string username, string password, byte[] salt) - { - using (SHA1 shaProvider = SHA1.Create()) - { - if (BitConverter.IsLittleEndian) - { - return BigInteger.ModPow( - g, - new BigInteger(Hash(salt, Hash(Encoding.UTF8.GetBytes($"{username.ToUpper()}:{password.ToUpper()}")))), - N - ).ToByteArray(); - } - else - { - return BigInteger.ModPow( - g, - new BigInteger(Hash(salt, Hash(Encoding.UTF8.GetBytes($"{username.ToUpper()}:{password.ToUpper()}")).Reverse().ToArray())), - N - ).ToByteArray(); - } - } - + return Framework.Cryptography.SRP6.CheckLogin(this.Username, password, salt, verifier); } } diff --git a/Extensions.cs b/Extensions.cs new file mode 100644 index 0000000..24c1740 --- /dev/null +++ b/Extensions.cs @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2012-2020 CypherCore + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Runtime.CompilerServices; + +namespace System +{ + public static class Extensions + { + public static bool HasAnyFlag(this T value, T flag) where T : struct + { + long lValue = Convert.ToInt64(value); + long lFlag = Convert.ToInt64(flag); + return (lValue & lFlag) != 0; + } + + public static string ToHexString(this byte[] byteArray, bool reverse = false) + { + if (reverse) + return byteArray.Reverse().Aggregate("", (current, b) => current + b.ToString("X2")); + else + return byteArray.Aggregate("", (current, b) => current + b.ToString("X2")); + } + + public static byte[] ToByteArray(this string str) + { + str = str.Replace(" ", String.Empty); + + var res = new byte[str.Length / 2]; + for (int i = 0; i < res.Length; ++i) + { + string temp = String.Concat(str[i * 2], str[i * 2 + 1]); + res[i] = Convert.ToByte(temp, 16); + } + return res; + } + + public static byte[] ToByteArray(this string value, char separator) + { + return Array.ConvertAll(value.Split(separator), byte.Parse); + } + + static uint LeftRotate(this uint value, int shiftCount) + { + return (value << shiftCount) | (value >> (0x20 - shiftCount)); + } + + public static byte[] GenerateRandomKey(this byte[] s, int length) + { + var random = new Random((int)((uint)(Guid.NewGuid().GetHashCode() ^ 1 >> 89 << 2 ^ 42)).LeftRotate(13)); + var key = new byte[length]; + + for (int i = 0; i < length; i++) + { + int randValue; + + do + { + randValue = (int)((uint)random.Next(0xFF)).LeftRotate(1) ^ i; + } while (randValue > 0xFF && randValue <= 0); + + key[i] = (byte)randValue; + } + + return key; + } + + public static bool Compare(this byte[] b, byte[] b2) + { + for (int i = 0; i < b2.Length; i++) + if (b[i] != b2[i]) + return false; + + return true; + } + + public static byte[] Combine(this byte[] data, params byte[][] pData) + { + var combined = data; + + foreach (var arr in pData) + { + var currentSize = combined.Length; + + Array.Resize(ref combined, currentSize + arr.Length); + + Buffer.BlockCopy(arr, 0, combined, currentSize, arr.Length); + } + + return combined; + } + + public static object[] Combine(this object[] data, params object[][] pData) + { + var combined = data; + + foreach (var arr in pData) + { + var currentSize = combined.Length; + + Array.Resize(ref combined, currentSize + arr.Length); + + Array.Copy(arr, 0, combined, currentSize, arr.Length); + } + + return combined; + } + + public static void Swap(ref T left, ref T right) + { + T temp = left; + left = right; + right = temp; + } + + public static uint[] SerializeObject(this T obj) + { + //if (obj.GetType()() == null) + //return null; + + var size = Marshal.SizeOf(typeof(T)); + var ptr = Marshal.AllocHGlobal(size); + byte[] array = new byte[size]; + + Marshal.StructureToPtr(obj, ptr, true); + Marshal.Copy(ptr, array, 0, size); + + Marshal.FreeHGlobal(ptr); + + uint[] result = new uint[size / 4]; + Buffer.BlockCopy(array, 0, result, 0, array.Length); + + return result; + } + + public static List DeserializeObjects(this ICollection data) + { + List list = new List(); + + if (data.Count == 0) + return list; + + if (typeof(T).GetCustomAttribute() == null) + return list; + + byte[] result = new byte[data.Count * sizeof(uint)]; + Buffer.BlockCopy(data.ToArray(), 0, result, 0, result.Length); + + var typeSize = Marshal.SizeOf(typeof(T)); + var objCount = data.Count / (typeSize / sizeof(uint)); + + for (var i = 0; i < objCount; ++i) + { + var ptr = Marshal.AllocHGlobal(typeSize); + Marshal.Copy(result, typeSize * i, ptr, typeSize); + list.Add((T)Marshal.PtrToStructure(ptr, typeof(T))); + Marshal.FreeHGlobal(ptr); + } + + return list; + } + + #region Strings + public static bool IsEmpty(this string str) + { + return string.IsNullOrEmpty(str); + } + + public static T ToEnum(this string str) where T : struct + { + T value; + if (!Enum.TryParse(str, out value)) + return default; + + return value; + } + + public static string ConvertFormatSyntax(this string str) + { + string pattern = @"(%\W*\d*[a-zA-Z]*)"; + + int count = 0; + string result = Regex.Replace(str, pattern, m => string.Concat("{", count++, "}")); + + return result; + } + + public static bool Like(this string toSearch, string toFind) + { + return toSearch.ToLower().Contains(toFind.ToLower()); + } + + public static bool IsNumber(this string str) + { + double value; + return double.TryParse(str, out value); + } + + public static int GetByteCount(this string str) + { + if (str.IsEmpty()) + return 0; + + return Encoding.UTF8.GetByteCount(str); + } + + public static bool isExtendedLatinCharacter(char wchar) + { + if (isBasicLatinCharacter(wchar)) + return true; + if (wchar >= 0x00C0 && wchar <= 0x00D6) // LATIN CAPITAL LETTER A WITH GRAVE - LATIN CAPITAL LETTER O WITH DIAERESIS + return true; + if (wchar >= 0x00D8 && wchar <= 0x00DE) // LATIN CAPITAL LETTER O WITH STROKE - LATIN CAPITAL LETTER THORN + return true; + if (wchar == 0x00DF) // LATIN SMALL LETTER SHARP S + return true; + if (wchar >= 0x00E0 && wchar <= 0x00F6) // LATIN SMALL LETTER A WITH GRAVE - LATIN SMALL LETTER O WITH DIAERESIS + return true; + if (wchar >= 0x00F8 && wchar <= 0x00FE) // LATIN SMALL LETTER O WITH STROKE - LATIN SMALL LETTER THORN + return true; + if (wchar >= 0x0100 && wchar <= 0x012F) // LATIN CAPITAL LETTER A WITH MACRON - LATIN SMALL LETTER I WITH OGONEK + return true; + if (wchar == 0x1E9E) // LATIN CAPITAL LETTER SHARP S + return true; + return false; + } + + public static bool isBasicLatinCharacter(char wchar) + { + if (wchar >= 'a' && wchar <= 'z') // LATIN SMALL LETTER A - LATIN SMALL LETTER Z + return true; + if (wchar >= 'A' && wchar <= 'Z') // LATIN CAPITAL LETTER A - LATIN CAPITAL LETTER Z + return true; + return false; + } + #endregion + + #region BinaryReader + public static string ReadCString(this BinaryReader reader) + { + byte num; + List temp = new List(); + + while ((num = reader.ReadByte()) != 0) + temp.Add(num); + + return Encoding.UTF8.GetString(temp.ToArray()); + } + + public static string ReadString(this BinaryReader reader, int count) + { + var array = reader.ReadBytes(count); + return Encoding.ASCII.GetString(array); + } + + public static string ReadStringFromChars(this BinaryReader reader, int count) + { + return new string(reader.ReadChars(count)); + } + + + public static T Read(this BinaryReader reader) where T : struct + { + byte[] result = reader.ReadBytes(Unsafe.SizeOf()); + + return Unsafe.ReadUnaligned(ref result[0]); + } + #endregion + } +} \ No newline at end of file diff --git a/SRP6.cs b/SRP6.cs new file mode 100644 index 0000000..4f562be --- /dev/null +++ b/SRP6.cs @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012-2020 CypherCore + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Numerics; +using System.Security.Cryptography; +using System.Text; + +namespace Framework.Cryptography +{ + public class SRP6 + { + static SHA1 _sha1; + static BigInteger _g; + static BigInteger _N; + + static SRP6() + { + _sha1 = new SHA1Managed(); + _g = new BigInteger(7); + _N = new BigInteger(new byte[] + { + 0x89, 0x4B, 0x64, 0x5E, 0x89, 0xE1, 0x53, 0x5B, 0xBD, 0xAD, 0x5B, 0x8B, 0x29, 0x06, 0x50, 0x53, + 0x08, 0x01, 0xB1, 0x8E, 0xBF, 0xBF, 0x5E, 0x8F, 0xAB, 0x3C, 0x82, 0x87, 0x2A, 0x3E, 0x9B, 0xB7, + }, true, true); + } + + public static (byte[] Salt, byte[] Verifier) MakeRegistrationData(string username, string password) + { + var salt = new byte[0].GenerateRandomKey(32); // random salt + return (salt, CalculateVerifier(username, password, salt)); + } + + [Obsolete] + public static (byte[] Salt, byte[] Verifier) MakeRegistrationDataFromHash(byte[] hash) + { + var salt = new byte[0].GenerateRandomKey(32); // random salt + return (salt, CalculateVerifierFromHash(hash, salt)); + } + + public static byte[] CalculateVerifier(string username, string password, byte[] salt) + { + // v = g ^ H(s || H(u || ':' || p)) mod N + return CalculateVerifierFromHash(_sha1.ComputeHash(Encoding.UTF8.GetBytes(username.ToUpperInvariant() + ":" + password.ToUpperInvariant())), salt); + } + + // merge this into CalculateVerifier once the sha_pass hack finally gets nuked from orbit + public static byte[] CalculateVerifierFromHash(byte[] hash, byte[] salt) + { + // v = BigInteger.ModPow(gBN, x, BN); + return BigInteger.ModPow(_g, new BigInteger(_sha1.ComputeHash(salt.Combine(hash)), true), _N).ToByteArray(); + } + + public static bool CheckLogin(string username, string password, byte[] salt, byte[] verifier) + { + return verifier.Compare(CalculateVerifier(username, password, salt)); + } + } +} \ No newline at end of file