Add IMasterKeyService

This commit is contained in:
2021-06-06 20:06:04 +02:00
parent 7a1f2946fc
commit efbac54600
2 changed files with 153 additions and 0 deletions

View 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);
}
}

View 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();
}
}
}