void.cat/VoidCat/Model/Extensions.cs
2022-02-24 23:05:33 +00:00

164 lines
4.6 KiB
C#

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
namespace VoidCat.Model;
public static class Extensions
{
public static Guid? GetUserId(this HttpContext context)
{
var claimSub = context?.User?.Claims?.FirstOrDefault(a => a.Type == ClaimTypes.NameIdentifier)?.Value;
return Guid.TryParse(claimSub, out var g) ? g : null;
}
public static Guid FromBase58Guid(this string base58)
{
var enc = new NBitcoin.DataEncoders.Base58Encoder();
return new Guid(enc.DecodeData(base58));
}
public static string ToBase58(this Guid id)
{
var enc = new NBitcoin.DataEncoders.Base58Encoder();
return enc.EncodeData(id.ToByteArray());
}
public static string? GetHeader(this IHeaderDictionary headers, string key)
{
var h = headers
.FirstOrDefault(a => a.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase));
return !string.IsNullOrEmpty(h.Value.ToString()) ? h.Value.ToString() : default;
}
public static string ToHex(this byte[] data)
{
return BitConverter.ToString(data).Replace("-", string.Empty).ToLower();
}
private static int HexToInt(char c)
{
switch (c)
{
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
throw new FormatException("Unrecognized hex char " + c);
}
}
private static readonly byte[,] ByteLookup = new byte[,]
{
// low nibble
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
},
// high nibble
{
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0
}
};
private static byte[] FromHex(this string input)
{
var result = new byte[(input.Length + 1) >> 1];
var lastCell = result.Length - 1;
var lastChar = input.Length - 1;
for (var i = 0; i < input.Length; i++)
{
result[lastCell - (i >> 1)] |= ByteLookup[i & 1, HexToInt(input[lastChar - i])];
}
return result;
}
public static string HashPassword(this string password)
{
return password.HashPassword("pbkdf2");
}
public static string HashPassword(this string password, string algo, string? saltHex = null)
{
var bytes = Encoding.UTF8.GetBytes(password);
switch (algo)
{
case "sha256":
{
var hash = SHA256.Create().ComputeHash(bytes);
return $"sha256:{hash.ToHex()}";
}
case "sha512":
{
var hash = SHA512.Create().ComputeHash(bytes);
return $"sha512:{hash.ToHex()}";
}
case "pbkdf2":
{
const int saltSize = 32;
const int iterations = 310_000;
var salt = new byte[saltSize];
if (saltHex == default)
{
RandomNumberGenerator.Fill(salt);
}
else
{
salt = saltHex.FromHex();
}
var pbkdf2 = new Rfc2898DeriveBytes(bytes, salt, iterations);
return $"pbkdf2:{salt.ToHex()}:{pbkdf2.GetBytes(salt.Length).ToHex()}";
}
}
throw new ArgumentException("Unknown algo", nameof(algo));
}
public static bool CheckPassword(this PrivateVoidUser vu, string password)
{
var hashParts = vu.PasswordHash.Split(":");
return vu.PasswordHash == password.HashPassword(hashParts[0], hashParts.Length == 3 ? hashParts[1] : null);
}
}