217 lines
6.8 KiB
C#
217 lines
6.8 KiB
C#
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<ApplicationsManager> _logger;
|
|
private readonly IKeyManager _keyManager;
|
|
private readonly IAuthManager _authManager;
|
|
private readonly IMasterKeyService _masterKeyService;
|
|
private readonly AkariPath _akariPath;
|
|
|
|
private IDictionary<string, Application> _applications;
|
|
|
|
public ApplicationsManager(ILogger<ApplicationsManager> 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<string, Application>();
|
|
|
|
File.WriteAllText(path, JsonSerializer.Serialize(_applications.Values));
|
|
}
|
|
else
|
|
{
|
|
_applications = JsonSerializer.Deserialize<ICollection<Application>>(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<string>());
|
|
|
|
_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<Application> GetEnumerator()
|
|
{
|
|
return _applications.Values.GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
}
|
|
}
|