Skip to content

Commit

Permalink
💾 Feat(Dashboard): Accept remote KWC invoking.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dynesshely committed Mar 14, 2024
1 parent c6218ec commit dc21831
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 11 deletions.
36 changes: 32 additions & 4 deletions KitX Dashboard/Managers/SecurityManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Common.BasicHelper.Utils;
using Common.BasicHelper.Utils.Extensions;
using DynamicData;
using KitX.Dashboard.Configuration;
using KitX.Dashboard.Network.DevicesNetwork;
using KitX.Shared.CSharp.Device;
using static System.Runtime.InteropServices.JavaScript.JSType;
using KitX.Shared.CSharp.Security;

namespace KitX.Dashboard.Managers;

Expand Down Expand Up @@ -135,7 +136,7 @@ public static bool IsDeviceKeyCorrect(DeviceLocator locator, DeviceKey key)

public static string? RsaEncryptString(DeviceKey key, string data)
{
if (data.Length >= 90) { /* ToDo: Split data */ }
if (data.Length >= 90) throw new ArgumentOutOfRangeException(nameof(data), "Data length is too long.");

using var rsa = RSA.Create(2048);

Expand All @@ -150,8 +151,6 @@ public static bool IsDeviceKeyCorrect(DeviceLocator locator, DeviceKey key)

public static string? RsaDecryptString(DeviceKey key, string encryptedData)
{
if (encryptedData.Length >= 90) { /* ToDo: Split data */ }

using var rsa = RSA.Create(2048);

rsa.ImportFromPem(key.RsaPrivateKeyPem);
Expand All @@ -161,6 +160,33 @@ public static bool IsDeviceKeyCorrect(DeviceLocator locator, DeviceKey key)
return rsa.Decrypt(dataBytes, RSAEncryptionPadding.OaepSHA256).ToUTF8();
}

public static EncryptedContent RsaEncryptContent(DeviceKey key, string content)
{
var aesKey = Password.GeneratePassword(length: 16);

var encryptedAesKey = RsaEncryptString(key, aesKey);

var encryptedContent = AesEncrypt(content, aesKey);

return new EncryptedContent
{
Device = key.Device,
RsaEncryptedAesKeyBase64 = encryptedAesKey,
AesEncryptedContentBase64 = encryptedContent,
};
}

public static string RsaDecryptContent(DeviceKey key, EncryptedContent content)
{
ArgumentNullException.ThrowIfNull(content.RsaEncryptedAesKeyBase64, nameof(content.RsaEncryptedAesKeyBase64));

ArgumentNullException.ThrowIfNull(content.AesEncryptedContentBase64, nameof(content.AesEncryptedContentBase64));

var aesKey = RsaDecryptString(key, content.RsaEncryptedAesKeyBase64);

return AesDecrypt(content.AesEncryptedContentBase64, aesKey!);
}

public static string GetSHA1(string data)
{
var hash = SHA1.HashData(data.FromUTF8());
Expand Down Expand Up @@ -231,5 +257,7 @@ public static string AesDecrypt(string source, string key, bool isSourceInBase64
public void Dispose()
{
RsaInstance?.Dispose();

GC.SuppressFinalize(this);
}
}
2 changes: 0 additions & 2 deletions KitX Dashboard/Models/DeviceCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
using System.Net.Http;
using System.Reactive;
using System.Text;
using System.Text.Encodings.Web;
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;
Expand Down
7 changes: 7 additions & 0 deletions KitX Dashboard/Network/DevicesNetwork/DevicesServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ private IHostBuilder CreateHostBuilder(string[] args) =>

internal bool IsDeviceTokenExist(string token) => SignedDeviceTokens.ContainsValue(token);

internal DeviceLocator? SearchDeviceByToken(string token)
{
if (IsDeviceTokenExist(token) == false) return null;

return SignedDeviceTokens.First(x => x.Value.Equals(token)).Key;
}

internal bool IsDeviceSignedIn(DeviceLocator locator) => SignedDeviceTokens.ContainsKey(locator);

internal void AddDeviceToken(DeviceLocator locator, string token) => SignedDeviceTokens.Add(locator, token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public IActionResult ExchangeKey([FromQuery] string verifyCodeSHA1, [FromQuery]
new StringBuilder()
.AppendLine($"Requested: {url}")
.AppendLine($"Responsed: {response.StatusCode} - {response.ReasonPhrase}")
.AppendLine($"Content: {await response.Content.ReadAsStringAsync()}")
.AppendLine(response.RequestMessage?.ToString())
.ToString()
);
Expand Down Expand Up @@ -192,3 +193,8 @@ public IActionResult Connect([FromQuery] string deviceBase64, [FromBody] string
return Ok(SecurityManager.Instance.EncryptString(token));
}
}

public static class DeviceControllerExtensions
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Common.BasicHelper.Utils.Extensions;
using KitX.Dashboard.Managers;
using KitX.Dashboard.Network.PluginsNetwork;
using KitX.Shared.CSharp.Device;
using KitX.Shared.CSharp.Security;
using KitX.Shared.CSharp.WebCommand;
using Microsoft.AspNetCore.Mvc;

namespace KitX.Dashboard.Network.DevicesNetwork.DevicesServerControllers.V1;

[ApiController]
[Route("Api/V1/[controller]")]
[ApiExplorerSettings(GroupName = "V1")]
public class PluginController : ControllerBase
{
[ApiExplorerSettings(GroupName = "V1")]
[HttpPost("Invoke", Name = nameof(Invoke))]
public IActionResult Invoke([FromQuery] string token, [FromBody] string requestJsonBase64)
{
if (DevicesServer.Instance.IsDeviceTokenExist(token))
{
var requestJson = Convert.FromBase64String(requestJsonBase64).ToUTF8();

var request = JsonSerializer.Deserialize<Request>(requestJson);

if (request is null) return BadRequest($"Wrong format of {nameof(requestJson)}");

var noTarget = request.Target is null;

var isMe = request.Target?.IsSameDevice(DevicesDiscoveryServer.Instance.DefaultDeviceInfo.Device) ?? false;

var isNotMe = !isMe;

if (noTarget || isNotMe) return BadRequest(noTarget ? "Provide target field please." : "Please send to actual target.");

var content = request.GetContent(toDecrypt =>
{
if (request.EncryptionInfo.IsEncrypted)
{
switch (request.EncryptionInfo.EncryptionMethod)
{
case Shared.CSharp.WebCommand.Infos.EncryptionMethods.Custom:
// ToDo: Add custom encryption method support
throw new NotImplementedException();
case Shared.CSharp.WebCommand.Infos.EncryptionMethods.RSA:
var device = DevicesServer.Instance.SearchDeviceByToken(token);
ArgumentNullException.ThrowIfNull(device, nameof(device));
var key = SecurityManager.SearchDeviceKey(device);
ArgumentNullException.ThrowIfNull(key, nameof(key));
var toDecryptContent = JsonSerializer.Deserialize<EncryptedContent>(toDecrypt);
ArgumentNullException.ThrowIfNull(toDecryptContent, nameof(toDecryptContent));
return SecurityManager.RsaDecryptContent(key, toDecryptContent);
case Shared.CSharp.WebCommand.Infos.EncryptionMethods.AES:
// ToDo: Add AES encryption method support
throw new NotImplementedException();
}
throw new InvalidOperationException("Invalid encryption method.");
}
else return toDecrypt;
});

request.Match(content, matchCommand: command =>
{
var kwc = JsonSerializer.Deserialize<Command>(command);
var connector = PluginsServer.Instance.FindConnector(kwc.PluginConnectionId);
if (connector is null) return;
connector.Request(request.Rebuild(request =>
{
request.Content = command;
return request;
}));
});

return Ok();
}
else
{
return BadRequest("You should connect to this device first.");
}
}
}

public static class PluginControllerExtensions
{
public static async Task<HttpResponseMessage> RemoteInvoke(this string targetAddress, string token, Request request)
{
var requestJson = JsonSerializer.Serialize(request);

var requestJsonBase64 = Convert.ToBase64String(requestJson.FromUTF8());

var toSend = JsonSerializer.Serialize(requestJsonBase64);

var url = $"http://{targetAddress}/Api/V1/Plugin/Invoke?token={token}";

using var http = new HttpClient();

var response = await http.PostAsync(
url,
new StringContent(toSend, Encoding.UTF8, "application/json")
);

return response;
}
}
2 changes: 2 additions & 0 deletions KitX Dashboard/Network/PluginsNetwork/PluginConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ public PluginConnector Run()
{
var kwc = JsonSerializer.Deserialize<Request>(message, serializerOptions);
if (kwc is null) return;
var command = JsonSerializer.Deserialize<Command>(kwc.Content, serializerOptions);
switch (command.Request)
Expand Down
19 changes: 18 additions & 1 deletion KitX Dashboard/Network/PluginsNetwork/PluginsServer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Fleck;
using KitX.Dashboard.Configuration;
Expand All @@ -9,7 +10,7 @@

namespace KitX.Dashboard.Network.PluginsNetwork;

public class PluginsServer : ConfigFetcher
public partial class PluginsServer : ConfigFetcher
{
private static PluginsServer? _pluginsServer;

Expand Down Expand Up @@ -41,6 +42,15 @@ public PluginsServer Run()

_server!.Start(socket =>
{
if (RegexToVerifyConnectionId().IsMatch(socket.ConnectionInfo.Path.Trim('/')) == false)
{
socket.Send("Invalid connection id.");
socket.Close();
return;
}
var connector = new PluginConnector(socket).Run();
_connectors.Add(connector);
Expand All @@ -63,6 +73,10 @@ public PluginsServer Run()
return null;
}

public PluginConnector? FindConnector(string connectionId) => PluginConnectors.FirstOrDefault(
x => x.ConnectionId?.Equals(connectionId) ?? false
);

public async Task<PluginsServer> Close()
{
await Task.Run(() =>
Expand All @@ -82,4 +96,7 @@ await Task.Run(() =>

return this;
}

[GeneratedRegex(@"^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$")]
private static partial Regex RegexToVerifyConnectionId();
}
4 changes: 2 additions & 2 deletions KitX Dashboard/Styles/FontsStyles.axaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls">
<Style Selector="Window">
<!--<Style Selector="Window">
<Setter Property="FontFamily" Value="{DynamicResource SourceHanSans}"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
Expand All @@ -11,5 +11,5 @@
</Style>
<Style Selector="ui|InfoBar">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
</Style>
</Style>-->
</Styles>
1 change: 0 additions & 1 deletion KitX Dashboard/ViewModels/Pages/DevicePageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Threading.Tasks;
using KitX.Dashboard.Models;
using KitX.Dashboard.Views;
using KitX.Shared.CSharp.Device;
using ReactiveUI;

namespace KitX.Dashboard.ViewModels.Pages;
Expand Down
1 change: 0 additions & 1 deletion KitX Dashboard/Views/ViewInstances.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Controls;
using DynamicData;
using KitX.Dashboard.Models;
using KitX.Dashboard.Services;
using KitX.Shared.CSharp.Plugin;
Expand Down

0 comments on commit dc21831

Please sign in to comment.