133 lines
3.4 KiB
C#
133 lines
3.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using Akari.Prototype.Server.Utils;
|
|
using Isopoh.Cryptography.Argon2;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Akari.Prototype.Server.Services
|
|
{
|
|
public class MasterKeyService : IMasterKeyService, IDisposable
|
|
{
|
|
private record Config(string Hash, string KeySalt);
|
|
|
|
public const string ConfigPath = "master";
|
|
public const int MasterKeyLength = 256 / 8;
|
|
|
|
public bool IsLoggedIn => _key is not null;
|
|
|
|
private readonly ILogger<MasterKeyService> _logger;
|
|
private readonly AkariPath _akariPath;
|
|
|
|
private Config _config;
|
|
private AesGcm _key;
|
|
|
|
public MasterKeyService(ILogger<MasterKeyService> logger, AkariPath akariPath)
|
|
{
|
|
_logger = logger;
|
|
_akariPath = akariPath;
|
|
}
|
|
|
|
public bool CheckConfig() => File.Exists(_akariPath.GetPath(ConfigPath));
|
|
|
|
public bool Login(string password)
|
|
{
|
|
if (_key is not null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (_config is null && !LoadConfig())
|
|
{
|
|
_logger.LogDebug("Can't load config, has a master password been set?");
|
|
|
|
return false;
|
|
}
|
|
|
|
if (!Argon2.Verify(_config.Hash, password))
|
|
{
|
|
_logger.LogDebug("Wrong password");
|
|
|
|
return false;
|
|
}
|
|
|
|
using var keyBytes = Security.Argon2idDeriveBytes(Encoding.UTF8.GetBytes(password), Convert.FromBase64String(_config.KeySalt), MasterKeyLength, clear: true);
|
|
|
|
_key = new AesGcm(keyBytes.Buffer);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool TryGetKey(out AesGcm key)
|
|
{
|
|
if (!IsLoggedIn)
|
|
{
|
|
_logger.LogDebug("Can't get key, not logged in");
|
|
|
|
key = null;
|
|
|
|
return false;
|
|
}
|
|
|
|
key = _key;
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool CreatePassword(string password)
|
|
{
|
|
if (CheckConfig())
|
|
{
|
|
_logger.LogDebug("Tried to create password but it's already set.");
|
|
|
|
return false;
|
|
}
|
|
|
|
var hash = Security.NewArgon2idHash(password);
|
|
Span<byte> salt = Security.DefaultSaltLength <= 1024 ? stackalloc byte[Security.DefaultSaltLength]
|
|
: new byte[Security.DefaultSaltLength];
|
|
|
|
RandomNumberGenerator.Fill(salt);
|
|
|
|
_config = new Config(hash, Convert.ToBase64String(salt));
|
|
|
|
SaveConfig();
|
|
|
|
return Login(password);
|
|
}
|
|
|
|
private bool LoadConfig()
|
|
{
|
|
if (!CheckConfig())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string path = _akariPath.GetPath(ConfigPath);
|
|
|
|
_config = JsonSerializer.Deserialize<Config>(File.ReadAllText(path));
|
|
|
|
_logger.LogDebug("Config loaded");
|
|
|
|
return true;
|
|
}
|
|
|
|
private void SaveConfig()
|
|
{
|
|
string path = _akariPath.GetPath(ConfigPath);
|
|
|
|
File.WriteAllText(path, JsonSerializer.Serialize(_config));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_key?.Dispose();
|
|
}
|
|
}
|
|
}
|