Skip to content

Commit

Permalink
💾 Feat: Try to connect to remote device and get token.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dynesshely committed Mar 6, 2024
1 parent 54be359 commit 539db95
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 29 deletions.
82 changes: 81 additions & 1 deletion KitX Dashboard/Managers/SecurityManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
Expand All @@ -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());
Expand Down
77 changes: 71 additions & 6 deletions KitX Dashboard/Models/DeviceCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<DeviceInfo, Unit>? AuthorizeAndExchangeDeviceKeyCommand { get; set; }

internal ReactiveCommand<DeviceInfo, Unit>? UnAuthorizeCommand { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
22 changes: 19 additions & 3 deletions KitX Dashboard/Network/DevicesNetwork/DevicesServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,7 +27,7 @@ public class DevicesServer : ConfigFetcher

private IHost? _host;

private List<string> SignedDeviceTokens { get; } = [];
private Dictionary<DeviceLocator, string> SignedDeviceTokens { get; } = [];

public async Task<DevicesServer> RunAsync()
{
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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<DeviceKey>(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));
}
}
47 changes: 39 additions & 8 deletions KitX Dashboard/Views/Pages/DevicePage.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,50 @@
<Border/>
</DockPanel>
</StackPanel>
<Grid Margin="0,5,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Top">
<Grid.Styles>
<StackPanel Margin="0,5,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Orientation="Horizontal"
Spacing="10">
<StackPanel.Styles>
<Style Selector="Border">
<Setter Property="Width" Value="15"/>
<Setter Property="Height" Value="15"/>
<Setter Property="CornerRadius" Value="15"/>
</Style>
</Grid.Styles>
<Border Background="{DynamicResource ThemePrimaryAccent}" IsVisible="{Binding IsAuthorized}"/>
<Border Background="Gray" IsVisible="{Binding !IsAuthorized}"/>
</Grid>
</StackPanel.Styles>
<Border Classes.authorized="{Binding IsAuthorized}" ToolTip.Tip="{Binding ConnectionToken}">
<Border.Styles>
<Style Selector="Border.authorized">
<Setter Property="BorderBrush" Value="{DynamicResource ThemePrimaryAccent}"/>
</Style>
</Border.Styles>
<Border.Transitions>
<ColorTransition Property="BorderBrush" Duration="0:0:0.2">
<ColorTransition.Easing>
<CubicEaseInOut/>
</ColorTransition.Easing>
</ColorTransition>
</Border.Transitions>

<Border Margin="3" Classes.connected="{Binding IsConnected}">
<Border.Styles>
<Style Selector="Border.connected">
<Setter Property="Background" Value="{DynamicResource ThemePrimaryAccent}"/>
</Style>
</Border.Styles>
<Border.Transitions>
<ColorTransition Property="Background" Duration="0:0:0.2">
<ColorTransition.Easing>
<CubicEaseInOut/>
</ColorTransition.Easing>
</ColorTransition>
</Border.Transitions>

</Border>

</Border>
</StackPanel>
</Grid>
</Button>
</DataTemplate>
Expand Down

0 comments on commit 539db95

Please sign in to comment.