Add IMasterKeyService
This commit is contained in:
21
Akari.Prototype.Server/Services/IMasterKeyService.cs
Normal file
21
Akari.Prototype.Server/Services/IMasterKeyService.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Akari.Prototype.Server.Services
|
||||
{
|
||||
public interface IMasterKeyService
|
||||
{
|
||||
public bool IsLoggedIn { get; }
|
||||
|
||||
public bool CheckConfig();
|
||||
|
||||
public bool Login(string password);
|
||||
|
||||
public bool TryGetKey(out AesGcm key);
|
||||
|
||||
public bool CreatePassword(string password);
|
||||
}
|
||||
}
|
||||
132
Akari.Prototype.Server/Services/MasterKeyService.cs
Normal file
132
Akari.Prototype.Server/Services/MasterKeyService.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user