diff --git a/Akari.Prototype.Server/Options/AkariOptions.cs b/Akari.Prototype.Server/Options/AkariOptions.cs new file mode 100644 index 0000000..c6156a4 --- /dev/null +++ b/Akari.Prototype.Server/Options/AkariOptions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Options +{ + public class AkariOptions + { + public const string FilePath = "akari.json"; + public const string SectionPath = "Akari"; + + [Required] + public string DataPath { get; set; } + } +} diff --git a/Akari.Prototype.Server/Program.cs b/Akari.Prototype.Server/Program.cs index e5e2f2b..0152a79 100644 --- a/Akari.Prototype.Server/Program.cs +++ b/Akari.Prototype.Server/Program.cs @@ -1,7 +1,9 @@ using Akari.Prototype.Server.Options; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.IO; @@ -15,7 +17,17 @@ namespace Akari.Prototype.Server { public static void Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + var host = CreateHostBuilder(args).Build(); + + var services = host.Services; + var akariOptions = services.GetRequiredService>().Value; + + if (!Directory.Exists(akariOptions.DataPath)) + { + Directory.CreateDirectory(akariOptions.DataPath); + } + + host.Run(); } // Additional configuration is required to successfully run gRPC on macOS. diff --git a/Akari.Prototype.Server/Services/AuthManager.cs b/Akari.Prototype.Server/Services/AuthManager.cs new file mode 100644 index 0000000..aec60b1 --- /dev/null +++ b/Akari.Prototype.Server/Services/AuthManager.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Services +{ + public class AuthManager : IAuthManager + { + private readonly ILogger _logger; + private readonly IKeyManager _keyManager; + + public AuthManager(ILogger logger, IKeyManager keyManager) + { + _logger = logger; + _keyManager = keyManager; + } + + public void Auth(byte[] token, string name) + { + + } + } +} diff --git a/Akari.Prototype.Server/Services/FingerprintManager.cs b/Akari.Prototype.Server/Services/FingerprintManager.cs new file mode 100644 index 0000000..77dfd3d --- /dev/null +++ b/Akari.Prototype.Server/Services/FingerprintManager.cs @@ -0,0 +1,71 @@ +using Akari.Prototype.Server.Options; +using Akari.Prototype.Server.Utils; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Services +{ + public class FingerprintManager : IFingerprintManager + { + public const string FingerprintsPath = "fingerprints.json"; + + private readonly ILogger _logger; + private readonly IOptions _akariOptions; + private readonly IAuthManager _authManager; + + /// + /// Map token hash to user-friendly name + /// + private IDictionary _fingerprintNames; + + public FingerprintManager(ILogger logger, IOptions akariOptions, + IAuthManager authManager) + { + _logger = logger; + _akariOptions = akariOptions; + _authManager = authManager; + + LoadFingerprints(); + } + + private void LoadFingerprints() + { + var path = AkariPath.GetPath(FingerprintsPath); + + // Create new + if (!File.Exists(path)) + { + _fingerprintNames = new Dictionary(); + + File.WriteAllText(path, JsonSerializer.Serialize(_fingerprintNames)); + } + // Load + else + { + _fingerprintNames = JsonSerializer.Deserialize>(File.ReadAllText(path)); + } + } + + public async Task VerifyToken(byte[] token) + { + var hash = Security.Argon2idHash(token); + + _logger.LogDebug($"Verifying hash: {hash}"); + + if (!_fingerprintNames.TryGetValue(hash, out var name)) + { + _logger.LogDebug($"Unknown hash, aborting: {hash}"); + + return; + } + + _authManager.Auth(token, name); + } + } +} diff --git a/Akari.Prototype.Server/Services/IAuthManager.cs b/Akari.Prototype.Server/Services/IAuthManager.cs new file mode 100644 index 0000000..4aa31b9 --- /dev/null +++ b/Akari.Prototype.Server/Services/IAuthManager.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Services +{ + public interface IAuthManager + { + void Auth(byte[] token, string name); + } +} diff --git a/Akari.Prototype.Server/Services/IFingerprintManager.cs b/Akari.Prototype.Server/Services/IFingerprintManager.cs new file mode 100644 index 0000000..4c06fdd --- /dev/null +++ b/Akari.Prototype.Server/Services/IFingerprintManager.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Services +{ + public interface IFingerprintManager + { + Task VerifyToken(byte[] vs); + } +} diff --git a/Akari.Prototype.Server/Services/IKeyManager.cs b/Akari.Prototype.Server/Services/IKeyManager.cs new file mode 100644 index 0000000..d8d9d15 --- /dev/null +++ b/Akari.Prototype.Server/Services/IKeyManager.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Services +{ + public interface IKeyManager + { + } +} diff --git a/Akari.Prototype.Server/Services/KeyManager.cs b/Akari.Prototype.Server/Services/KeyManager.cs new file mode 100644 index 0000000..6aa06be --- /dev/null +++ b/Akari.Prototype.Server/Services/KeyManager.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Services +{ + public class KeyManager : IKeyManager + { + } +} diff --git a/Akari.Prototype.Server/Services/TcpProviderService.cs b/Akari.Prototype.Server/Services/TcpProviderService.cs index 7ab1bfd..d5445ad 100644 --- a/Akari.Prototype.Server/Services/TcpProviderService.cs +++ b/Akari.Prototype.Server/Services/TcpProviderService.cs @@ -17,20 +17,23 @@ namespace Akari.Prototype.Server.Services public class TcpProviderService : BackgroundService { public const int BufferLength = 4096; - public const int MaxTokenLength = 192; + public const int MaxTokenLength = 24; public const int ReceiveTimeout = 500_000; private readonly ILogger _logger; private readonly TcpProviderOptions _options; private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly IFingerprintManager _fingerprintManager; private Task _backgroundTask; private TcpListener _listener; - public TcpProviderService(ILogger logger, IOptions options, IHostApplicationLifetime hostApplicationLifetime) + public TcpProviderService(ILogger logger, IOptions options, + IHostApplicationLifetime hostApplicationLifetime, IFingerprintManager fingerprintManager) { _logger = logger; _options = options.Value; _hostApplicationLifetime = hostApplicationLifetime; + _fingerprintManager = fingerprintManager; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -117,8 +120,9 @@ namespace Akari.Prototype.Server.Services position += read; } - // TODO: Send token to AuthManager that will compare it to stored hash using Argon2 _logger.LogDebug($"Received token: {Convert.ToBase64String(data[..position].Span)}"); + + await _fingerprintManager.VerifyToken(data[..position].ToArray()); } } } diff --git a/Akari.Prototype.Server/Startup.cs b/Akari.Prototype.Server/Startup.cs index 8d3662f..80693d6 100644 --- a/Akari.Prototype.Server/Startup.cs +++ b/Akari.Prototype.Server/Startup.cs @@ -32,6 +32,10 @@ namespace Akari.Prototype.Server .Bind(Configuration.GetSection(TcpProviderOptions.SectionPath)) .ValidateDataAnnotations(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddHostedService(); } diff --git a/Akari.Prototype.Server/Utils/AkariPath.cs b/Akari.Prototype.Server/Utils/AkariPath.cs new file mode 100644 index 0000000..424d597 --- /dev/null +++ b/Akari.Prototype.Server/Utils/AkariPath.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Utils +{ + public static class AkariPath + { + public const string BasePath = "Data"; + + public static string GetPath(string path) => Path.Combine(BasePath, path); + } +} diff --git a/Akari.Prototype.Server/Utils/IPAddressAttribute.cs b/Akari.Prototype.Server/Utils/IPAddressAttribute.cs index 80702e1..09518b9 100644 --- a/Akari.Prototype.Server/Utils/IPAddressAttribute.cs +++ b/Akari.Prototype.Server/Utils/IPAddressAttribute.cs @@ -11,7 +11,7 @@ namespace Akari.Prototype.Server.Utils { public IPAddressAttribute() : base("Must be a valid IP address") { - + } public override bool IsValid(object value) diff --git a/Akari.Prototype.Server/Utils/Security.cs b/Akari.Prototype.Server/Utils/Security.cs new file mode 100644 index 0000000..46f68a2 --- /dev/null +++ b/Akari.Prototype.Server/Utils/Security.cs @@ -0,0 +1,35 @@ +using Isopoh.Cryptography.Argon2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; + +namespace Akari.Prototype.Server.Utils +{ + public static class Security + { + public const int HashLength = 32; + public const int SaltLength = 12; + + public static string Argon2idHash(byte[] password) + { + var salt = new byte[8]; + + RandomNumberGenerator.Fill(salt); + + var config = new Argon2Config() + { + HashLength = 32, + Lanes = Environment.ProcessorCount / 2, + Threads = Environment.ProcessorCount / 2, + Password = password, + Salt = salt, + Type = Argon2Type.HybridAddressing, + Version = Argon2Version.Nineteen + }; + + return Argon2.Hash(config); + } + } +} diff --git a/Akari.Prototype.Server/akari.json b/Akari.Prototype.Server/akari.json new file mode 100644 index 0000000..6dadfaa --- /dev/null +++ b/Akari.Prototype.Server/akari.json @@ -0,0 +1,6 @@ +{ + "Akari": + { + "DataPath": "Data" + } +}