diff --git a/SphereOS/Crypto/Sha256.cs b/SphereOS/Crypto/Sha256.cs new file mode 100644 index 0000000..84e2e64 --- /dev/null +++ b/SphereOS/Crypto/Sha256.cs @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2010 Yuri K. Schlesner + * + * 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. + */ + +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; + +namespace SphereOS.Crypto +{ + internal class Sha256 + { + private static readonly uint[] K = new uint[64] { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 + }; + + private static uint ROTL(uint x, byte n) + { + if (n >= 32) throw new ArgumentException("n"); + return (x << n) | (x >> (32 - n)); + } + + private static uint ROTR(uint x, byte n) + { + if (n >= 32) throw new ArgumentException("n"); + return (x >> n) | (x << (32 - n)); + } + + private static uint Ch(uint x, uint y, uint z) + { + return (x & y) ^ ((~x) & z); + } + + private static uint Maj(uint x, uint y, uint z) + { + return (x & y) ^ (x & z) ^ (y & z); + } + + private static uint Sigma0(uint x) + { + return ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22); + } + + private static uint Sigma1(uint x) + { + return ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25); + } + + private static uint sigma0(uint x) + { + return ROTR(x, 7) ^ ROTR(x, 18) ^ (x >> 3); + } + + private static uint sigma1(uint x) + { + return ROTR(x, 17) ^ ROTR(x, 19) ^ (x >> 10); + } + + + private uint[] H = new uint[8] { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 + }; + + private byte[] pendingBlock = new byte[64]; + private uint pendingBlockOff = 0; + private uint[] uintBuffer = new uint[16]; + + private ulong bitsProcessed = 0; + + private bool closed = false; + + private void ProcessBlock(uint[] M) + { + if (M.Length != 16) throw new ArgumentException("M"); + + // 1. Prepare the message schedule (W[t]): + uint[] W = new uint[64]; + for (int t = 0; t < 16; ++t) + { + W[t] = M[t]; + } + + for (int t = 16; t < 64; ++t) + { + W[t] = sigma1(W[t - 2]) + W[t - 7] + sigma0(W[t - 15]) + W[t - 16]; + } + + // 2. Initialize the eight working variables with the (i-1)-st hash value: + uint a = H[0], + b = H[1], + c = H[2], + d = H[3], + e = H[4], + f = H[5], + g = H[6], + h = H[7]; + + // 3. For t=0 to 63: + for (int t = 0; t < 64; ++t) + { + uint T1 = h + Sigma1(e) + Ch(e, f, g) + K[t] + W[t]; + uint T2 = Sigma0(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + // 4. Compute the intermediate hash value H: + H[0] = a + H[0]; + H[1] = b + H[1]; + H[2] = c + H[2]; + H[3] = d + H[3]; + H[4] = e + H[4]; + H[5] = f + H[5]; + H[6] = g + H[6]; + H[7] = h + H[7]; + } + + internal void AddData(byte[] data, uint offset, uint len) + { + if (closed) + throw new InvalidOperationException("Adding data to a closed hasher."); + + if (len == 0) + return; + + bitsProcessed += len * 8; + + while (len > 0) + { + uint amount_to_copy; + + if (len < 64) + { + if (pendingBlockOff + len > 64) + amount_to_copy = 64 - pendingBlockOff; + else + amount_to_copy = len; + } + else + { + amount_to_copy = 64 - pendingBlockOff; + } + + Array.Copy(data, offset, pendingBlock, pendingBlockOff, amount_to_copy); + len -= amount_to_copy; + offset += amount_to_copy; + pendingBlockOff += amount_to_copy; + + if (pendingBlockOff == 64) + { + toUintArray(pendingBlock, uintBuffer); + ProcessBlock(uintBuffer); + pendingBlockOff = 0; + } + } + } + + internal byte[] GetHash() + { + return toByteArray(GetHashUint()); + } + + internal uint[] GetHashUint() + { + if (!closed) + { + ulong size_temp = bitsProcessed; + + AddData(new byte[1] { 0x80 }, 0, 1); + + uint available_space = 64 - pendingBlockOff; + + if (available_space < 8) + available_space += 64; + + // 0-initialized + byte[] padding = new byte[available_space]; + // Insert length ulong + for (uint i = 1; i <= 8; ++i) + { + padding[padding.Length - i] = (byte)size_temp; + size_temp >>= 8; + } + + AddData(padding, 0u, (uint)padding.Length); + + if (pendingBlockOff != 0) throw new Exception("Pending block offset should be 0."); + + closed = true; + } + + return H; + } + + private static void toUintArray(byte[] src, uint[] dest) + { + for (uint i = 0, j = 0; i < dest.Length; ++i, j += 4) + { + dest[i] = ((uint)src[j + 0] << 24) | ((uint)src[j + 1] << 16) | ((uint)src[j + 2] << 8) | ((uint)src[j + 3]); + } + } + + private static byte[] toByteArray(uint[] src) + { + byte[] dest = new byte[src.Length * 4]; + int pos = 0; + + for (int i = 0; i < src.Length; ++i) + { + dest[pos++] = (byte)(src[i] >> 24); + dest[pos++] = (byte)(src[i] >> 16); + dest[pos++] = (byte)(src[i] >> 8); + dest[pos++] = (byte)(src[i]); + } + + return dest; + } + } +} \ No newline at end of file diff --git a/SphereOS/Users/User.cs b/SphereOS/Users/User.cs index 611921b..b932962 100644 --- a/SphereOS/Users/User.cs +++ b/SphereOS/Users/User.cs @@ -1,4 +1,5 @@ -using System; +using SphereOS.Crypto; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -15,7 +16,7 @@ internal class User /// Create a user. /// /// The username. - /// The password. + /// The hashed password. /// Whether the user is an admin. internal User(string username, string password, bool admin) { @@ -35,7 +36,7 @@ internal User(string username, string password, bool admin) internal bool Admin { get; set; } = false; /// - /// The password of the user. + /// The hashed password of the user. /// internal string Password { get; set; } @@ -63,7 +64,7 @@ internal void FlushMessages() /// Whether the password is valid. internal bool Authenticate(string password) { - return password == Password; + return UserManager.HashPasswordSha256(password) == Password; } } } diff --git a/SphereOS/Users/UserManager.cs b/SphereOS/Users/UserManager.cs index b7ec8ad..830d03d 100644 --- a/SphereOS/Users/UserManager.cs +++ b/SphereOS/Users/UserManager.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using SphereOS.Commands.UsersTopic; +using SphereOS.Crypto; using SphereOS.Logging; using SphereOS.Text; @@ -41,8 +42,8 @@ internal static void Load() string password = reader.ReadString("password", section); bool admin = reader.ReadBool("admin", section); - User user = new User(username, password, admin); - Users.Add(user); + // Create the user without hashing the password, because it is already hashed on disk. + AddUser(username, password, admin, hash: false); //Util.PrintTask($"DEBUG: UserMgr loadusr {username} {admin}"); break; @@ -75,13 +76,16 @@ internal static void Load() } } - internal static User AddUser(string username, string password, bool admin) + internal static User AddUser(string username, string password, bool admin, bool hash = true) { if (GetUser(username) != null) throw new InvalidOperationException($"A user named {username} already exists!"); - User user = new User(username, password, admin); + + User user = new User(username, hash ? HashPasswordSha256(password) : password, admin); Users.Add(user); + Flush(); + if (admin) { Log.Info("UserManager", $"Admin user {username} was added."); @@ -90,6 +94,7 @@ internal static User AddUser(string username, string password, bool admin) { Log.Info("UserManager", $"User {username} was added."); } + return user; } @@ -163,5 +168,13 @@ internal static void Flush() Log.Warning("UserManager", $"Failed to flush user data: {e.Message}"); } } + + internal static string HashPasswordSha256(string password) + { + Sha256 sha256 = new Sha256(); + byte[] passwordBytesUnhashed = Encoding.Unicode.GetBytes(password); + sha256.AddData(passwordBytesUnhashed, 0, (uint)passwordBytesUnhashed.Length); + return Convert.ToBase64String(sha256.GetHash()); + } } }