diff --git a/KitX Dashboard/Managers/SecurityManager.cs b/KitX Dashboard/Managers/SecurityManager.cs index f42202c..151dd32 100644 --- a/KitX Dashboard/Managers/SecurityManager.cs +++ b/KitX Dashboard/Managers/SecurityManager.cs @@ -38,7 +38,29 @@ private void Initialize() LocalDeviceKey = SecurityConfig.DeviceKeys.FirstOrDefault(x => x.Device.IsSameDevice(device)); - if (LocalDeviceKey is not null) return; + if (LocalDeviceKey is not null) + { + var isPublicKeyNotExists = LocalDeviceKey.RsaPublicKeyModulus is null || LocalDeviceKey.RsaPublicKeyExponent is null; + + var isPrivateKeyNotExists = LocalDeviceKey.RsaPrivateKeyModulus is null || LocalDeviceKey.RsaPrivateKeyD is null; + + if (isPrivateKeyNotExists || isPublicKeyNotExists) AddLocalDevice(device); + + var rsaParams = new RSAParameters + { + Modulus = Convert.FromBase64String(LocalDeviceKey.RsaPublicKeyModulus!), + Exponent = Convert.FromBase64String(LocalDeviceKey.RsaPublicKeyExponent!), + D = Convert.FromBase64String(LocalDeviceKey.RsaPrivateKeyD!), + }; + + RsaInstance = RSA.Create(); + + RsaInstance.KeySize = 2048; + + RsaInstance.ImportParameters(rsaParams); + + return; + } AddLocalDevice(device); } @@ -89,6 +111,17 @@ public SecurityManager RemoveDeviceKey(DeviceInfo deviceInfo) return this; } + public static DeviceKey? SearchDeviceKey(DeviceLocator locator) => SecurityConfig.DeviceKeys.FirstOrDefault(x => x.Device.IsSameDevice(locator)); + + public static bool IsDeviceKeyCorrect(DeviceLocator locator, DeviceKey key) + { + var existing = SearchDeviceKey(locator); + + if (existing is null) return false; + + return existing.IsSameKey(key); + } + public static bool IsDeviceAuthorized(DeviceLocator device) => SecurityConfig.DeviceKeys.Any(x => x.Device.IsSameDevice(device)); public string? EncryptString(string data) @@ -111,6 +144,53 @@ public SecurityManager RemoveDeviceKey(DeviceInfo deviceInfo) return RsaInstance.Decrypt(encryptedDataBytes, RSAEncryptionPadding.OaepSHA256).ToUTF8(); } + public DeviceKey? GetPrivateDeviceKey() => LocalDeviceKey is null ? null : new DeviceKey() + { + Device = LocalDeviceKey.Device, + RsaPrivateKeyD = LocalDeviceKey.RsaPrivateKeyD, + RsaPrivateKeyModulus = LocalDeviceKey.RsaPrivateKeyModulus, + }; + + public static string? RsaEncryptString(DeviceKey key, string data) + { + using var rsa = RSA.Create(); + + rsa.KeySize = 2048; + + var rsaParams = new RSAParameters + { + Modulus = Convert.FromBase64String(key.RsaPublicKeyModulus!), + Exponent = Convert.FromBase64String(key.RsaPublicKeyExponent!), + }; + + rsa.ImportParameters(rsaParams); + + var dataBytes = data.FromUTF8(); + + return Convert.ToBase64String( + rsa.Encrypt(dataBytes, RSAEncryptionPadding.OaepSHA256) + ); + } + + public static string? RsaDecryptString(DeviceKey key, string encryptedData) + { + using var rsa = RSA.Create(); + + rsa.KeySize = 2048; + + var rsaParams = new RSAParameters + { + Modulus = Convert.FromBase64String(key.RsaPrivateKeyModulus!), + D = Convert.FromBase64String(key.RsaPrivateKeyD!), + }; + + rsa.ImportParameters(rsaParams); + + var dataBytes = Convert.FromBase64String(encryptedData); + + return rsa.Decrypt(dataBytes, RSAEncryptionPadding.OaepSHA256).ToUTF8(); + } + public static string GetSHA1(string data) { var hash = SHA1.HashData(data.FromUTF8()); diff --git a/KitX Dashboard/Models/DeviceCase.cs b/KitX Dashboard/Models/DeviceCase.cs index 0c81745..078bf56 100644 --- a/KitX Dashboard/Models/DeviceCase.cs +++ b/KitX Dashboard/Models/DeviceCase.cs @@ -5,6 +5,7 @@ using System.Text.Json; using Avalonia.Threading; using Common.BasicHelper.Utils.Extensions; +using Fizzler; using KitX.Dashboard.Managers; using KitX.Dashboard.Network.DevicesNetwork; using KitX.Dashboard.Network.DevicesNetwork.DevicesServerControllers.V1; @@ -74,12 +75,7 @@ public override void InitCommands() return; } - var localKey = new DeviceKey() - { - Device = SecurityManager.Instance.LocalDeviceKey.Device, - RsaPrivateKeyD = SecurityManager.Instance.LocalDeviceKey.RsaPrivateKeyD, - RsaPrivateKeyModulus = SecurityManager.Instance.LocalDeviceKey.RsaPrivateKeyModulus - }; + var localKey = SecurityManager.Instance.GetPrivateDeviceKey(); var localKeyJson = JsonSerializer.Serialize(localKey); @@ -194,22 +190,91 @@ public DeviceInfo DeviceInfo this.RaiseAndSetIfChanged(ref deviceInfo, value); Update(); + + if (IsAuthorized && (IsConnected == false)) + Connect(); } } private void Update() { this.RaisePropertyChanged(nameof(IsAuthorized)); + this.RaisePropertyChanged(nameof(IsConnected)); this.RaisePropertyChanged(nameof(IsCurrentDevice)); this.RaisePropertyChanged(nameof(IsMainDevice)); } + private void Connect() + { + TasksManager.RunTask(async () => + { + using var http = new HttpClient(); + + var targetKey = SecurityManager.SearchDeviceKey(DeviceInfo.Device); + + if (targetKey is null) return; + + var localKey = SecurityManager.Instance.GetPrivateDeviceKey(); + + var localKeyJson = JsonSerializer.Serialize(localKey); + + var localKeyEncrypted = SecurityManager.RsaEncryptString(targetKey, localKeyJson); + + var address = $"{DeviceInfo.Device.IPv4}:{DeviceInfo.DevicesServerPort}"; + + var url = $"http://{address}/Api/V1/Device/{nameof(DeviceController.Connect)}"; + + var response = await http.PostAsync( + url, + new StringContent( + JsonSerializer.Serialize(localKeyEncrypted), + Encoding.UTF8, + "application/json" + ) + ); + + if (response.IsSuccessStatusCode) + { + Log.Information($"Connected to {DeviceInfo.Device.DeviceName} with response {response}"); + + var body = response.Content.ToString(); + + if (body is null) return; + + ConnectionToken = SecurityManager.RsaDecryptString(targetKey, body); + + Update(); + } + else + { + Log.Warning( + new StringBuilder() + .AppendLine($"Requested: {url}") + .AppendLine($"Responsed: {response.StatusCode} - {response.ReasonPhrase}") + .AppendLine(response.RequestMessage?.ToString()) + .ToString() + ); + } + + }, $"Connecting {DeviceInfo.Device.DeviceName}"); + } + public bool IsAuthorized => SecurityManager.IsDeviceAuthorized(DeviceInfo.Device); + public bool IsConnected => DevicesServer.Instance.IsDeviceSignedIn(DeviceInfo.Device) || ConnectionToken is not null; + public bool IsCurrentDevice => DeviceInfo.IsCurrentDevice(); public bool IsMainDevice => DeviceInfo.IsMainDevice; + private string? connectionToken; + + public string? ConnectionToken + { + get => connectionToken; + set => this.RaiseAndSetIfChanged(ref connectionToken, value); + } + internal ReactiveCommand? AuthorizeAndExchangeDeviceKeyCommand { get; set; } internal ReactiveCommand? UnAuthorizeCommand { get; set; } diff --git a/KitX Dashboard/Network/DevicesNetwork/DevicesDiscoveryServer.cs b/KitX Dashboard/Network/DevicesNetwork/DevicesDiscoveryServer.cs index cece157..916ce5a 100644 --- a/KitX Dashboard/Network/DevicesNetwork/DevicesDiscoveryServer.cs +++ b/KitX Dashboard/Network/DevicesNetwork/DevicesDiscoveryServer.cs @@ -416,10 +416,6 @@ public static bool IsCurrentDevice(this DeviceInfo info) public static bool IsSameDevice(this DeviceInfo info, DeviceInfo target) => info.Device.IsSameDevice(target.Device); - public static bool IsSameDevice(this DeviceLocator current, DeviceLocator target) - => current.DeviceName.Equals(target.DeviceName) - && current.MacAddress.Equals(target.MacAddress); - public static void UpdateTo(this DeviceInfo info, DeviceInfo target) { var type = typeof(DeviceInfo); diff --git a/KitX Dashboard/Network/DevicesNetwork/DevicesServer.cs b/KitX Dashboard/Network/DevicesNetwork/DevicesServer.cs index 2d9e35c..168ba1d 100644 --- a/KitX Dashboard/Network/DevicesNetwork/DevicesServer.cs +++ b/KitX Dashboard/Network/DevicesNetwork/DevicesServer.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using KitX.Dashboard.Configuration; using KitX.Dashboard.Services; +using KitX.Shared.CSharp.Device; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; @@ -26,7 +27,7 @@ public class DevicesServer : ConfigFetcher private IHost? _host; - private List SignedDeviceTokens { get; } = []; + private Dictionary SignedDeviceTokens { get; } = []; public async Task RunAsync() { @@ -72,9 +73,24 @@ private IHostBuilder CreateHostBuilder(string[] args) => }) ; - internal bool IsDeviceTokenExist(string token) => SignedDeviceTokens.Contains(token); + internal bool IsDeviceTokenExist(string token) => SignedDeviceTokens.ContainsValue(token); - internal void AddDeviceToken(string token) => SignedDeviceTokens.Add(token); + internal bool IsDeviceSignedIn(DeviceLocator locator) => SignedDeviceTokens.ContainsKey(locator); + + internal void AddDeviceToken(DeviceLocator locator, string token) => SignedDeviceTokens.Add(locator, token); + + internal string SignInDevice(DeviceLocator locator) + { + var token = Guid.NewGuid().ToString(); + + while (SignedDeviceTokens.ContainsValue(token)) + token = Guid.NewGuid().ToString(); + + if (SignedDeviceTokens.TryAdd(locator, token) == false) + SignedDeviceTokens[locator] = token; + + return token; + } } public class Startup diff --git a/KitX Dashboard/Network/DevicesNetwork/DevicesServerControllers/V1/DeviceController.cs b/KitX Dashboard/Network/DevicesNetwork/DevicesServerControllers/V1/DeviceController.cs index ac8162a..f2ce1c2 100644 --- a/KitX Dashboard/Network/DevicesNetwork/DevicesServerControllers/V1/DeviceController.cs +++ b/KitX Dashboard/Network/DevicesNetwork/DevicesServerControllers/V1/DeviceController.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using System.Net; +using System.Net.Http; using System.Text; using System.Text.Json; using Avalonia.Threading; @@ -65,12 +66,7 @@ public IActionResult ExchangeKey([FromQuery] string verifyCodeSHA1, [FromQuery] return; } - var currentKey = new DeviceKey() - { - Device = SecurityManager.Instance.LocalDeviceKey.Device, - RsaPrivateKeyD = SecurityManager.Instance.LocalDeviceKey.RsaPrivateKeyD, - RsaPrivateKeyModulus = SecurityManager.Instance.LocalDeviceKey.RsaPrivateKeyModulus, - }; + var currentKey = SecurityManager.Instance.GetPrivateDeviceKey(); if (currentKey is null) { @@ -170,4 +166,28 @@ public IActionResult CancelExchangingKey() return Ok(); } + + [ApiExplorerSettings(GroupName = "V1")] + [HttpPost(nameof(Connect), Name = nameof(Connect))] + public IActionResult Connect([FromQuery] DeviceLocator device, [FromBody] string deviceKeyEncrypted) + { + var key = SecurityManager.SearchDeviceKey(device); + + if (key is null) return BadRequest("You are not authorized by remote device."); + + var deviceKeyDecrypted = SecurityManager.Instance.DecryptString(deviceKeyEncrypted); + + if (deviceKeyDecrypted is null) return StatusCode(500, "Remote crashed when decrypting device key."); + + var deviceKey = JsonSerializer.Deserialize(deviceKeyDecrypted); + + if (deviceKey is null) return StatusCode(500, "Remote crashed when deserializing device key."); + + if (SecurityManager.IsDeviceKeyCorrect(device, deviceKey) == false) + return BadRequest("You provided incorrect device key."); + + var token = DevicesServer.Instance.SignInDevice(device); + + return Ok(SecurityManager.Instance.EncryptString(token)); + } } diff --git a/KitX Dashboard/Views/Pages/DevicePage.axaml b/KitX Dashboard/Views/Pages/DevicePage.axaml index 2541749..3d8f14a 100644 --- a/KitX Dashboard/Views/Pages/DevicePage.axaml +++ b/KitX Dashboard/Views/Pages/DevicePage.axaml @@ -169,19 +169,50 @@ - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + +