using Akari.Prototype.Server.Models; using Akari.Prototype.Server.Utils; using Isopoh.Cryptography.Argon2; using Microsoft.Extensions.Logging; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text.Json; using System.Threading.Tasks; namespace Akari.Prototype.Server.Services { public sealed class ApplicationsManager : IApplicationsManager { public const string ApplicationsPath = "applications.json"; public const int ApplicationTokenLength = 18; private readonly ILogger _logger; private readonly IKeyManager _keyManager; private readonly IAuthManager _authManager; private readonly IMasterKeyService _masterKeyService; private readonly AkariPath _akariPath; private IDictionary _applications; public ApplicationsManager(ILogger logger, IKeyManager keyManager, IAuthManager authManager, IMasterKeyService masterKeyService, AkariPath akariPath) { _logger = logger; _keyManager = keyManager; _authManager = authManager; _masterKeyService = masterKeyService; _akariPath = akariPath; LoadApplications(); } private void LoadApplications() { var path = _akariPath.GetPath(ApplicationsPath); if (!File.Exists(path)) { _applications = new Dictionary(); File.WriteAllText(path, JsonSerializer.Serialize(_applications.Values)); } else { _applications = JsonSerializer.Deserialize>(File.ReadAllText(path)).ToDictionary(a => a.Name); } } private void SaveApplications() { var path = _akariPath.GetPath(ApplicationsPath); File.WriteAllText(path, JsonSerializer.Serialize(_applications.Values)); } public bool TryCreate(string applicationName, out string applicationToken) { if (_applications.ContainsKey(applicationName)) { _logger.LogDebug($"Can't create '{applicationName}' as it already exists"); applicationToken = null; return false; } if (!_masterKeyService.IsLoggedIn) { _logger.LogDebug("Can't create an application if master service is not logged in"); applicationToken = null; return false; } // Application token var tokenData = new byte[ApplicationTokenLength]; RandomNumberGenerator.Fill(tokenData); var hash = Security.NewArgon2idHash(tokenData); var application = new Application(applicationName, hash, new List()); _applications[applicationName] = application; applicationToken = Convert.ToBase64String(tokenData); // Application key _keyManager.Create(applicationName); SaveApplications(); _logger.LogDebug($"Application '{applicationName}' created"); return true; } public bool Contains(string applicationName) { return _applications.ContainsKey(applicationName); } public bool IsFingerprintRegistered(string applicationName, string fingerprintName) => _applications.TryGetValue(applicationName, out var application) && application.Fingerprints.Contains(fingerprintName); public bool Remove(string applicationName) { if (!_applications.TryGetValue(applicationName, out var application)) { _logger.LogDebug($"Can't remove non existing application: {applicationName}"); return false; } // Clear keys _keyManager.Clear(application); _applications.Remove(application.Name); SaveApplications(); return true; } public bool AddFingerprint(string applicationName, string fingerprintName) { if (IsFingerprintRegistered(applicationName, fingerprintName)) { _logger.LogDebug($"'{fingerprintName}' is already registered for '{applicationName}'"); return false; } // Verify master password if (!_masterKeyService.IsLoggedIn) { _logger.LogDebug($"Can't add a fingerprint for {applicationName} if master service is not logged in"); } // Try get fingerprint key if (!_authManager.TryGetKey(fingerprintName, out var key)) { _logger.LogDebug($"Fingerprint '{fingerprintName}' has not been auth"); return false; } // Encrypt key _keyManager.AddFingerprint(applicationName, fingerprintName, key); _applications[applicationName].Fingerprints.Add(fingerprintName); SaveApplications(); return true; } public bool VerifyToken(string applicationName, string applicationToken) { if (!_applications.TryGetValue(applicationName, out var application)) { return false; } return Argon2.Verify(application.TokenHash, Convert.FromBase64String(applicationToken)); } public bool TryRetrieveKey(string applicationName, string applicationToken, out AesGcm applicationKey) { applicationKey = null; if (!_applications.TryGetValue(applicationName, out var application)) { _logger.LogDebug($"Tried to retrieve key for non existing application: {applicationName}"); return false; } if (!VerifyToken(applicationName, applicationToken)) { _logger.LogDebug($"Can't retrieve '{applicationName}' key, wrong token"); return false; } if (!_authManager.TryGetKey(application.Fingerprints, out var fingerprintName, out var fingerprintKey)) { _logger.LogDebug($"Can't retrieve '{applicationName}' key, no fingerprint auth found"); return false; } applicationKey = _keyManager.RetrieveKey(applicationName, fingerprintName, fingerprintKey); return true; } public IEnumerator GetEnumerator() { return _applications.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }