2022-02-24 23:05:33 +00:00
|
|
|
using System.Security.Claims;
|
2022-02-21 22:35:06 +00:00
|
|
|
using System.Security.Cryptography;
|
|
|
|
using System.Text;
|
2022-03-01 16:48:42 +00:00
|
|
|
using Amazon;
|
|
|
|
using Amazon.Runtime;
|
|
|
|
using Amazon.S3;
|
2022-03-08 13:47:42 +00:00
|
|
|
using VoidCat.Model.Exceptions;
|
2022-02-21 22:35:06 +00:00
|
|
|
|
2022-01-25 23:39:51 +00:00
|
|
|
namespace VoidCat.Model;
|
|
|
|
|
|
|
|
public static class Extensions
|
|
|
|
{
|
2022-03-01 16:48:42 +00:00
|
|
|
public static AmazonS3Client CreateClient(this S3BlobConfig c)
|
|
|
|
{
|
|
|
|
return new AmazonS3Client(new BasicAWSCredentials(c.AccessKey, c.SecretKey),
|
|
|
|
new AmazonS3Config
|
|
|
|
{
|
|
|
|
RegionEndpoint = !string.IsNullOrEmpty(c.Region) ? RegionEndpoint.GetBySystemName(c.Region) : null,
|
|
|
|
ServiceURL = c.ServiceUrl?.ToString(),
|
|
|
|
UseHttp = c.ServiceUrl?.Scheme == "http",
|
|
|
|
ForcePathStyle = true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-02-24 23:05:33 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-03-04 20:05:01 +00:00
|
|
|
|
2022-03-02 11:37:15 +00:00
|
|
|
public static IEnumerable<string>? GetUserRoles(this HttpContext context)
|
|
|
|
{
|
|
|
|
return context?.User?.Claims?.Where(a => a.Type == ClaimTypes.Role)
|
|
|
|
?.Select(a => a?.Value!);
|
|
|
|
}
|
2022-02-24 23:05:33 +00:00
|
|
|
|
2022-03-02 11:37:15 +00:00
|
|
|
public static bool IsRole(this HttpContext context, string role)
|
|
|
|
{
|
|
|
|
return GetUserRoles(context)?.Contains(role) ?? false;
|
|
|
|
}
|
2022-03-04 20:05:01 +00:00
|
|
|
|
2022-01-25 23:39:51 +00:00
|
|
|
public static Guid FromBase58Guid(this string base58)
|
|
|
|
{
|
|
|
|
var enc = new NBitcoin.DataEncoders.Base58Encoder();
|
2022-03-08 13:47:42 +00:00
|
|
|
var guidBytes = enc.DecodeData(base58);
|
|
|
|
if (guidBytes.Length != 16) throw new VoidInvalidIdException(base58);
|
|
|
|
return new Guid(guidBytes);
|
2022-01-25 23:39:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static string ToBase58(this Guid id)
|
|
|
|
{
|
|
|
|
var enc = new NBitcoin.DataEncoders.Base58Encoder();
|
|
|
|
return enc.EncodeData(id.ToByteArray());
|
|
|
|
}
|
2022-02-10 22:22:34 +00:00
|
|
|
|
|
|
|
public static string? GetHeader(this IHeaderDictionary headers, string key)
|
|
|
|
{
|
2022-02-21 12:54:57 +00:00
|
|
|
var h = headers
|
|
|
|
.FirstOrDefault(a => a.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase));
|
2022-02-21 22:35:06 +00:00
|
|
|
|
2022-02-21 12:54:57 +00:00
|
|
|
return !string.IsNullOrEmpty(h.Value.ToString()) ? h.Value.ToString() : default;
|
2022-02-10 22:22:34 +00:00
|
|
|
}
|
2022-02-21 22:35:06 +00:00
|
|
|
|
2022-06-10 20:42:36 +00:00
|
|
|
public static bool CanEdit(this SecretVoidFileMeta file, Guid? editSecret)
|
2022-03-15 10:39:36 +00:00
|
|
|
{
|
2022-06-10 20:42:36 +00:00
|
|
|
return file.EditSecret == editSecret;
|
2022-03-15 10:39:36 +00:00
|
|
|
}
|
|
|
|
|
2022-02-21 22:35:06 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:48:42 +00:00
|
|
|
public static byte[] FromHex(this string input)
|
2022-02-21 22:35:06 +00:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
2022-03-04 20:05:01 +00:00
|
|
|
return password.Hash("pbkdf2");
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
|
|
|
|
2022-03-04 20:05:01 +00:00
|
|
|
public static string Hash(this string password, string algo, string? saltHex = null)
|
2022-02-21 22:35:06 +00:00
|
|
|
{
|
|
|
|
var bytes = Encoding.UTF8.GetBytes(password);
|
2022-03-04 20:05:01 +00:00
|
|
|
return Hash(bytes, algo, saltHex);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string Hash(this byte[] bytes, string algo, string? saltHex = null)
|
|
|
|
{
|
2022-02-21 22:35:06 +00:00
|
|
|
switch (algo)
|
|
|
|
{
|
2022-03-04 20:05:01 +00:00
|
|
|
case "md5":
|
|
|
|
{
|
|
|
|
var hash = MD5.Create().ComputeHash(bytes);
|
|
|
|
return $"md5:{hash.ToHex()}";
|
|
|
|
}
|
|
|
|
case "sha1":
|
|
|
|
{
|
|
|
|
var hash = SHA1.Create().ComputeHash(bytes);
|
|
|
|
return $"sha1:{hash.ToHex()}";
|
|
|
|
}
|
2022-02-21 22:35:06 +00:00
|
|
|
case "sha256":
|
|
|
|
{
|
|
|
|
var hash = SHA256.Create().ComputeHash(bytes);
|
2022-02-22 14:20:31 +00:00
|
|
|
return $"sha256:{hash.ToHex()}";
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
|
|
|
case "sha512":
|
|
|
|
{
|
|
|
|
var hash = SHA512.Create().ComputeHash(bytes);
|
2022-02-22 14:20:31 +00:00
|
|
|
return $"sha512:{hash.ToHex()}";
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
|
|
|
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);
|
2022-02-22 14:20:31 +00:00
|
|
|
return $"pbkdf2:{salt.ToHex()}:{pbkdf2.GetBytes(salt.Length).ToHex()}";
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new ArgumentException("Unknown algo", nameof(algo));
|
|
|
|
}
|
|
|
|
|
2022-02-27 13:54:25 +00:00
|
|
|
public static bool CheckPassword(this InternalVoidUser vu, string password)
|
2022-02-21 22:35:06 +00:00
|
|
|
{
|
2022-06-08 16:17:53 +00:00
|
|
|
var hashParts = vu.Password.Split(":");
|
|
|
|
return vu.Password == password.Hash(hashParts[0], hashParts.Length == 3 ? hashParts[1] : null);
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
2022-03-01 16:48:42 +00:00
|
|
|
}
|