using Akari.Prototype.Server.Models; using Akari.Prototype.Server.Utils; using Isopoh.Cryptography.Argon2; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Akari.Prototype.Server.Services { public sealed class AuthManager : IAuthManager, IDisposable { public const int AuthKeysLength = 256 / 8; public const string SaltsPath = "auth_salts.json"; public IEnumerable>> Pairs => _keys; private readonly ILogger _logger; private readonly IKeyManager _keyManager; private readonly AkariPath _akariPath; private IDictionary> _keys; private IDictionary _salts; public AuthManager(ILogger logger, IKeyManager keyManager, AkariPath akariPath) { _logger = logger; _keyManager = keyManager; _akariPath = akariPath; _keys = new ConcurrentDictionary>(); LoadSalts(); } private void LoadSalts() { var path = _akariPath.GetPath(SaltsPath); // Create new if (!File.Exists(path)) { _salts = new Dictionary(); File.WriteAllText(path, JsonSerializer.Serialize(_salts)); } // Load else { _salts = JsonSerializer.Deserialize>(File.ReadAllText(path)); } } private void SaveSalts() { var path = _akariPath.GetPath(SaltsPath); File.WriteAllText(path, JsonSerializer.Serialize(_salts)); } public void GenerateSalt(string name) { Span salt = Security.DefaultSaltLength <= 1024 ? stackalloc byte[Security.DefaultSaltLength] : new byte[Security.DefaultSaltLength]; RandomNumberGenerator.Fill(salt); _salts[name] = Convert.ToBase64String(salt); SaveSalts(); } public void Auth(string name, byte[] token) { // Retrieve salt if (!_salts.TryGetValue(name, out var salt)) { throw new Exception($"Can't find the salt for '{name}', try to register the fingerprint again"); } // Derive key and store it using var key = Security.Argon2idDeriveBytes(token, Convert.FromBase64String(salt), AuthKeysLength, clear: true); SetKey(name, new AesGcm(key.Buffer)); } public bool Remove(string name) { return _keys.Remove(name); } private void SetKey(string name, AesGcm aesGcm) { _logger.LogDebug($"New fingerprint auth: {name}"); if (_keys.TryGetValue(name, out var oldEntry)) { _logger.LogDebug($"Old auth were present for '{name}', clearing it"); oldEntry.Value.Dispose(); } _keys[name] = new TimedEntry(DateTime.Now, aesGcm); _logger.LogDebug($"New auth '{name}' at [{_keys[name].CreationDate}], expires at [{_keys[name].CreationDate + AuthLifetimeService.AuthLifetime}]"); } public void Dispose() { _keys.Clear(); } } }