From d7691e414b20adce0db8671ad3d14b8b49758978 Mon Sep 17 00:00:00 2001 From: Eveldee Date: Sat, 5 Jun 2021 12:20:14 +0200 Subject: [PATCH] Use random salt for each fingerprint key Salt length depended on fingerprint name length, which lead to weak salts in some cases It's now a fixed length (12 bytes) --- Akari.Prototype.Server/Program.cs | 1 + .../Services/AuthManager.cs | 56 ++++++++++++++++++- .../Services/FingerprintManager.cs | 2 + .../Services/IAuthManager.cs | 2 + .../Services/TcpProviderService.cs | 15 +++-- 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/Akari.Prototype.Server/Program.cs b/Akari.Prototype.Server/Program.cs index c5a70d7..1e5605a 100644 --- a/Akari.Prototype.Server/Program.cs +++ b/Akari.Prototype.Server/Program.cs @@ -2,6 +2,7 @@ using Akari.Prototype.Server.Cli.Commands; using Akari.Prototype.Server.Options; using Akari.Prototype.Server.Utils.Extensions; using CliFx; +using Isopoh.Cryptography.Argon2; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/Akari.Prototype.Server/Services/AuthManager.cs b/Akari.Prototype.Server/Services/AuthManager.cs index 51823b4..941372c 100644 --- a/Akari.Prototype.Server/Services/AuthManager.cs +++ b/Akari.Prototype.Server/Services/AuthManager.cs @@ -6,10 +6,12 @@ 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; @@ -18,25 +20,75 @@ 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) + 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, Encoding.UTF8.GetBytes(name), AuthKeysLength, clear: true); + using var key = Security.Argon2idDeriveBytes(token, Convert.FromBase64String(salt), AuthKeysLength, clear: true); SetKey(name, new AesGcm(key.Buffer)); } diff --git a/Akari.Prototype.Server/Services/FingerprintManager.cs b/Akari.Prototype.Server/Services/FingerprintManager.cs index 09a072d..f59a37f 100644 --- a/Akari.Prototype.Server/Services/FingerprintManager.cs +++ b/Akari.Prototype.Server/Services/FingerprintManager.cs @@ -67,6 +67,8 @@ namespace Akari.Prototype.Server.Services { SaveFingerprints(); + _authManager.GenerateSalt(name); + return true; } diff --git a/Akari.Prototype.Server/Services/IAuthManager.cs b/Akari.Prototype.Server/Services/IAuthManager.cs index bc130ce..ac28b5b 100644 --- a/Akari.Prototype.Server/Services/IAuthManager.cs +++ b/Akari.Prototype.Server/Services/IAuthManager.cs @@ -11,6 +11,8 @@ namespace Akari.Prototype.Server.Services { IEnumerable>> Pairs { get; } + void GenerateSalt(string name); + void Auth(string name, byte[] token); bool Remove(string name); diff --git a/Akari.Prototype.Server/Services/TcpProviderService.cs b/Akari.Prototype.Server/Services/TcpProviderService.cs index c193ffc..dab6c53 100644 --- a/Akari.Prototype.Server/Services/TcpProviderService.cs +++ b/Akari.Prototype.Server/Services/TcpProviderService.cs @@ -47,7 +47,7 @@ namespace Akari.Prototype.Server.Services _logger.LogInformation($"Now listening on: {_listener.LocalEndpoint}"); - return Task.CompletedTask; + return base.StartAsync(cancellationToken); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -126,7 +126,7 @@ namespace Akari.Prototype.Server.Services position += read; } - var text = Encoding.UTF8.GetString(data.Span); + var text = Encoding.UTF8.GetString(data[..position].Span); var splitIndex = text.IndexOf('$'); _logger.LogDebug($"Received text: {text}"); @@ -138,9 +138,14 @@ namespace Akari.Prototype.Server.Services var handle = GCHandle.Alloc(text, GCHandleType.Pinned); - _fingerprintManager.VerifyFingerprint(text[..splitIndex], Convert.FromBase64String(text[(splitIndex + 1)..])); - - handle.Free(); + try + { + _fingerprintManager.VerifyFingerprint(text[..splitIndex], Convert.FromBase64String(text[(splitIndex + 1)..])); + } + finally + { + handle.Free(); + } } public override void Dispose()